Repository: narwhals-dev/narwhals
Pull request #97 (2024-05-07 04:49:11)

Add docstring for Expr.is_between()
Towards #71 


Patch:
diff --git a/narwhals/_pandas_like/series.py b/narwhals/_pandas_like/series.py
index 64c64b92..25363e48 100644
--- a/narwhals/_pandas_like/series.py
+++ b/narwhals/_pandas_like/series.py
@@ -150,7 +150,17 @@ class PandasSeries:
         self, lower_bound: Any, upper_bound: Any, closed: str = "both"
     ) -> PandasSeries:
         ser = self._series
-        return self._from_series(ser.between(lower_bound, upper_bound, inclusive=closed))
+        if closed == "left":
+            res = ser.ge(lower_bound) & ser.lt(upper_bound)
+        elif closed == "right":
+            res = ser.gt(lower_bound) & ser.le(upper_bound)
+        elif closed == "none":
+            res = ser.gt(lower_bound) & ser.lt(upper_bound)
+        elif closed == "both":
+            res = ser.ge(lower_bound) & ser.le(upper_bound)
+        else:  # pragma: no cover
+            raise AssertionError
+        return self._from_series(res)
 
     def is_in(self, other: Any) -> PandasSeries:
         import pandas as pd
diff --git a/narwhals/expression.py b/narwhals/expression.py
index 6d3d8ef3..7f712f04 100644
--- a/narwhals/expression.py
+++ b/narwhals/expression.py
@@ -727,6 +727,52 @@ class Expr:
     def is_between(
         self, lower_bound: Any, upper_bound: Any, closed: str = "both"
     ) -> Expr:
+        """
+        Check if this expression is between the given lower and upper bounds.
+
+        Arguments:
+            lower_bound: Lower bound value.
+
+            upper_bound: Upper bound value.
+
+            closed: Define which sides of the interval are closed (inclusive).
+
+        Examples:
+            >>> import pandas as pd
+            >>> import polars as pl
+            >>> import narwhals as nw
+            >>> df_pd = pd.DataFrame({'a': [1, 2, 3, 4, 5]})
+            >>> df_pl = pl.DataFrame({'a': [1, 2, 3, 4, 5]})
+
+            Let's define a dataframe-agnostic function:
+            >>> def func(df_any):
+            ...     df = nw.from_native(df_any)
+            ...     df = df.select(nw.col('a').is_between(2,4,'right'))
+            ...     return nw.to_native(df)
+
+            We can then pass either pandas or Polars to `func`:
+
+            >>> func(df_pd)
+                   a
+            0  False
+            1  False
+            2   True
+            3   True
+            4  False
+            >>> func(df_pl)
+            shape: (5, 1)
+            ┌───────┐
+            │ a     │
+            │ ---   │
+            │ bool  │
+            ╞═══════╡
+            │ false │
+            │ false │
+            │ true  │
+            │ true  │
+            │ false │
+            └───────┘
+        """
         return self.__class__(
             lambda plx: self._call(plx).is_between(lower_bound, upper_bound, closed)
         )


Test patch:
diff --git a/tests/expr/is_between_test.py b/tests/expr/is_between_test.py
new file mode 100644
index 00000000..b2832663
--- /dev/null
+++ b/tests/expr/is_between_test.py
@@ -0,0 +1,33 @@
+from __future__ import annotations
+
+from typing import Any
+
+import pandas as pd
+import polars as pl
+import pytest
+
+import narwhals as nw
+from tests.utils import compare_dicts
+
+data = {
+    "a": [1, 4, 2, 5],
+}
+
+
+@pytest.mark.parametrize("constructor", [pd.DataFrame, pl.DataFrame])
+@pytest.mark.parametrize(
+    ("closed", "expected"),
+    [
+        ("left", [True, True, True, False]),
+        ("right", [False, True, True, True]),
+        ("both", [True, True, True, True]),
+        ("none", [False, True, True, False]),
+    ],
+)
+def test_over_single(constructor: Any, closed: str, expected: list[bool]) -> None:
+    df = nw.from_native(constructor(data), eager_only=True)
+    result = df.select(nw.col("a").is_between(1, 5, closed=closed))
+    expected_dict = {"a": expected}
+    compare_dicts(result, expected_dict)
+    result = df.with_columns(a=df["a"].is_between(1, 5, closed=closed))
+    compare_dicts(result, expected_dict)