Repository: narwhals-dev/narwhals

Issue:
[Bug]: is_between incorrect behaviour (TypeError: cannot create expression literal for value of type Expr)
### Describe the bug

When attempting to use nw.lit to create literal expressions for filtering dates in a Narwhal DataFrame, the following error occurs:

`TypeError: cannot create expression literal for value of type Expr.`

This happens specifically when using nw.lit in combination with `.filter()` and `.is_between()` for date-based comparisons. However, casting and aliasing the literals and then using them in `.to_native()` before filtering resolves the issue (but I can't do this since I want to have agnostic dataframe filtering.

### Steps or code to reproduce the bug

Setup:

```python
import polars as pl
import narwhal as nw

start_train_date = "2024-01-01"
end_train_date = "2024-03-05"

df_polars = pl.DataFrame({
    "application_started_at": [
        "2024-01-01", "2024-02-01", "2024-03-01", "2024-04-01", "2024-04-06",
    ]
})

df_native = nw.from_native(df_polars)
```

then:

```python
# This will raise the TypeError
df_native.select(
    nw.col("application_started_at").cast(nw.Date).dt.date(),
    nw.lit(start_train_date).cast(nw.Date).alias("a"),
    nw.lit(end_train_date).cast(nw.Date).alias("b")
).filter(
    nw.col("application_started_at").is_between(nw.col("a"), nw.col("b"), closed="both")
).to_native()
```

to get the error and:

```python
(
    df_native
    .select(
        nw.col(col_time).cast(nw.Date).dt.date(),
        nw.lit(start_train_date).cast(nw.Date).alias("a"),
        nw.lit(end_train_date).cast(nw.Date).alias("b")
    )
    .to_native()
    .filter(
        pl.col(col_time).is_between(pl.col("a"), pl.col("b"), closed="both")
    )
)
```

to get the expected results.

### Expected results

application_started_at | a | b
-- | -- | --
2024-01-01 | 2024-01-01 | 2024-03-05
2024-02-01 | 2024-01-01 | 2024-03-05
2024-03-01 | 2024-01-01 | 2024-03-05

### Actual results

`TypeError: cannot create expression literal for value of type Expr.`

### Please run narwhals.show_version() and enter the output below.

```shell
1.19.1
```


### Relevant log output

```shell
TypeError: cannot create expression literal for value of type Expr.

Hint: Pass `allow_object=True` to accept any value and create a literal of type Object.
File <command-332563390273393>, line 19
      4 df_polars = pl.DataFrame({
      5     "application_started_at": [
      6         "2024-01-01", "2024-02-01", "2024-03-01", "2024-04-01", "2024-04-06",
      7     ]
      8 })
     10 df_native = nw.from_native(df_polars)
     12 (
     13     df_native
     14     .select(
     15         nw.col(col_time).cast(nw.Date).dt.date(),
     16         nw.lit(start_train_date).cast(nw.Date).alias("a"),
     17         nw.lit(end_train_date).cast(nw.Date).alias("b")
     18     )
---> 19     .filter(
     20         nw.col(col_time).is_between(nw.col("a"), nw.col("b"), closed="both")
     21     )
     22     .to_native()
     23 )
File /local_disk0/.ephemeral_nfs/envs/pythonEnv-ad765748-653a-475d-a18a-358020328a68/lib/python3.11/site-packages/polars/functions/lit.py:179, in lit(value, dtype, allow_object)
    176 except AttributeError:
    177     item = value
--> 179 return wrap_expr(plr.lit(item, allow_object, is_scalar=True))
```


Discussion:
FBruzzesi: On a second thought, we are creating some asymmetry between what we can pass to `Series.is_between` vs `Expr.is_between`, aren't we? I would expect to be able to pass series' to  `Series.is_between`, e.g.

```py
some_series.is_between(lower_bound_series, upper_bound_series)
``` 

Apologies if this is already possible, if so we may want to add a test case for it

Patch:
diff --git a/docs/how_it_works.md b/docs/how_it_works.md
index 70bc54bf..6a670358 100644
--- a/docs/how_it_works.md
+++ b/docs/how_it_works.md
@@ -266,7 +266,6 @@ In order to tell whether an aggregation is simple, Narwhals uses the private `_d
 ```python exec="1" result="python" session="pandas_impl" source="above"
 print(pn.col("a").mean())
 print((pn.col("a") + 1).mean())
-print(pn.mean("a"))
 ```
 
 For simple aggregations, Narwhals can just look at `_depth` and `function_name` and figure out
diff --git a/narwhals/_arrow/namespace.py b/narwhals/_arrow/namespace.py
index 884bc8f0..ea37ae76 100644
--- a/narwhals/_arrow/namespace.py
+++ b/narwhals/_arrow/namespace.py
@@ -348,31 +348,6 @@ class ArrowNamespace(CompliantNamespace[ArrowSeries]):
             result_table, backend_version=self._backend_version, version=self._version
         )
 
-    def sum(self: Self, *column_names: str) -> ArrowExpr:
-        return ArrowExpr.from_column_names(
-            *column_names, backend_version=self._backend_version, version=self._version
-        ).sum()
-
-    def mean(self: Self, *column_names: str) -> ArrowExpr:
-        return ArrowExpr.from_column_names(
-            *column_names, backend_version=self._backend_version, version=self._version
-        ).mean()
-
-    def median(self: Self, *column_names: str) -> ArrowExpr:
-        return ArrowExpr.from_column_names(
-            *column_names, backend_version=self._backend_version, version=self._version
-        ).median()
-
-    def max(self: Self, *column_names: str) -> ArrowExpr:
-        return ArrowExpr.from_column_names(
-            *column_names, backend_version=self._backend_version, version=self._version
-        ).max()
-
-    def min(self: Self, *column_names: str) -> ArrowExpr:
-        return ArrowExpr.from_column_names(
-            *column_names, backend_version=self._backend_version, version=self._version
-        ).min()
-
     @property
     def selectors(self: Self) -> ArrowSelectorNamespace:
         return ArrowSelectorNamespace(
diff --git a/narwhals/_arrow/series.py b/narwhals/_arrow/series.py
index 2f29ab9d..45124744 100644
--- a/narwhals/_arrow/series.py
+++ b/narwhals/_arrow/series.py
@@ -487,6 +487,12 @@ class ArrowSeries(CompliantSeries):
         import pyarrow.compute as pc
 
         ser = self._native_series
+        _, lower_bound = broadcast_and_extract_native(
+            self, lower_bound, self._backend_version
+        )
+        _, upper_bound = broadcast_and_extract_native(
+            self, upper_bound, self._backend_version
+        )
         if closed == "left":
             ge = pc.greater_equal(ser, lower_bound)
             lt = pc.less(ser, upper_bound)
diff --git a/narwhals/_dask/namespace.py b/narwhals/_dask/namespace.py
index 38cd16a8..126afaae 100644
--- a/narwhals/_dask/namespace.py
+++ b/narwhals/_dask/namespace.py
@@ -91,31 +91,6 @@ class DaskNamespace(CompliantNamespace["dask_expr.Series"]):
             kwargs={},
         )
 
-    def min(self, *column_names: str) -> DaskExpr:
-        return DaskExpr.from_column_names(
-            *column_names, backend_version=self._backend_version, version=self._version
-        ).min()
-
-    def max(self, *column_names: str) -> DaskExpr:
-        return DaskExpr.from_column_names(
-            *column_names, backend_version=self._backend_version, version=self._version
-        ).max()
-
-    def mean(self, *column_names: str) -> DaskExpr:
-        return DaskExpr.from_column_names(
-            *column_names, backend_version=self._backend_version, version=self._version
-        ).mean()
-
-    def median(self, *column_names: str) -> DaskExpr:
-        return DaskExpr.from_column_names(
-            *column_names, backend_version=self._backend_version, version=self._version
-        ).median()
-
-    def sum(self, *column_names: str) -> DaskExpr:
-        return DaskExpr.from_column_names(
-            *column_names, backend_version=self._backend_version, version=self._version
-        ).sum()
-
     def len(self) -> DaskExpr:
         import dask.dataframe as dd
         import pandas as pd
diff --git a/narwhals/_pandas_like/namespace.py b/narwhals/_pandas_like/namespace.py
index 0b060708..7885d7de 100644
--- a/narwhals/_pandas_like/namespace.py
+++ b/narwhals/_pandas_like/namespace.py
@@ -169,47 +169,6 @@ class PandasLikeNamespace(CompliantNamespace[PandasLikeSeries]):
             kwargs={},
         )
 
-    # --- reduction ---
-    def sum(self, *column_names: str) -> PandasLikeExpr:
-        return PandasLikeExpr.from_column_names(
-            *column_names,
-            implementation=self._implementation,
-            backend_version=self._backend_version,
-            version=self._version,
-        ).sum()
-
-    def mean(self, *column_names: str) -> PandasLikeExpr:
-        return PandasLikeExpr.from_column_names(
-            *column_names,
-            implementation=self._implementation,
-            backend_version=self._backend_version,
-            version=self._version,
-        ).mean()
-
-    def median(self, *column_names: str) -> PandasLikeExpr:
-        return PandasLikeExpr.from_column_names(
-            *column_names,
-            implementation=self._implementation,
-            backend_version=self._backend_version,
-            version=self._version,
-        ).median()
-
-    def max(self, *column_names: str) -> PandasLikeExpr:
-        return PandasLikeExpr.from_column_names(
-            *column_names,
-            implementation=self._implementation,
-            backend_version=self._backend_version,
-            version=self._version,
-        ).max()
-
-    def min(self, *column_names: str) -> PandasLikeExpr:
-        return PandasLikeExpr.from_column_names(
-            *column_names,
-            implementation=self._implementation,
-            backend_version=self._backend_version,
-            version=self._version,
-        ).min()
-
     def len(self) -> PandasLikeExpr:
         return PandasLikeExpr(
             lambda df: [
diff --git a/narwhals/_pandas_like/series.py b/narwhals/_pandas_like/series.py
index 1d895e14..c9f3d006 100644
--- a/narwhals/_pandas_like/series.py
+++ b/narwhals/_pandas_like/series.py
@@ -263,6 +263,8 @@ class PandasLikeSeries(CompliantSeries):
         self, lower_bound: Any, upper_bound: Any, closed: str = "both"
     ) -> PandasLikeSeries:
         ser = self._native_series
+        _, lower_bound = broadcast_align_and_extract_native(self, lower_bound)
+        _, upper_bound = broadcast_align_and_extract_native(self, upper_bound)
         if closed == "left":
             res = ser.ge(lower_bound) & ser.lt(upper_bound)
         elif closed == "right":
@@ -273,7 +275,14 @@ class PandasLikeSeries(CompliantSeries):
             res = ser.ge(lower_bound) & ser.le(upper_bound)
         else:  # pragma: no cover
             raise AssertionError
-        return self._from_native_series(res)
+        return self._from_native_series(
+            rename(
+                res,
+                ser.name,
+                implementation=self._implementation,
+                backend_version=self._backend_version,
+            )
+        )
 
     def is_in(self, other: Any) -> PandasLikeSeries:
         ser = self._native_series
diff --git a/narwhals/_polars/namespace.py b/narwhals/_polars/namespace.py
index 3e1ea176..00e005c3 100644
--- a/narwhals/_polars/namespace.py
+++ b/narwhals/_polars/namespace.py
@@ -128,17 +128,6 @@ class PolarsNamespace:
             pl.lit(value), version=self._version, backend_version=self._backend_version
         )
 
-    def mean(self: Self, *column_names: str) -> PolarsExpr:
-        import polars as pl
-
-        from narwhals._polars.expr import PolarsExpr
-
-        return PolarsExpr(
-            pl.mean([*column_names]),  # type: ignore[arg-type]
-            version=self._version,
-            backend_version=self._backend_version,
-        )
-
     def mean_horizontal(self: Self, *exprs: IntoPolarsExpr) -> PolarsExpr:
         import polars as pl
 
@@ -160,17 +149,6 @@ class PolarsNamespace:
             backend_version=self._backend_version,
         )
 
-    def median(self: Self, *column_names: str) -> PolarsExpr:
-        import polars as pl
-
-        from narwhals._polars.expr import PolarsExpr
-
-        return PolarsExpr(
-            pl.median([*column_names]),  # type: ignore[arg-type]
-            version=self._version,
-            backend_version=self._backend_version,
-        )
-
     def concat_str(
         self,
         exprs: Iterable[IntoPolarsExpr],
diff --git a/narwhals/expr.py b/narwhals/expr.py
index 777cae61..ba0896db 100644
--- a/narwhals/expr.py
+++ b/narwhals/expr.py
@@ -1666,7 +1666,10 @@ class Expr:
 
     # --- transform ---
     def is_between(
-        self, lower_bound: Any, upper_bound: Any, closed: str = "both"
+        self,
+        lower_bound: Any | IntoExpr,
+        upper_bound: Any | IntoExpr,
+        closed: str = "both",
     ) -> Self:
         """Check if this expression is between the given lower and upper bounds.
 
@@ -1724,7 +1727,9 @@ class Expr:
         """
         return self.__class__(
             lambda plx: self._to_compliant_expr(plx).is_between(
-                lower_bound, upper_bound, closed
+                extract_compliant(plx, lower_bound),
+                extract_compliant(plx, upper_bound),
+                closed,
             )
         )
 
@@ -6049,7 +6054,7 @@ def col(*names: str | Iterable[str]) -> Expr:
     """Creates an expression that references one or more columns by their name(s).
 
     Arguments:
-        names: Name(s) of the columns to use in the aggregation function.
+        names: Name(s) of the columns to use.
 
     Returns:
         A new expression.
@@ -6308,7 +6313,7 @@ def sum(*columns: str) -> Expr:
         ----
         a: [[3]]
     """
-    return Expr(lambda plx: plx.sum(*columns))
+    return Expr(lambda plx: plx.col(*columns).sum())
 
 
 def mean(*columns: str) -> Expr:
@@ -6359,7 +6364,7 @@ def mean(*columns: str) -> Expr:
         ----
         a: [[4]]
     """
-    return Expr(lambda plx: plx.mean(*columns))
+    return Expr(lambda plx: plx.col(*columns).mean())
 
 
 def median(*columns: str) -> Expr:
@@ -6411,7 +6416,7 @@ def median(*columns: str) -> Expr:
         ----
         a: [[4]]
     """
-    return Expr(lambda plx: plx.median(*columns))
+    return Expr(lambda plx: plx.col(*columns).median())
 
 
 def min(*columns: str) -> Expr:
@@ -6462,7 +6467,7 @@ def min(*columns: str) -> Expr:
         ----
         b: [[5]]
     """
-    return Expr(lambda plx: plx.min(*columns))
+    return Expr(lambda plx: plx.col(*columns).min())
 
 
 def max(*columns: str) -> Expr:
@@ -6513,7 +6518,7 @@ def max(*columns: str) -> Expr:
         ----
         a: [[2]]
     """
-    return Expr(lambda plx: plx.max(*columns))
+    return Expr(lambda plx: plx.col(*columns).max())
 
 
 def sum_horizontal(*exprs: IntoExpr | Iterable[IntoExpr]) -> Expr:
diff --git a/narwhals/series.py b/narwhals/series.py
index 228fb01c..9f572839 100644
--- a/narwhals/series.py
+++ b/narwhals/series.py
@@ -256,6 +256,9 @@ class Series(Generic[IntoSeriesT]):
             indices: Position(s) to set items at.
             values: Values to set.
 
+        Returns:
+            A new Series with values set at given positions.
+
         Note:
             This method always returns a new Series, without modifying the original one.
             Using this function in a for-loop is an anti-pattern, we recommend building
@@ -319,6 +322,9 @@ class Series(Generic[IntoSeriesT]):
     def shape(self) -> tuple[int]:
         """Get the shape of the Series.
 
+        Returns:
+            A tuple containing the length of the Series.
+
         Examples:
             >>> import pandas as pd
             >>> import polars as pl
@@ -359,6 +365,9 @@ class Series(Generic[IntoSeriesT]):
     def pipe(self, function: Callable[[Any], Self], *args: Any, **kwargs: Any) -> Self:
         """Pipe function call.
 
+        Returns:
+            A new Series with the results of the piped function applied.
+
         Examples:
             >>> import polars as pl
             >>> import pandas as pd
@@ -416,6 +425,9 @@ class Series(Generic[IntoSeriesT]):
 
         Null values count towards the total.
 
+        Returns:
+            The number of elements in the Series.
+
         Examples:
             >>> import narwhals as nw
             >>> from narwhals.typing import IntoSeries
@@ -444,6 +456,9 @@ class Series(Generic[IntoSeriesT]):
     def dtype(self: Self) -> DType:
         """Get the data type of the Series.
 
+        Returns:
+            The data type of the Series.
+
         Examples:
             >>> import pandas as pd
             >>> import polars as pl
@@ -472,6 +487,9 @@ class Series(Generic[IntoSeriesT]):
     def name(self) -> str:
         """Get the name of the Series.
 
+        Returns:
+            The name of the Series.
+
         Examples:
             >>> import pandas as pd
             >>> import polars as pl
@@ -639,7 +657,7 @@ class Series(Generic[IntoSeriesT]):
         """Convert to dataframe.
 
         Returns:
-            A new DataFrame.
+            A DataFrame containing this Series as a single column.
 
         Examples:
             >>> import pandas as pd
@@ -719,6 +737,9 @@ class Series(Generic[IntoSeriesT]):
     def mean(self) -> Any:
         """Reduce this Series to the mean value.
 
+        Returns:
+            The average of all elements in the Series.
+
         Examples:
             >>> import pandas as pd
             >>> import polars as pl
@@ -749,6 +770,9 @@ class Series(Generic[IntoSeriesT]):
         Notes:
             Results might slightly differ across backends due to differences in the underlying algorithms used to compute the median.
 
+        Returns:
+            The median value of all elements in the Series.
+
         Examples:
             >>> import pandas as pd
             >>> import polars as pl
@@ -910,6 +934,9 @@ class Series(Generic[IntoSeriesT]):
     def min(self) -> Any:
         """Get the minimal value in this Series.
 
+        Returns:
+            The minimum value in the Series.
+
         Examples:
             >>> import pandas as pd
             >>> import polars as pl
@@ -937,6 +964,9 @@ class Series(Generic[IntoSeriesT]):
     def max(self) -> Any:
         """Get the maximum value in this Series.
 
+        Returns:
+            The maximum value in the Series.
+
         Examples:
             >>> import pandas as pd
             >>> import polars as pl
@@ -1028,6 +1058,9 @@ class Series(Generic[IntoSeriesT]):
     def sum(self) -> Any:
         """Reduce this Series to the sum value.
 
+        Returns:
+            The sum of all elements in the Series.
+
         Examples:
             >>> import pandas as pd
             >>> import polars as pl
@@ -1059,6 +1092,9 @@ class Series(Generic[IntoSeriesT]):
             ddof: "Delta Degrees of Freedom": the divisor used in the calculation is N - ddof,
                      where N represents the number of elements.
 
+        Returns:
+            The standard deviation of all elements in the Series.
+
         Examples:
             >>> import pandas as pd
             >>> import polars as pl
@@ -1224,6 +1260,9 @@ class Series(Generic[IntoSeriesT]):
         Arguments:
             other: Sequence of primitive type.
 
+        Returns:
+            A new Series with boolean values indicating if the elements are in the other sequence.
+
         Examples:
             >>> import pandas as pd
             >>> import polars as pl
@@ -1303,6 +1342,9 @@ class Series(Generic[IntoSeriesT]):
             See [null_handling](../pandas_like_concepts/null_handling.md/)
             for reference.
 
+        Returns:
+            A new Series with null values removed.
+
         Examples:
             >>> import pandas as pd
             >>> import polars as pl
@@ -1395,7 +1437,7 @@ class Series(Generic[IntoSeriesT]):
             reverse: reverse the operation
 
         Returns:
-            A new Series with the cumulative sum of the values.
+            A new Series with the cumulative sum of non-null values.
 
         Examples:
             >>> import pandas as pd
@@ -1440,6 +1482,9 @@ class Series(Generic[IntoSeriesT]):
                 expensive to compute. Settings this to `True` blocks the possibility
                 to run on the streaming engine for Polars.
 
+        Returns:
+            A new Series with duplicate values removed.
+
         Examples:
             >>> import pandas as pd
             >>> import polars as pl
@@ -1487,6 +1532,9 @@ class Series(Generic[IntoSeriesT]):
 
                 s.diff().fill_null(0).cast(nw.Int64)
 
+        Returns:
+            A new Series with the difference between each element and its predecessor.
+
         Examples:
             >>> import pandas as pd
             >>> import polars as pl
@@ -1527,6 +1575,9 @@ class Series(Generic[IntoSeriesT]):
             n: Number of indices to shift forward. If a negative value is passed,
                 values are shifted in the opposite direction instead.
 
+        Returns:
+            A new Series with values shifted by n positions.
+
         Notes:
             pandas may change the dtype here, for example when introducing missing
             values in an integer column. To ensure, that the dtype doesn't change,
@@ -1586,6 +1637,9 @@ class Series(Generic[IntoSeriesT]):
             seed: Seed for the random number generator. If set to None (default), a random
                 seed is generated for each sample operation.
 
+        Returns:
+            A new Series containing randomly sampled values from the original Series.
+
         Notes:
             The `sample` method returns a Series with a specified number of
             randomly selected items chosen from this Series.
@@ -1729,6 +1783,9 @@ class Series(Generic[IntoSeriesT]):
         Arguments:
             name: The new name.
 
+        Returns:
+            A new Series with the updated name.
+
         Examples:
             >>> import pandas as pd
             >>> import polars as pl
@@ -1793,6 +1850,9 @@ class Series(Generic[IntoSeriesT]):
                 (default), the data type is determined automatically based on the other
                 inputs.
 
+        Returns:
+            A new Series with values replaced according to the mapping.
+
         Examples:
             >>> import narwhals as nw
             >>> from narwhals.typing import IntoSeriesT
@@ -1858,6 +1918,9 @@ class Series(Generic[IntoSeriesT]):
             descending: Sort in descending order.
             nulls_last: Place null values last instead of first.
 
+        Returns:
+            A new sorted Series.
+
         Examples:
             >>> import pandas as pd
             >>> import polars as pl
@@ -1922,6 +1985,9 @@ class Series(Generic[IntoSeriesT]):
             See [null_handling](../pandas_like_concepts/null_handling.md/)
             for reference.
 
+        Returns:
+            A boolean Series indicating which values are null.
+
         Examples:
             >>> import pandas as pd
             >>> import polars as pl
@@ -1984,6 +2050,9 @@ class Series(Generic[IntoSeriesT]):
             See [null_handling](../pandas_like_concepts/null_handling.md/)
             for reference.
 
+        Returns:
+            A new Series with null values filled according to the specified value or strategy.
+
         Examples:
             >>> import pandas as pd
             >>> import polars as pl
@@ -2069,21 +2138,22 @@ class Series(Generic[IntoSeriesT]):
         )
 
     def is_between(
-        self, lower_bound: Any, upper_bound: Any, closed: str = "both"
+        self, lower_bound: Any | Self, upper_bound: Any | Self, closed: str = "both"
     ) -> Self:
         """Get a boolean mask of the values that are between the given lower/upper bounds.
 
         Arguments:
             lower_bound: Lower bound value.
-
             upper_bound: Upper bound value.
-
             closed: Define which sides of the interval are closed (inclusive).
 
         Notes:
             If the value of the `lower_bound` is greater than that of the `upper_bound`,
             then the values will be False, as no value can satisfy the condition.
 
+        Returns:
+            A boolean Series indicating which values are between the given bounds.
+
         Examples:
             >>> import pandas as pd
             >>> import polars as pl
@@ -2119,12 +2189,19 @@ class Series(Generic[IntoSeriesT]):
             ]
         """
         return self._from_compliant_series(
-            self._compliant_series.is_between(lower_bound, upper_bound, closed=closed)
+            self._compliant_series.is_between(
+                self._extract_native(lower_bound),
+                self._extract_native(upper_bound),
+                closed=closed,
+            )
         )
 
     def n_unique(self) -> int:
         """Count the number of unique values.
 
+        Returns:
+            Number of unique values in the Series.
+
         Examples:
             >>> import pandas as pd
             >>> import polars as pl
@@ -2152,6 +2229,9 @@ class Series(Generic[IntoSeriesT]):
     def to_numpy(self) -> np.ndarray:
         """Convert to numpy.
 
+        Returns:
+            NumPy ndarray representation of the Series.
+
         Examples:
             >>> import pandas as pd
             >>> import polars as pl
@@ -2180,6 +2260,9 @@ class Series(Generic[IntoSeriesT]):
     def to_pandas(self) -> pd.Series:
         """Convert to pandas.
 
+        Returns:
+            A pandas Series containing the data from this Series.
+
         Examples:
             >>> import pandas as pd
             >>> import polars as pl
@@ -2337,6 +2420,9 @@ class Series(Generic[IntoSeriesT]):
     def filter(self, other: Any) -> Self:
         """Filter elements in the Series based on a condition.
 
+        Returns:
+            A new Series with elements that satisfy the condition.
+
         Examples:
             >>> import pandas as pd
             >>> import polars as pl
@@ -2377,7 +2463,7 @@ class Series(Generic[IntoSeriesT]):
         r"""Get a mask of all duplicated rows in the Series.
 
         Returns:
-            A new Series.
+            A new Series with boolean values indicating duplicated rows.
 
         Examples:
             >>> import narwhals as nw
@@ -2416,6 +2502,9 @@ class Series(Generic[IntoSeriesT]):
     def is_empty(self: Self) -> bool:
         r"""Check if the series is empty.
 
+        Returns:
+            A boolean indicating if the series is empty.
+
         Examples:
             >>> import narwhals as nw
             >>> from narwhals.typing import IntoSeries
@@ -2446,6 +2535,9 @@ class Series(Generic[IntoSeriesT]):
     def is_unique(self: Self) -> Self:
         r"""Get a mask of all unique rows in the Series.
 
+        Returns:
+            A new Series with boolean values indicating unique rows.
+
         Examples:
             >>> import narwhals as nw
             >>> from narwhals.typing import IntoSeriesT
@@ -2489,6 +2581,9 @@ class Series(Generic[IntoSeriesT]):
             See [null_handling](../pandas_like_concepts/null_handling.md/)
             for reference.
 
+        Returns:
+            The number of null values in the Series.
+
         Examples:
             >>> import narwhals as nw
             >>> from narwhals.typing import IntoSeries
@@ -2521,6 +2616,9 @@ class Series(Generic[IntoSeriesT]):
     def is_first_distinct(self: Self) -> Self:
         r"""Return a boolean mask indicating the first occurrence of each distinct value.
 
+        Returns:
+            A new Series with boolean values indicating the first occurrence of each distinct value.
+
         Examples:
             >>> import narwhals as nw
             >>> from narwhals.typing import IntoSeriesT
@@ -2561,6 +2659,9 @@ class Series(Generic[IntoSeriesT]):
     def is_last_distinct(self: Self) -> Self:
         r"""Return a boolean mask indicating the last occurrence of each distinct value.
 
+        Returns:
+            A new Series with boolean values indicating the last occurrence of each distinct value.
+
         Examples:
             >>> import narwhals as nw
             >>> from narwhals.typing import IntoSeriesT
@@ -2604,6 +2705,9 @@ class Series(Generic[IntoSeriesT]):
         Arguments:
             descending: Check if the Series is sorted in descending order.
 
+        Returns:
+            A boolean indicating if the Series is sorted.
+
         Examples:
             >>> import narwhals as nw
             >>> from narwhals.typing import IntoSeries
@@ -2652,7 +2756,9 @@ class Series(Generic[IntoSeriesT]):
             normalize: If true gives relative frequencies of the unique values
 
         Returns:
-            A new DataFrame.
+            A DataFrame with two columns:
+            - The original values as first column
+            - Either count or proportion as second column, depending on normalize parameter.
 
         Examples:
             >>> import narwhals as nw
@@ -2709,6 +2815,9 @@ class Series(Generic[IntoSeriesT]):
             quantile: Quantile between 0.0 and 1.0.
             interpolation: Interpolation method.
 
+        Returns:
+            The quantile value.
+
         Examples:
             >>> import narwhals as nw
             >>> from narwhals.typing import IntoSeries
@@ -2749,6 +2858,9 @@ class Series(Generic[IntoSeriesT]):
             mask: Boolean Series
             other: Series of same type.
 
+        Returns:
+            A new Series with values selected from self or other based on the mask.
+
         Examples:
             >>> import narwhals as nw
             >>> from narwhals.typing import IntoSeriesT
@@ -2805,6 +2917,9 @@ class Series(Generic[IntoSeriesT]):
         If no index is provided, this is equivalent to `s[0]`, with a check
         that the shape is (1,). With an index, this is equivalent to `s[index]`.
 
+        Returns:
+            The scalar value of the Series or the element at the given index.
+
         Examples:
             >>> import narwhals as nw
             >>> from narwhals.typing import IntoSeries
@@ -2879,6 +2994,9 @@ class Series(Generic[IntoSeriesT]):
         Arguments:
             n: Number of rows to return.
 
+        Returns:
+            A new Series with the last n rows.
+
         Examples:
             >>> import narwhals as nw
             >>> from narwhals.typing import IntoSeriesT
@@ -2918,6 +3036,9 @@ class Series(Generic[IntoSeriesT]):
         Arguments:
             decimals: Number of decimals to round by.
 
+        Returns:
+            A new Series with rounded values.
+
         Notes:
             For values exactly halfway between rounded decimal values pandas behaves differently than Polars and Arrow.
 
@@ -2969,6 +3090,9 @@ class Series(Generic[IntoSeriesT]):
             separator: Separator/delimiter used when generating column names.
             drop_first: Remove the first category from the variable being encoded.
 
+        Returns:
+            A new DataFrame containing the dummy/indicator variables.
+
         Notes:
             pandas and Polars handle null values differently. Polars distinguishes
             between NaN and Null, whereas pandas doesn't.
@@ -3039,6 +3163,9 @@ class Series(Generic[IntoSeriesT]):
             n: Gather every *n*-th row.
             offset: Starting index.
 
+        Returns:
+            A new Series with every nth value starting from the offset.
+
         Examples:
             >>> import narwhals as nw
             >>> from narwhals.typing import IntoSeriesT
@@ -3075,6 +3202,9 @@ class Series(Generic[IntoSeriesT]):
     def to_arrow(self: Self) -> pa.Array:
         r"""Convert to arrow.
 
+        Returns:
+            A PyArrow Array containing the data from the Series.
+
         Examples:
             >>> import narwhals as nw
             >>> from narwhals.typing import IntoSeries
@@ -3116,6 +3246,9 @@ class Series(Generic[IntoSeriesT]):
 
         Can return multiple values.
 
+        Returns:
+            A new Series containing the mode(s) (values that appear most frequently).
+
         Examples:
             >>> import pandas as pd
             >>> import polars as pl
@@ -4280,6 +4413,9 @@ class SeriesStringNamespace(Generic[SeriesT]):
         Arguments:
             n: Number of elements to take. Negative indexing is supported (see note (1.))
 
+        Returns:
+            A new Series containing the first n characters of each string.
+
         Notes:
             1. When the `n` input is negative, `head` returns characters up to the n-th from the end of the string.
                 For example, if `n = -3`, then all characters except the last three are returned.
diff --git a/narwhals/stable/v1/__init__.py b/narwhals/stable/v1/__init__.py
index 4bd0cc1b..2b5be2ee 100644
--- a/narwhals/stable/v1/__init__.py
+++ b/narwhals/stable/v1/__init__.py
@@ -478,7 +478,7 @@ class Series(NwSeries[Any]):
         """Convert to dataframe.
 
         Returns:
-            A new DataFrame.
+            A DataFrame containing this Series as a single column.
 
         Examples:
             >>> import pandas as pd
@@ -535,7 +535,9 @@ class Series(NwSeries[Any]):
             normalize: If true gives relative frequencies of the unique values
 
         Returns:
-            A new DataFrame.
+            A DataFrame with two columns:
+            - The original values as first column
+            - Either count or proportion as second column, depending on normalize parameter.
 
         Examples:
             >>> import narwhals as nw
@@ -2319,7 +2321,7 @@ def col(*names: str | Iterable[str]) -> Expr:
     """Creates an expression that references one or more columns by their name(s).
 
     Arguments:
-        names: Name(s) of the columns to use in the aggregation function.
+        names: Name(s) of the columns to use.
 
     Returns:
         A new expression.


Test patch:
diff --git a/tests/expr_and_series/is_between_test.py b/tests/expr_and_series/is_between_test.py
index 8d08c6fa..57ad545c 100644
--- a/tests/expr_and_series/is_between_test.py
+++ b/tests/expr_and_series/is_between_test.py
@@ -7,10 +7,6 @@ from tests.utils import Constructor
 from tests.utils import ConstructorEager
 from tests.utils import assert_equal_data
 
-data = {
-    "a": [1, 4, 2, 5],
-}
-
 
 @pytest.mark.parametrize(
     ("closed", "expected"),
@@ -22,12 +18,21 @@ data = {
     ],
 )
 def test_is_between(constructor: Constructor, closed: str, expected: list[bool]) -> None:
+    data = {"a": [1, 4, 2, 5]}
     df = nw.from_native(constructor(data))
     result = df.select(nw.col("a").is_between(1, 5, closed=closed))
     expected_dict = {"a": expected}
     assert_equal_data(result, expected_dict)
 
 
+def test_is_between_expressified(constructor: Constructor) -> None:
+    data = {"a": [1, 4, 2, 5], "b": [0, 5, 2, 4], "c": [9, 9, 9, 9]}
+    df = nw.from_native(constructor(data))
+    result = df.select(nw.col("a").is_between(nw.col("b") * 0.9, nw.col("c") - 1))
+    expected_dict = {"a": [True, False, True, True]}
+    assert_equal_data(result, expected_dict)
+
+
 @pytest.mark.parametrize(
     ("closed", "expected"),
     [
@@ -40,7 +45,16 @@ def test_is_between(constructor: Constructor, closed: str, expected: list[bool])
 def test_is_between_series(
     constructor_eager: ConstructorEager, closed: str, expected: list[bool]
 ) -> None:
+    data = {"a": [1, 4, 2, 5]}
     df = nw.from_native(constructor_eager(data), eager_only=True)
     result = df.with_columns(a=df["a"].is_between(1, 5, closed=closed))
     expected_dict = {"a": expected}
     assert_equal_data(result, expected_dict)
+
+
+def test_is_between_expressified_series(constructor_eager: ConstructorEager) -> None:
+    data = {"a": [1, 4, 2, 5], "b": [0, 5, 2, 4], "c": [9, 9, 9, 9]}
+    df = nw.from_native(constructor_eager(data), eager_only=True)
+    result = df["a"].is_between(df["b"], df["c"]).to_frame()
+    expected_dict = {"a": [True, False, True, True]}
+    assert_equal_data(result, expected_dict)