Repository: narwhals-dev/narwhals
Pull request #1121 (2024-10-03 14:05:07)

docs : docstrings examples - dtypes
Just a quick check: considering I'll have to use cast to get some of the datatypes, maybe I should wrap the from_native into a func after all?
## What type of PR is this? (check all applicable)

- [ ] 💾 Refactor
- [ ] ✨ Feature
- [ ] 🐛 Bug Fix
- [ ] 🔧 Optimization
- [x] 📝 Documentation
- [ ] ✅ Test
- [ ] 🐳 Other

## Related issues 

- Related issue #1077
- Closes #

## Checklist

- [x] Code follows style guide (ruff)
- [ ] Tests added 
- [ ] Documented the changes

## If you have comments or can explain your changes, please do so below.




Patch:
diff --git a/docs/api-reference/expr.md b/docs/api-reference/expr.md
index e99ffd4f..72ea830f 100644
--- a/docs/api-reference/expr.md
+++ b/docs/api-reference/expr.md
@@ -11,6 +11,7 @@
         - arg_true
         - cast
         - count
+        - cum_count
         - cum_sum
         - diff
         - drop_nulls
diff --git a/docs/api-reference/series.md b/docs/api-reference/series.md
index e1b11c38..76132051 100644
--- a/docs/api-reference/series.md
+++ b/docs/api-reference/series.md
@@ -15,6 +15,7 @@
         - cast
         - clip
         - count
+        - cum_count
         - cum_sum
         - diff
         - drop_nulls
diff --git a/docs/installation.md b/docs/installation.md
index 6da80b12..e9b13d48 100644
--- a/docs/installation.md
+++ b/docs/installation.md
@@ -29,7 +29,7 @@ To verify the installation, start the Python REPL and execute:
 ```python
 >>> import narwhals
 >>> narwhals.__version__
-'1.13.4'
+'1.13.5'
 ```
 If you see the version number, then the installation was successful!
 
diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt
index beec6070..eccadeb2 100644
--- a/docs/requirements-docs.txt
+++ b/docs/requirements-docs.txt
@@ -1,3 +1,4 @@
+black  # required by mkdocstrings_handlers
 jinja2
 duckdb
 markdown-exec[ansi]
diff --git a/narwhals/__init__.py b/narwhals/__init__.py
index a16e1a9e..e69db9da 100644
--- a/narwhals/__init__.py
+++ b/narwhals/__init__.py
@@ -68,7 +68,7 @@ from narwhals.utils import maybe_get_index
 from narwhals.utils import maybe_reset_index
 from narwhals.utils import maybe_set_index
 
-__version__ = "1.13.4"
+__version__ = "1.13.5"
 
 __all__ = [
     "dependencies",
diff --git a/narwhals/_arrow/dataframe.py b/narwhals/_arrow/dataframe.py
index 999d4905..e6c5bfd8 100644
--- a/narwhals/_arrow/dataframe.py
+++ b/narwhals/_arrow/dataframe.py
@@ -614,11 +614,8 @@ class ArrowDataFrame:
         keep: Literal["any", "first", "last", "none"] = "any",
         maintain_order: bool = False,
     ) -> Self:
-        """
-        NOTE:
-            The param `maintain_order` is only here for compatibility with the Polars API
-            and has no effect on the output.
-        """
+        # The param `maintain_order` is only here for compatibility with the Polars API
+        # and has no effect on the output.
         import numpy as np  # ignore-banned-import
         import pyarrow as pa  # ignore-banned-import()
         import pyarrow.compute as pc  # ignore-banned-import()
diff --git a/narwhals/_arrow/expr.py b/narwhals/_arrow/expr.py
index 91c8bfcc..71646001 100644
--- a/narwhals/_arrow/expr.py
+++ b/narwhals/_arrow/expr.py
@@ -430,6 +430,9 @@ class ArrowExpr:
             dtypes=self._dtypes,
         )
 
+    def cum_count(self: Self, *, reverse: bool) -> Self:
+        return reuse_series_implementation(self, "cum_count", reverse=reverse)
+
     @property
     def dt(self: Self) -> ArrowExprDateTimeNamespace:
         return ArrowExprDateTimeNamespace(self)
diff --git a/narwhals/_arrow/series.py b/narwhals/_arrow/series.py
index d0c2bf98..be8ec994 100644
--- a/narwhals/_arrow/series.py
+++ b/narwhals/_arrow/series.py
@@ -394,7 +394,9 @@ class ArrowSeries:
     def cum_sum(self) -> Self:
         import pyarrow.compute as pc  # ignore-banned-import()
 
-        return self._from_native_series(pc.cumulative_sum(self._native_series))
+        return self._from_native_series(
+            pc.cumulative_sum(self._native_series, skip_nulls=True)
+        )
 
     def round(self, decimals: int) -> Self:
         import pyarrow.compute as pc  # ignore-banned-import()
@@ -518,7 +520,7 @@ class ArrowSeries:
         name: str | None = None,
         normalize: bool = False,
     ) -> ArrowDataFrame:
-        """Parallel is unused, exists for compatibility"""
+        """Parallel is unused, exists for compatibility."""
         import pyarrow as pa  # ignore-banned-import()
         import pyarrow.compute as pc  # ignore-banned-import()
 
@@ -700,11 +702,8 @@ class ArrowSeries:
             return pc.all(pc.less_equal(ser[:-1], ser[1:]))  # type: ignore[no-any-return]
 
     def unique(self: Self, *, maintain_order: bool = False) -> ArrowSeries:
-        """
-        NOTE:
-            The param `maintain_order` is only here for compatibility with the Polars API
-            and has no effect on the output.
-        """
+        # The param `maintain_order` is only here for compatibility with the Polars API
+        # and has no effect on the output.
         import pyarrow.compute as pc  # ignore-banned-import()
 
         return self._from_native_series(pc.unique(self._native_series))
@@ -815,6 +814,14 @@ class ArrowSeries:
             plx.col(col_token) == plx.col(col_token).max()
         )[self.name]
 
+    def cum_count(self: Self, *, reverse: bool) -> Self:
+        not_na_series = (~self.is_null()).cast(self._dtypes.UInt32())
+        return (
+            not_na_series.cum_sum()
+            if not reverse
+            else len(self) - not_na_series.cum_sum() + not_na_series - 1
+        )
+
     def __iter__(self: Self) -> Iterator[Any]:
         yield from self._native_series.__iter__()
 
diff --git a/narwhals/_arrow/utils.py b/narwhals/_arrow/utils.py
index 9d8a8272..d5f5fc31 100644
--- a/narwhals/_arrow/utils.py
+++ b/narwhals/_arrow/utils.py
@@ -197,8 +197,7 @@ def validate_dataframe_comparand(
 
 
 def horizontal_concat(dfs: list[Any]) -> Any:
-    """
-    Concatenate (native) DataFrames horizontally.
+    """Concatenate (native) DataFrames horizontally.
 
     Should be in namespace.
     """
@@ -219,8 +218,7 @@ def horizontal_concat(dfs: list[Any]) -> Any:
 
 
 def vertical_concat(dfs: list[Any]) -> Any:
-    """
-    Concatenate (native) DataFrames vertically.
+    """Concatenate (native) DataFrames vertically.
 
     Should be in namespace.
     """
diff --git a/narwhals/_dask/dataframe.py b/narwhals/_dask/dataframe.py
index d7121a04..82f0aa52 100644
--- a/narwhals/_dask/dataframe.py
+++ b/narwhals/_dask/dataframe.py
@@ -198,11 +198,8 @@ class DaskLazyFrame:
         keep: Literal["any", "first", "last", "none"] = "any",
         maintain_order: bool = False,
     ) -> Self:
-        """
-        NOTE:
-            The param `maintain_order` is only here for compatibility with the Polars API
-            and has no effect on the output.
-        """
+        # The param `maintain_order` is only here for compatibility with the Polars API
+        # and has no effect on the output.
         subset = flatten(subset) if subset else None
         native_frame = self._native_frame
         if keep == "none":
diff --git a/narwhals/_dask/expr.py b/narwhals/_dask/expr.py
index 79766d2a..8de079df 100644
--- a/narwhals/_dask/expr.py
+++ b/narwhals/_dask/expr.py
@@ -749,6 +749,10 @@ class DaskExpr:
         msg = "`Expr.mode` is not supported for the Dask backend."
         raise NotImplementedError(msg)
 
+    def cum_count(self: Self, *, reverse: bool) -> Self:
+        msg = "`Expr.cum_count` is not supported for the Dask backend."
+        raise NotImplementedError(msg)
+
     @property
     def str(self: Self) -> DaskExprStringNamespace:
         return DaskExprStringNamespace(self)
diff --git a/narwhals/_dask/group_by.py b/narwhals/_dask/group_by.py
index e4c1e14c..fd765b22 100644
--- a/narwhals/_dask/group_by.py
+++ b/narwhals/_dask/group_by.py
@@ -97,8 +97,7 @@ def agg_dask(
     keys: list[str],
     from_dataframe: Callable[[Any], DaskLazyFrame],
 ) -> DaskLazyFrame:
-    """
-    This should be the fastpath, but cuDF is too far behind to use it.
+    """This should be the fastpath, but cuDF is too far behind to use it.
 
     - https://github.com/rapidsai/cudf/issues/15118
     - https://github.com/rapidsai/cudf/issues/15084
diff --git a/narwhals/_expression_parsing.py b/narwhals/_expression_parsing.py
index abd34d69..27c9526d 100644
--- a/narwhals/_expression_parsing.py
+++ b/narwhals/_expression_parsing.py
@@ -155,8 +155,10 @@ def parse_into_exprs(
     namespace: CompliantNamespace,
     **named_exprs: IntoCompliantExpr,
 ) -> ListOfCompliantExpr:
-    """Parse each input as an expression (if it's not already one). See `parse_into_expr` for
-    more details."""
+    """Parse each input as an expression (if it's not already one).
+
+    See `parse_into_expr` for more details.
+    """
     return [parse_into_expr(into_expr, namespace=namespace) for into_expr in exprs] + [
         parse_into_expr(expr, namespace=namespace).alias(name)
         for name, expr in named_exprs.items()
@@ -209,12 +211,13 @@ def reuse_series_implementation(
     If Series.foo is already defined, and we'd like Expr.foo to be the same, we can
     leverage this method to do that for us.
 
-    Arguments
+    Arguments:
         expr: expression object.
         attr: name of method.
         returns_scalar: whether the Series version returns a scalar. In this case,
             the expression version should return a 1-row Series.
-        args, kwargs: arguments and keyword arguments to pass to function.
+        args: arguments to pass to function.
+        kwargs: keyword arguments to pass to function.
     """
     plx = expr.__narwhals_namespace__()
 
@@ -278,9 +281,8 @@ def reuse_series_implementation(
 def reuse_series_namespace_implementation(
     expr: CompliantExprT, series_namespace: str, attr: str, *args: Any, **kwargs: Any
 ) -> CompliantExprT:
-    """Just like `reuse_series_implementation`, but for e.g. `Expr.dt.foo` instead
-    of `Expr.foo`.
-    """
+    # Just like `reuse_series_implementation`, but for e.g. `Expr.dt.foo` instead
+    # of `Expr.foo`.
     plx = expr.__narwhals_namespace__()
     return plx._create_expr_from_callable(  # type: ignore[return-value]
         lambda df: [
@@ -295,16 +297,16 @@ def reuse_series_namespace_implementation(
 
 
 def is_simple_aggregation(expr: CompliantExpr) -> bool:
-    """
-    Check if expr is a very simple one, such as:
+    """Check if expr is a very simple one.
 
-    - nw.col('a').mean()  # depth 1
-    - nw.mean('a')  # depth 1
-    - nw.len()  # depth 0
+    Examples:
+        - nw.col('a').mean()  # depth 1
+        - nw.mean('a')  # depth 1
+        - nw.len()  # depth 0
 
     as opposed to, say
 
-    - nw.col('a').filter(nw.col('b')>nw.col('c')).max()
+        - nw.col('a').filter(nw.col('b')>nw.col('c')).max()
 
     because then, we can use a fastpath in pandas.
     """
@@ -324,7 +326,7 @@ def combine_root_names(parsed_exprs: Sequence[CompliantExpr]) -> list[str] | Non
 
 
 def reduce_output_names(parsed_exprs: Sequence[CompliantExpr]) -> list[str] | None:
-    """Returns the left-most output name"""
+    """Returns the left-most output name."""
     return (
         parsed_exprs[0]._output_names[:1]
         if parsed_exprs[0]._output_names is not None
diff --git a/narwhals/_pandas_like/dataframe.py b/narwhals/_pandas_like/dataframe.py
index e55e1524..de3eaacf 100644
--- a/narwhals/_pandas_like/dataframe.py
+++ b/narwhals/_pandas_like/dataframe.py
@@ -301,11 +301,8 @@ class PandasLikeDataFrame:
         named: bool = False,
         buffer_size: int = 512,
     ) -> Iterator[list[tuple[Any, ...]]] | Iterator[list[dict[str, Any]]]:
-        """
-        NOTE:
-            The param ``buffer_size`` is only here for compatibility with the Polars API
-            and has no effect on the output.
-        """
+        # The param ``buffer_size`` is only here for compatibility with the Polars API
+        # and has no effect on the output.
         if not named:
             yield from self._native_frame.itertuples(index=False, name=None)
         else:
@@ -667,11 +664,8 @@ class PandasLikeDataFrame:
         keep: Literal["any", "first", "last", "none"] = "any",
         maintain_order: bool = False,
     ) -> Self:
-        """
-        NOTE:
-            The param `maintain_order` is only here for compatibility with the Polars API
-            and has no effect on the output.
-        """
+        # The param `maintain_order` is only here for compatibility with the Polars API
+        # and has no effect on the output.
         mapped_keep = {"none": False, "any": "first"}.get(keep, keep)
         subset = flatten(subset) if subset else None
         return self._from_native_frame(
diff --git a/narwhals/_pandas_like/expr.py b/narwhals/_pandas_like/expr.py
index 408fc0ba..7b2d5b70 100644
--- a/narwhals/_pandas_like/expr.py
+++ b/narwhals/_pandas_like/expr.py
@@ -441,6 +441,9 @@ class PandasLikeExpr:
             dtypes=self._dtypes,
         )
 
+    def cum_count(self: Self, *, reverse: bool) -> Self:
+        return reuse_series_implementation(self, "cum_count", reverse=reverse)
+
     @property
     def str(self: Self) -> PandasLikeExprStringNamespace:
         return PandasLikeExprStringNamespace(self)
diff --git a/narwhals/_pandas_like/group_by.py b/narwhals/_pandas_like/group_by.py
index b21392d8..56802e2b 100644
--- a/narwhals/_pandas_like/group_by.py
+++ b/narwhals/_pandas_like/group_by.py
@@ -136,8 +136,7 @@ def agg_pandas(  # noqa: PLR0915
     dataframe_is_empty: bool,
     native_namespace: Any,
 ) -> PandasLikeDataFrame:
-    """
-    This should be the fastpath, but cuDF is too far behind to use it.
+    """This should be the fastpath, but cuDF is too far behind to use it.
 
     - https://github.com/rapidsai/cudf/issues/15118
     - https://github.com/rapidsai/cudf/issues/15084
diff --git a/narwhals/_pandas_like/series.py b/narwhals/_pandas_like/series.py
index 1bee0079..d235be0e 100644
--- a/narwhals/_pandas_like/series.py
+++ b/narwhals/_pandas_like/series.py
@@ -498,11 +498,8 @@ class PandasLikeSeries:
         return self._from_native_series(self._native_series.cumsum())
 
     def unique(self, *, maintain_order: bool = False) -> PandasLikeSeries:
-        """
-        NOTE:
-            The param `maintain_order` is only here for compatibility with the Polars API
-            and has no effect on the output.
-        """
+        # The param `maintain_order` is only here for compatibility with the Polars API
+        # and has no effect on the output.
         return self._from_native_series(
             self._native_series.__class__(
                 self._native_series.unique(), name=self._native_series.name
@@ -653,7 +650,7 @@ class PandasLikeSeries:
         name: str | None = None,
         normalize: bool = False,
     ) -> PandasLikeDataFrame:
-        """Parallel is unused, exists for compatibility"""
+        """Parallel is unused, exists for compatibility."""
         from narwhals._pandas_like.dataframe import PandasLikeDataFrame
 
         index_name_ = "index" if self._name is None else self._name
@@ -759,6 +756,15 @@ class PandasLikeSeries:
         result.name = native_series.name
         return self._from_native_series(result)
 
+    def cum_count(self: Self, *, reverse: bool) -> Self:
+        not_na_series = ~self._native_series.isna()
+        result = (
+            not_na_series.cumsum()
+            if not reverse
+            else len(self) - not_na_series.cumsum() + not_na_series - 1
+        )
+        return self._from_native_series(result)
+
     def __iter__(self: Self) -> Iterator[Any]:
         yield from self._native_series.__iter__()
 
diff --git a/narwhals/_pandas_like/utils.py b/narwhals/_pandas_like/utils.py
index d68f41be..e9da85ed 100644
--- a/narwhals/_pandas_like/utils.py
+++ b/narwhals/_pandas_like/utils.py
@@ -171,8 +171,7 @@ def create_compliant_series(
 def horizontal_concat(
     dfs: list[Any], *, implementation: Implementation, backend_version: tuple[int, ...]
 ) -> Any:
-    """
-    Concatenate (native) DataFrames horizontally.
+    """Concatenate (native) DataFrames horizontally.
 
     Should be in namespace.
     """
@@ -192,8 +191,7 @@ def horizontal_concat(
 def vertical_concat(
     dfs: list[Any], *, implementation: Implementation, backend_version: tuple[int, ...]
 ) -> Any:
-    """
-    Concatenate (native) DataFrames vertically.
+    """Concatenate (native) DataFrames vertically.
 
     Should be in namespace.
     """
@@ -644,8 +642,11 @@ def select_columns_by_name(
     backend_version: tuple[int, ...],
     implementation: Implementation,
 ) -> T:
-    """Select columns by name. Prefer this over `df.loc[:, column_names]` as it's
-    generally more performant."""
+    """Select columns by name.
+
+    Prefer this over `df.loc[:, column_names]` as it's
+    generally more performant.
+    """
     if (df.columns.dtype.kind == "b") or (  # type: ignore[attr-defined]
         implementation is Implementation.PANDAS and backend_version < (1, 5)
     ):
diff --git a/narwhals/_polars/expr.py b/narwhals/_polars/expr.py
index 18cbbac5..7f2bb3c7 100644
--- a/narwhals/_polars/expr.py
+++ b/narwhals/_polars/expr.py
@@ -127,6 +127,15 @@ class PolarsExpr:
     def __invert__(self) -> Self:
         return self._from_native_expr(self._native_expr.__invert__())
 
+    def cum_count(self: Self, *, reverse: bool) -> Self:
+        if self._backend_version < (0, 20, 4):
+            not_null = ~self._native_expr.is_null()
+            result = not_null.cum_sum(reverse=reverse)
+        else:
+            result = self._native_expr.cum_count(reverse=reverse)
+
+        return self._from_native_expr(result)
+
     @property
     def dt(self) -> PolarsExprDateTimeNamespace:
         return PolarsExprDateTimeNamespace(self)
diff --git a/narwhals/_polars/series.py b/narwhals/_polars/series.py
index 4feff11c..5f6a940c 100644
--- a/narwhals/_polars/series.py
+++ b/narwhals/_polars/series.py
@@ -281,6 +281,15 @@ class PolarsSeries:
             result, backend_version=self._backend_version, dtypes=self._dtypes
         )
 
+    def cum_count(self: Self, *, reverse: bool) -> Self:
+        if self._backend_version < (0, 20, 4):
+            not_null_series = ~self._native_series.is_null()
+            result = not_null_series.cum_sum(reverse=reverse)
+        else:
+            result = self._native_series.cum_count(reverse=reverse)
+
+        return self._from_native_series(result)
+
     @property
     def dt(self) -> PolarsSeriesDateTimeNamespace:
         return PolarsSeriesDateTimeNamespace(self)
diff --git a/narwhals/dataframe.py b/narwhals/dataframe.py
index 34482072..0aa5820a 100644
--- a/narwhals/dataframe.py
+++ b/narwhals/dataframe.py
@@ -309,8 +309,7 @@ class BaseFrame(Generic[FrameT]):
 
 
 class DataFrame(BaseFrame[DataFrameT]):
-    """
-    Narwhals DataFrame, backed by a native dataframe.
+    """Narwhals DataFrame, backed by a native dataframe.
 
     The native dataframe might be pandas.DataFrame, polars.DataFrame, ...
 
@@ -362,8 +361,7 @@ class DataFrame(BaseFrame[DataFrameT]):
         )
 
     def __arrow_c_stream__(self, requested_schema: object | None = None) -> object:
-        """
-        Export a DataFrame via the Arrow PyCapsule Interface.
+        """Export a DataFrame via the Arrow PyCapsule Interface.
 
         - if the underlying dataframe implements the interface, it'll return that
         - else, it'll call `to_arrow` and then defer to PyArrow's implementation
@@ -386,8 +384,7 @@ class DataFrame(BaseFrame[DataFrameT]):
         return pa_table.__arrow_c_stream__(requested_schema=requested_schema)
 
     def lazy(self) -> LazyFrame[Any]:
-        """
-        Lazify the DataFrame (if possible).
+        """Lazify the DataFrame (if possible).
 
         If a library does not support lazy execution, then this is a no-op.
 
@@ -431,8 +428,7 @@ class DataFrame(BaseFrame[DataFrameT]):
         return self._lazyframe(self._compliant_frame.lazy(), level=self._level)
 
     def to_native(self) -> DataFrameT:
-        """
-        Convert Narwhals DataFrame to native one.
+        """Convert Narwhals DataFrame to native one.
 
         Returns:
             Object of class that user started with.
@@ -475,12 +471,10 @@ class DataFrame(BaseFrame[DataFrameT]):
             bar: [[6,7,8]]
             ham: [["a","b","c"]]
         """
-
         return self._compliant_frame._native_frame  # type: ignore[no-any-return]
 
     def to_pandas(self) -> pd.DataFrame:
-        """
-        Convert this DataFrame to a pandas DataFrame.
+        """Convert this DataFrame to a pandas DataFrame.
 
         Examples:
             Construct pandas, Polars (eager) and PyArrow DataFrames:
@@ -523,8 +517,7 @@ class DataFrame(BaseFrame[DataFrameT]):
         return self._compliant_frame.to_pandas()
 
     def write_csv(self, file: str | Path | BytesIO | None = None) -> Any:
-        r"""
-        Write dataframe to comma-separated values (CSV) file.
+        r"""Write dataframe to comma-separated values (CSV) file.
 
         Examples:
             Construct pandas and Polars DataFrames:
@@ -559,8 +552,7 @@ class DataFrame(BaseFrame[DataFrameT]):
         return self._compliant_frame.write_csv(file)
 
     def write_parquet(self, file: str | Path | BytesIO) -> Any:
-        """
-        Write dataframe to parquet file.
+        """Write dataframe to parquet file.
 
         Examples:
             Construct pandas, Polars and PyArrow DataFrames:
@@ -589,8 +581,7 @@ class DataFrame(BaseFrame[DataFrameT]):
         self._compliant_frame.write_parquet(file)
 
     def to_numpy(self) -> np.ndarray:
-        """
-        Convert this DataFrame to a NumPy ndarray.
+        """Convert this DataFrame to a NumPy ndarray.
 
         Examples:
             Construct pandas and polars DataFrames:
@@ -629,8 +620,7 @@ class DataFrame(BaseFrame[DataFrameT]):
 
     @property
     def shape(self) -> tuple[int, int]:
-        """
-        Get the shape of the DataFrame.
+        """Get the shape of the DataFrame.
 
         Examples:
             Construct pandas and polars DataFrames:
@@ -662,8 +652,7 @@ class DataFrame(BaseFrame[DataFrameT]):
         return self._compliant_frame.shape  # type: ignore[no-any-return]
 
     def get_column(self, name: str) -> Series:
-        """
-        Get a single column by name.
+        """Get a single column by name.
 
         Notes:
             Although `name` is typed as `str`, pandas does allow non-string column
@@ -753,8 +742,7 @@ class DataFrame(BaseFrame[DataFrameT]):
             | tuple[slice, slice]
         ),
     ) -> Series | Self:
-        """
-        Extract column or slice of DataFrame.
+        """Extract column or slice of DataFrame.
 
         Arguments:
             item: How to slice dataframe. What happens depends on what is passed. It's easiest
@@ -879,8 +867,7 @@ class DataFrame(BaseFrame[DataFrameT]):
     def to_dict(
         self, *, as_series: bool = True
     ) -> dict[str, Series] | dict[str, list[Any]]:
-        """
-        Convert DataFrame to a dictionary mapping column name to values.
+        """Convert DataFrame to a dictionary mapping column name to values.
 
         Arguments:
             as_series: If set to true ``True``, then the values are Narwhals Series,
@@ -930,8 +917,7 @@ class DataFrame(BaseFrame[DataFrameT]):
         return self._compliant_frame.to_dict(as_series=as_series)  # type: ignore[no-any-return]
 
     def row(self, index: int) -> tuple[Any, ...]:
-        """
-        Get values at given row.
+        """Get values at given row.
 
         !!!note
             You should NEVER use this method to iterate over a DataFrame;
@@ -968,8 +954,7 @@ class DataFrame(BaseFrame[DataFrameT]):
 
     # inherited
     def pipe(self, function: Callable[[Any], Self], *args: Any, **kwargs: Any) -> Self:
-        """
-        Pipe function call.
+        """Pipe function call.
 
         Examples:
             >>> import polars as pl
@@ -1009,8 +994,7 @@ class DataFrame(BaseFrame[DataFrameT]):
         return super().pipe(function, *args, **kwargs)
 
     def drop_nulls(self: Self, subset: str | list[str] | None = None) -> Self:
-        """
-        Drop null values.
+        """Drop null values.
 
         Arguments:
             subset: Column name(s) for which null values are considered. If set to None
@@ -1052,8 +1036,7 @@ class DataFrame(BaseFrame[DataFrameT]):
         return super().drop_nulls(subset=subset)
 
     def with_row_index(self, name: str = "index") -> Self:
-        """
-        Insert column which enumerates rows.
+        """Insert column which enumerates rows.
 
         Examples:
             Construct pandas as polars DataFrames:
@@ -1094,8 +1077,7 @@ class DataFrame(BaseFrame[DataFrameT]):
 
     @property
     def schema(self) -> Schema:
-        r"""
-        Get an ordered mapping of column names to their data type.
+        r"""Get an ordered mapping of column names to their data type.
 
         Examples:
             >>> import polars as pl
@@ -1128,8 +1110,7 @@ class DataFrame(BaseFrame[DataFrameT]):
         return super().schema
 
     def collect_schema(self: Self) -> Schema:
-        r"""
-        Get an ordered mapping of column names to their data type.
+        r"""Get an ordered mapping of column names to their data type.
 
         Examples:
             >>> import polars as pl
@@ -1163,8 +1144,7 @@ class DataFrame(BaseFrame[DataFrameT]):
 
     @property
     def columns(self) -> list[str]:
-        """
-        Get column names.
+        """Get column names.
 
         Examples:
             >>> import pandas as pd
@@ -1217,8 +1197,7 @@ class DataFrame(BaseFrame[DataFrameT]):
         *,
         named: bool = False,
     ) -> list[tuple[Any, ...]] | list[dict[str, Any]]:
-        """
-        Returns all data in the DataFrame as a list of rows of python-native values.
+        """Returns all data in the DataFrame as a list of rows of python-native values.
 
         Arguments:
             named: By default, each row is returned as a tuple of values given
@@ -1270,8 +1249,7 @@ class DataFrame(BaseFrame[DataFrameT]):
     def iter_rows(
         self, *, named: bool = False, buffer_size: int = 512
     ) -> Iterator[tuple[Any, ...]] | Iterator[dict[str, Any]]:
-        """
-        Returns an iterator over the DataFrame of rows of python-native values.
+        """Returns an iterator over the DataFrame of rows of python-native values.
 
         Arguments:
             named: By default, each row is returned as a tuple of values given
@@ -1314,8 +1292,7 @@ class DataFrame(BaseFrame[DataFrameT]):
     def with_columns(
         self, *exprs: IntoExpr | Iterable[IntoExpr], **named_exprs: IntoExpr
     ) -> Self:
-        r"""
-        Add columns to this DataFrame.
+        r"""Add columns to this DataFrame.
 
         Added columns will replace existing columns with the same name.
 
@@ -1381,8 +1358,7 @@ class DataFrame(BaseFrame[DataFrameT]):
         *exprs: IntoExpr | Iterable[IntoExpr],
         **named_exprs: IntoExpr,
     ) -> Self:
-        r"""
-        Select columns from this DataFrame.
+        r"""Select columns from this DataFrame.
 
         Arguments:
             *exprs: Column(s) to select, specified as positional arguments.
@@ -1500,8 +1476,7 @@ class DataFrame(BaseFrame[DataFrameT]):
         return super().select(*exprs, **named_exprs)
 
     def rename(self, mapping: dict[str, str]) -> Self:
-        """
-        Rename column names.
+        """Rename column names.
 
         Arguments:
             mapping: Key value pairs that map from old name to new name.
@@ -1542,8 +1517,7 @@ class DataFrame(BaseFrame[DataFrameT]):
         return super().rename(mapping)
 
     def head(self, n: int = 5) -> Self:
-        """
-        Get the first `n` rows.
+        """Get the first `n` rows.
 
         Arguments:
             n: Number of rows to return. If a negative value is passed, return all rows
@@ -1589,8 +1563,7 @@ class DataFrame(BaseFrame[DataFrameT]):
         return super().head(n)
 
     def tail(self, n: int = 5) -> Self:
-        """
-        Get the last `n` rows.
+        """Get the last `n` rows.
 
         Arguments:
             n: Number of rows to return. If a negative value is passed, return all rows
@@ -1636,8 +1609,7 @@ class DataFrame(BaseFrame[DataFrameT]):
         return super().tail(n)
 
     def drop(self, *columns: str | Iterable[str], strict: bool = True) -> Self:
-        """
-        Remove columns from the dataframe.
+        """Remove columns from the dataframe.
 
         Arguments:
             *columns: Names of the columns that should be removed from the dataframe.
@@ -1709,8 +1681,7 @@ class DataFrame(BaseFrame[DataFrameT]):
         keep: Literal["any", "first", "last", "none"] = "any",
         maintain_order: bool = False,
     ) -> Self:
-        """
-        Drop duplicate rows from this dataframe.
+        """Drop duplicate rows from this dataframe.
 
         Arguments:
             subset: Column name(s) to consider when identifying duplicate rows.
@@ -1762,8 +1733,7 @@ class DataFrame(BaseFrame[DataFrameT]):
         return super().unique(subset, keep=keep, maintain_order=maintain_order)
 
     def filter(self, *predicates: IntoExpr | Iterable[IntoExpr] | list[bool]) -> Self:
-        r"""
-        Filter the rows in the DataFrame based on one or more predicate expressions.
+        r"""Filter the rows in the DataFrame based on one or more predicate expressions.
 
         The original order of the remaining rows is preserved.
 
@@ -1870,8 +1840,7 @@ class DataFrame(BaseFrame[DataFrameT]):
     def group_by(
         self, *keys: str | Iterable[str], drop_null_keys: bool = False
     ) -> GroupBy[Self]:
-        r"""
-        Start a group by operation.
+        r"""Start a group by operation.
 
         Arguments:
             *keys: Column(s) to group by. Accepts multiple columns names as a list.
@@ -1954,8 +1923,7 @@ class DataFrame(BaseFrame[DataFrameT]):
         descending: bool | Sequence[bool] = False,
         nulls_last: bool = False,
     ) -> Self:
-        r"""
-        Sort the dataframe by the given columns.
+        r"""Sort the dataframe by the given columns.
 
         Arguments:
             by: Column(s) names to sort by.
@@ -2019,8 +1987,7 @@ class DataFrame(BaseFrame[DataFrameT]):
         right_on: str | list[str] | None = None,
         suffix: str = "_right",
     ) -> Self:
-        r"""
-        Join in SQL-like fashion.
+        r"""Join in SQL-like fashion.
 
         Arguments:
             other: Lazy DataFrame to join with.
@@ -2099,8 +2066,7 @@ class DataFrame(BaseFrame[DataFrameT]):
         by: str | list[str] | None = None,
         strategy: Literal["backward", "forward", "nearest"] = "backward",
     ) -> Self:
-        """
-        Perform an asof join.
+        """Perform an asof join.
 
         This is similar to a left-join except that we match on nearest key rather than equal keys.
 
@@ -2275,8 +2241,7 @@ class DataFrame(BaseFrame[DataFrameT]):
 
     # --- descriptive ---
     def is_duplicated(self: Self) -> Series:
-        r"""
-        Get a mask of all duplicated rows in this DataFrame.
+        r"""Get a mask of all duplicated rows in this DataFrame.
 
         Examples:
             >>> import narwhals as nw
@@ -2326,8 +2291,7 @@ class DataFrame(BaseFrame[DataFrameT]):
         )
 
     def is_empty(self: Self) -> bool:
-        r"""
-        Check if the dataframe is empty.
+        r"""Check if the dataframe is empty.
 
         Examples:
             >>> import narwhals as nw
@@ -2356,8 +2320,7 @@ class DataFrame(BaseFrame[DataFrameT]):
         return self._compliant_frame.is_empty()  # type: ignore[no-any-return]
 
     def is_unique(self: Self) -> Series:
-        r"""
-        Get a mask of all unique rows in this DataFrame.
+        r"""Get a mask of all unique rows in this DataFrame.
 
         Examples:
             >>> import narwhals as nw
@@ -2407,8 +2370,7 @@ class DataFrame(BaseFrame[DataFrameT]):
         )
 
     def null_count(self: Self) -> Self:
-        r"""
-        Create a new DataFrame that shows the null counts per column.
+        r"""Create a new DataFrame that shows the null counts per column.
 
         Notes:
             pandas and Polars handle null values differently. Polars distinguishes
@@ -2459,8 +2421,7 @@ class DataFrame(BaseFrame[DataFrameT]):
         return self._from_compliant_dataframe(self._compliant_frame.null_count())
 
     def item(self: Self, row: int | None = None, column: int | str | None = None) -> Any:
-        r"""
-        Return the DataFrame as a scalar, or return the element at the given row/column.
+        r"""Return the DataFrame as a scalar, or return the element at the given row/column.
 
         Notes:
             If row/col not provided, this is equivalent to df[0,0], with a check that the shape is (1,1).
@@ -2491,8 +2452,7 @@ class DataFrame(BaseFrame[DataFrameT]):
         return self._compliant_frame.item(row=row, column=column)
 
     def clone(self) -> Self:
-        r"""
-        Create a copy of this DataFrame.
+        r"""Create a copy of this DataFrame.
 
         Examples:
             >>> import narwhals as nw
@@ -2527,8 +2487,7 @@ class DataFrame(BaseFrame[DataFrameT]):
         return super().clone()
 
     def gather_every(self: Self, n: int, offset: int = 0) -> Self:
-        r"""
-        Take every nth row in the DataFrame and return as a new DataFrame.
+        r"""Take every nth row in the DataFrame and return as a new DataFrame.
 
         Arguments:
             n: Gather every *n*-th row.
@@ -2581,8 +2540,7 @@ class DataFrame(BaseFrame[DataFrameT]):
         sort_columns: bool = False,
         separator: str = "_",
     ) -> Self:
-        r"""
-        Create a spreadsheet-style pivot table as a DataFrame.
+        r"""Create a spreadsheet-style pivot table as a DataFrame.
 
         Arguments:
             on: Name of the column(s) whose values will be used as the header of the
@@ -2653,8 +2611,7 @@ class DataFrame(BaseFrame[DataFrameT]):
         )
 
     def to_arrow(self: Self) -> pa.Table:
-        r"""
-        Convert to arrow table.
+        r"""Convert to arrow table.
 
         Examples:
             >>> import narwhals as nw
@@ -2696,8 +2653,7 @@ class DataFrame(BaseFrame[DataFrameT]):
         with_replacement: bool = False,
         seed: int | None = None,
     ) -> Self:
-        r"""
-        Sample from this DataFrame.
+        r"""Sample from this DataFrame.
 
         Arguments:
             n: Number of items to return. Cannot be used with fraction.
@@ -2756,8 +2712,7 @@ class DataFrame(BaseFrame[DataFrameT]):
         variable_name: str | None = None,
         value_name: str | None = None,
     ) -> Self:
-        r"""
-        Unpivot a DataFrame from wide to long format.
+        r"""Unpivot a DataFrame from wide to long format.
 
         Optionally leaves identifiers set.
 
@@ -2837,8 +2792,7 @@ class DataFrame(BaseFrame[DataFrameT]):
 
 
 class LazyFrame(BaseFrame[FrameT]):
-    """
-    Narwhals DataFrame, backed by a native dataframe.
+    """Narwhals DataFrame, backed by a native dataframe.
 
     The native dataframe might be pandas.DataFrame, polars.LazyFrame, ...
 
@@ -2882,8 +2836,7 @@ class LazyFrame(BaseFrame[FrameT]):
         raise TypeError(msg)
 
     def collect(self) -> DataFrame[Any]:
-        r"""
-        Materialize this LazyFrame into a DataFrame.
+        r"""Materialize this LazyFrame into a DataFrame.
 
         Returns:
             DataFrame
@@ -2923,8 +2876,7 @@ class LazyFrame(BaseFrame[FrameT]):
         )
 
     def to_native(self) -> FrameT:
-        """
-        Convert Narwhals LazyFrame to native one.
+        """Convert Narwhals LazyFrame to native one.
 
         Returns:
             Object of class that user started with.
@@ -2958,13 +2910,11 @@ class LazyFrame(BaseFrame[FrameT]):
             │ 3   ┆ 8.0 ┆ c   │
             └─────┴─────┴─────┘
         """
-
         return to_native(narwhals_object=self, pass_through=False)
 
     # inherited
     def pipe(self, function: Callable[[Any], Self], *args: Any, **kwargs: Any) -> Self:
-        """
-        Pipe function call.
+        """Pipe function call.
 
         Examples:
             >>> import polars as pl
@@ -3002,8 +2952,7 @@ class LazyFrame(BaseFrame[FrameT]):
         return super().pipe(function, *args, **kwargs)
 
     def drop_nulls(self: Self, subset: str | list[str] | None = None) -> Self:
-        """
-        Drop null values.
+        """Drop null values.
 
         Arguments:
             subset: Column name(s) for which null values are considered. If set to None
@@ -3045,8 +2994,7 @@ class LazyFrame(BaseFrame[FrameT]):
         return super().drop_nulls(subset=subset)
 
     def with_row_index(self, name: str = "index") -> Self:
-        """
-        Insert column which enumerates rows.
+        """Insert column which enumerates rows.
 
         Examples:
             >>> import polars as pl
@@ -3085,8 +3033,7 @@ class LazyFrame(BaseFrame[FrameT]):
 
     @property
     def schema(self) -> Schema:
-        r"""
-        Get an ordered mapping of column names to their data type.
+        r"""Get an ordered mapping of column names to their data type.
 
         Examples:
             >>> import polars as pl
@@ -3105,8 +3052,7 @@ class LazyFrame(BaseFrame[FrameT]):
         return super().schema
 
     def collect_schema(self: Self) -> Schema:
-        r"""
-        Get an ordered mapping of column names to their data type.
+        r"""Get an ordered mapping of column names to their data type.
 
         Examples:
             >>> import polars as pl
@@ -3126,8 +3072,7 @@ class LazyFrame(BaseFrame[FrameT]):
 
     @property
     def columns(self) -> list[str]:
-        r"""
-        Get column names.
+        r"""Get column names.
 
         Examples:
             >>> import pandas as pd
@@ -3155,8 +3100,7 @@ class LazyFrame(BaseFrame[FrameT]):
     def with_columns(
         self, *exprs: IntoExpr | Iterable[IntoExpr], **named_exprs: IntoExpr
     ) -> Self:
-        r"""
-        Add columns to this LazyFrame.
+        r"""Add columns to this LazyFrame.
 
         Added columns will replace existing columns with the same name.
 
@@ -3235,8 +3179,7 @@ class LazyFrame(BaseFrame[FrameT]):
         *exprs: IntoExpr | Iterable[IntoExpr],
         **named_exprs: IntoExpr,
     ) -> Self:
-        r"""
-        Select columns from this LazyFrame.
+        r"""Select columns from this LazyFrame.
 
         Arguments:
             *exprs: Column(s) to select, specified as positional arguments.
@@ -3403,8 +3346,7 @@ class LazyFrame(BaseFrame[FrameT]):
         return super().select(*exprs, **named_exprs)
 
     def rename(self, mapping: dict[str, str]) -> Self:
-        r"""
-        Rename column names.
+        r"""Rename column names.
 
         Arguments:
             mapping: Key value pairs that map from old name to new name, or a
@@ -3447,8 +3389,7 @@ class LazyFrame(BaseFrame[FrameT]):
         return super().rename(mapping)
 
     def head(self, n: int = 5) -> Self:
-        r"""
-        Get the first `n` rows.
+        r"""Get the first `n` rows.
 
         Arguments:
             n: Number of rows to return.
@@ -3504,8 +3445,7 @@ class LazyFrame(BaseFrame[FrameT]):
         return super().head(n)
 
     def tail(self, n: int = 5) -> Self:
-        r"""
-        Get the last `n` rows.
+        r"""Get the last `n` rows.
 
         Arguments:
             n: Number of rows to return.
@@ -3561,8 +3501,7 @@ class LazyFrame(BaseFrame[FrameT]):
         return super().tail(n)
 
     def drop(self, *columns: str | Iterable[str], strict: bool = True) -> Self:
-        r"""
-        Remove columns from the LazyFrame.
+        r"""Remove columns from the LazyFrame.
 
         Arguments:
             *columns: Names of the columns that should be removed from the dataframe.
@@ -3639,8 +3578,7 @@ class LazyFrame(BaseFrame[FrameT]):
         keep: Literal["any", "first", "last", "none"] = "any",
         maintain_order: bool = False,
     ) -> Self:
-        """
-        Drop duplicate rows from this LazyFrame.
+        """Drop duplicate rows from this LazyFrame.
 
         Arguments:
             subset: Column name(s) to consider when identifying duplicate rows.
@@ -3696,8 +3634,7 @@ class LazyFrame(BaseFrame[FrameT]):
         return super().unique(subset, keep=keep, maintain_order=maintain_order)
 
     def filter(self, *predicates: IntoExpr | Iterable[IntoExpr] | list[bool]) -> Self:
-        r"""
-        Filter the rows in the LazyFrame based on a predicate expression.
+        r"""Filter the rows in the LazyFrame based on a predicate expression.
 
         The original order of the remaining rows is preserved.
 
@@ -3845,8 +3782,7 @@ class LazyFrame(BaseFrame[FrameT]):
     def group_by(
         self, *keys: str | Iterable[str], drop_null_keys: bool = False
     ) -> LazyGroupBy[Self]:
-        r"""
-        Start a group by operation.
+        r"""Start a group by operation.
 
         Arguments:
             *keys:
@@ -3955,8 +3891,7 @@ class LazyFrame(BaseFrame[FrameT]):
         descending: bool | Sequence[bool] = False,
         nulls_last: bool = False,
     ) -> Self:
-        r"""
-        Sort the LazyFrame by the given columns.
+        r"""Sort the LazyFrame by the given columns.
 
         Arguments:
             by: Column(s) names to sort by.
@@ -4021,8 +3956,7 @@ class LazyFrame(BaseFrame[FrameT]):
         right_on: str | list[str] | None = None,
         suffix: str = "_right",
     ) -> Self:
-        r"""
-        Add a join operation to the Logical Plan.
+        r"""Add a join operation to the Logical Plan.
 
         Arguments:
             other: Lazy DataFrame to join with.
@@ -4101,8 +4035,7 @@ class LazyFrame(BaseFrame[FrameT]):
         by: str | list[str] | None = None,
         strategy: Literal["backward", "forward", "nearest"] = "backward",
     ) -> Self:
-        """
-        Perform an asof join.
+        """Perform an asof join.
 
         This is similar to a left-join except that we match on nearest key rather than equal keys.
 
@@ -4275,8 +4208,7 @@ class LazyFrame(BaseFrame[FrameT]):
         )
 
     def clone(self) -> Self:
-        r"""
-        Create a copy of this DataFrame.
+        r"""Create a copy of this DataFrame.
 
         Examples:
             >>> import narwhals as nw
@@ -4311,8 +4243,7 @@ class LazyFrame(BaseFrame[FrameT]):
         return super().clone()
 
     def lazy(self) -> Self:
-        """
-        Lazify the DataFrame (if possible).
+        """Lazify the DataFrame (if possible).
 
         If a library does not support lazy execution, then this is a no-op.
 
@@ -4345,8 +4276,7 @@ class LazyFrame(BaseFrame[FrameT]):
         return self
 
     def gather_every(self: Self, n: int, offset: int = 0) -> Self:
-        r"""
-        Take every nth row in the DataFrame and return as a new DataFrame.
+        r"""Take every nth row in the DataFrame and return as a new DataFrame.
 
         Arguments:
             n: Gather every *n*-th row.
@@ -4393,8 +4323,7 @@ class LazyFrame(BaseFrame[FrameT]):
         variable_name: str | None = None,
         value_name: str | None = None,
     ) -> Self:
-        r"""
-        Unpivot a DataFrame from wide to long format.
+        r"""Unpivot a DataFrame from wide to long format.
 
         Optionally leaves identifiers set.
 
diff --git a/narwhals/dependencies.py b/narwhals/dependencies.py
index 7aaa9f15..3f5e1838 100644
--- a/narwhals/dependencies.py
+++ b/narwhals/dependencies.py
@@ -185,8 +185,7 @@ def is_numpy_array(arr: Any) -> TypeGuard[np.ndarray]:
 
 
 def is_pandas_like_dataframe(df: Any) -> bool:
-    """
-    Check whether `df` is a pandas-like DataFrame without doing any imports
+    """Check whether `df` is a pandas-like DataFrame without doing any imports.
 
     By "pandas-like", we mean: pandas, Modin, cuDF.
     """
@@ -194,8 +193,7 @@ def is_pandas_like_dataframe(df: Any) -> bool:
 
 
 def is_pandas_like_series(ser: Any) -> bool:
-    """
-    Check whether `ser` is a pandas-like Series without doing any imports
+    """Check whether `ser` is a pandas-like Series without doing any imports.
 
     By "pandas-like", we mean: pandas, Modin, cuDF.
     """
@@ -203,8 +201,7 @@ def is_pandas_like_series(ser: Any) -> bool:
 
 
 def is_pandas_like_index(index: Any) -> bool:
-    """
-    Check whether `index` is a pandas-like Index without doing any imports
+    """Check whether `index` is a pandas-like Index without doing any imports.
 
     By "pandas-like", we mean: pandas, Modin, cuDF.
     """
@@ -214,8 +211,7 @@ def is_pandas_like_index(index: Any) -> bool:
 
 
 def is_into_series(native_series: IntoSeries) -> bool:
-    """
-    Check whether `native_series` can be converted to a Narwhals Series.
+    """Check whether `native_series` can be converted to a Narwhals Series.
 
     Arguments:
         native_series: The object to check.
@@ -252,8 +248,7 @@ def is_into_series(native_series: IntoSeries) -> bool:
 
 
 def is_into_dataframe(native_dataframe: Any) -> bool:
-    """
-    Check whether `native_dataframe` can be converted to a Narwhals DataFrame.
+    """Check whether `native_dataframe` can be converted to a Narwhals DataFrame.
 
     Arguments:
         native_dataframe: The object to check.
diff --git a/narwhals/dtypes.py b/narwhals/dtypes.py
index 73a77af1..278e7d71 100644
--- a/narwhals/dtypes.py
+++ b/narwhals/dtypes.py
@@ -36,40 +36,312 @@ class NumericType(DType): ...
 class TemporalType(DType): ...
 
 
-class Int64(NumericType): ...
+class Int64(NumericType):
+    """64-bit signed integer type.
+
+    Examples:
+        >>> import pandas as pd
+        >>> import polars as pl
+        >>> import pyarrow as pa
+        >>> import narwhals as nw
+        >>> data = [2, 1, 3, 7]
+        >>> ser_pd = pd.Series(data)
+        >>> ser_pl = pl.Series(data)
+        >>> ser_pa = pa.chunked_array([data])
+
+        >>> nw.from_native(ser_pd, series_only=True).dtype
+        Int64
+        >>> nw.from_native(ser_pl, series_only=True).dtype
+        Int64
+        >>> nw.from_native(ser_pa, series_only=True).dtype
+        Int64
+    """
+
+
+class Int32(NumericType):
+    """32-bit signed integer type.
+
+    Examples:
+        >>> import pandas as pd
+        >>> import polars as pl
+        >>> import pyarrow as pa
+        >>> import narwhals as nw
+        >>> data = [2, 1, 3, 7]
+        >>> ser_pd = pd.Series(data)
+        >>> ser_pl = pl.Series(data)
+        >>> ser_pa = pa.chunked_array([data])
+
+        >>> def func(ser):
+        ...     ser_nw = nw.from_native(ser, series_only=True)
+        ...     return ser_nw.cast(nw.Int32).dtype
+
+        >>> func(ser_pd)
+        Int32
+        >>> func(ser_pl)
+        Int32
+        >>> func(ser_pa)
+        Int32
+    """
+
+
+class Int16(NumericType):
+    """16-bit signed integer type.
+
+    Examples:
+        >>> import pandas as pd
+        >>> import polars as pl
+        >>> import pyarrow as pa
+        >>> import narwhals as nw
+        >>> data = [2, 1, 3, 7]
+        >>> ser_pd = pd.Series(data)
+        >>> ser_pl = pl.Series(data)
+        >>> ser_pa = pa.chunked_array([data])
+
+       >>> def func(ser):
+       ...     ser_nw = nw.from_native(ser, series_only=True)
+       ...     return ser_nw.cast(nw.Int16).dtype
+
+
+       >>> func(ser_pd)
+       Int16
+       >>> func(ser_pl)
+       Int16
+       >>> func(ser_pa)
+       Int16
+    """
+
+
+class Int8(NumericType):
+    """8-bit signed integer type.
+
+    Examples:
+       >>> import pandas as pd
+       >>> import polars as pl
+       >>> import pyarrow as pa
+       >>> import narwhals as nw
+       >>> data = [2, 1, 3, 7]
+       >>> ser_pd = pd.Series(data)
+       >>> ser_pl = pl.Series(data)
+       >>> ser_pa = pa.chunked_array([data])
+
 
+       >>> def func(ser):
+       ...     ser_nw = nw.from_native(ser, series_only=True)
+       ...     return ser_nw.cast(nw.Int8).dtype
 
-class Int32(NumericType): ...
 
+       >>> func(ser_pd)
+       Int8
+       >>> func(ser_pl)
+       Int8
+       >>> func(ser_pa)
+       Int8
+    """
+
+
+class UInt64(NumericType):
+    """64-bit unsigned integer type.
+
+    Examples:
+       >>> import pandas as pd
+       >>> import polars as pl
+       >>> import pyarrow as pa
+       >>> import narwhals as nw
+       >>> data = [2, 1, 3, 7]
+       >>> ser_pd = pd.Series(data)
+       >>> ser_pl = pl.Series(data)
+       >>> ser_pa = pa.chunked_array([data])
 
-class Int16(NumericType): ...
 
+       >>> def func(ser):
+       ...     ser_nw = nw.from_native(ser, series_only=True)
+       ...     return ser_nw.cast(nw.UInt64).dtype
 
-class Int8(NumericType): ...
+
+       >>> func(ser_pd)
+       UInt64
+       >>> func(ser_pl)
+       UInt64
+       >>> func(ser_pa)
+       UInt64
+    """
 
 
-class UInt64(NumericType): ...
+class UInt32(NumericType):
+    """32-bit unsigned integer type.
 
+    Examples:
+       >>> import pandas as pd
+       >>> import polars as pl
+       >>> import pyarrow as pa
+       >>> import narwhals as nw
+       >>> data = [2, 1, 3, 7]
+       >>> ser_pd = pd.Series(data)
+       >>> ser_pl = pl.Series(data)
+       >>> ser_pa = pa.chunked_array([data])
 
-class UInt32(NumericType): ...
 
+       >>> def func(ser):
+       ...     ser_nw = nw.from_native(ser, series_only=True)
+       ...     return ser_nw.cast(nw.UInt32).dtype
 
-class UInt16(NumericType): ...
 
+       >>> func(ser_pd)
+       UInt32
+       >>> func(ser_pl)
+       UInt32
+       >>> func(ser_pa)
+       UInt32
+    """
 
-class UInt8(NumericType): ...
 
+class UInt16(NumericType):
+    """16-bit unsigned integer type.
+
+    Examples:
+       >>> import pandas as pd
+       >>> import polars as pl
+       >>> import pyarrow as pa
+       >>> import narwhals as nw
+       >>> data = [2, 1, 3, 7]
+       >>> ser_pd = pd.Series(data)
+       >>> ser_pl = pl.Series(data)
+       >>> ser_pa = pa.chunked_array([data])
+
+       >>> def func(ser):
+       ...     ser_nw = nw.from_native(ser, series_only=True)
+       ...     return ser_nw.cast(nw.UInt16).dtype
+
+       >>> func(ser_pd)
+       UInt16
+       >>> func(ser_pl)
+       UInt16
+       >>> func(ser_pa)
+       UInt16
+    """
 
-class Float64(NumericType): ...
 
+class UInt8(NumericType):
+    """8-bit unsigned integer type.
 
-class Float32(NumericType): ...
+    Examples:
+       >>> import pandas as pd
+       >>> import polars as pl
+       >>> import pyarrow as pa
+       >>> import narwhals as nw
+       >>> data = [2, 1, 3, 7]
+       >>> ser_pd = pd.Series(data)
+       >>> ser_pl = pl.Series(data)
+       >>> ser_pa = pa.chunked_array([data])
+
+
+       >>> def func(ser):
+       ...     ser_nw = nw.from_native(ser, series_only=True)
+       ...     return ser_nw.cast(nw.UInt8).dtype
+
+       >>> func(ser_pd)
+       UInt8
+       >>> func(ser_pl)
+       UInt8
+       >>> func(ser_pa)
+       UInt8
+    """
+
+
+class Float64(NumericType):
+    """64-bit floating point type.
+
+    Examples:
+        >>> import pandas as pd
+        >>> import polars as pl
+        >>> import pyarrow as pa
+        >>> import narwhals as nw
+        >>> data = [0.001, 0.1, 0.01, 0.1]
+        >>> ser_pd = pd.Series(data)
+        >>> ser_pl = pl.Series(data)
+        >>> ser_pa = pa.chunked_array([data])
+
+        >>> nw.from_native(ser_pd, series_only=True).dtype
+        Float64
+        >>> nw.from_native(ser_pl, series_only=True).dtype
+        Float64
+        >>> nw.from_native(ser_pa, series_only=True).dtype
+        Float64
+    """
 
 
-class String(DType): ...
+class Float32(NumericType):
+    """32-bit floating point type.
 
+    Examples:
+       >>> import pandas as pd
+       >>> import polars as pl
+       >>> import pyarrow as pa
+       >>> import narwhals as nw
+       >>> data = [0.001, 0.1, 0.01, 0.1]
+       >>> ser_pd = pd.Series(data)
+       >>> ser_pl = pl.Series(data)
+       >>> ser_pa = pa.chunked_array([data])
 
-class Boolean(DType): ...
+
+       >>> def func(ser):
+       ...     ser_nw = nw.from_native(ser, series_only=True)
+       ...     return ser_nw.cast(nw.Float32).dtype
+
+
+       >>> func(ser_pd)
+       Float32
+       >>> func(ser_pl)
+       Float32
+       >>> func(ser_pa)
+       Float32
+    """
+
+
+class String(DType):
+    """UTF-8 encoded string type.
+
+    Examples:
+       >>> import pandas as pd
+       >>> import polars as pl
+       >>> import pyarrow as pa
+       >>> import narwhals as nw
+       >>> data = ["beluga", "narwhal", "orca", "vaquita"]
+       >>> ser_pd = pd.Series(data)
+       >>> ser_pl = pl.Series(data)
+       >>> ser_pa = pa.chunked_array([data])
+
+
+       >>> nw.from_native(ser_pd, series_only=True).dtype
+       String
+       >>> nw.from_native(ser_pl, series_only=True).dtype
+       String
+       >>> nw.from_native(ser_pa, series_only=True).dtype
+       String
+    """
+
+
+class Boolean(DType):
+    """Boolean type.
+
+    Examples:
+       >>> import pandas as pd
+       >>> import polars as pl
+       >>> import pyarrow as pa
+       >>> import narwhals as nw
+       >>> data = [True, False, False, True]
+       >>> ser_pd = pd.Series(data)
+       >>> ser_pl = pl.Series(data)
+       >>> ser_pa = pa.chunked_array([data])
+
+
+       >>> nw.from_native(ser_pd, series_only=True).dtype
+       Boolean
+       >>> nw.from_native(ser_pl, series_only=True).dtype
+       Boolean
+       >>> nw.from_native(ser_pa, series_only=True).dtype
+       Boolean
+    """
 
 
 class Object(DType): ...
@@ -79,8 +351,7 @@ class Unknown(DType): ...
 
 
 class Datetime(TemporalType):
-    """
-    Data type representing a calendar date and time of day.
+    """Data type representing a calendar date and time of day.
 
     Arguments:
         time_unit: Unit of time. Defaults to `'us'` (microseconds).
@@ -128,8 +399,7 @@ class Datetime(TemporalType):
 
 
 class Duration(TemporalType):
-    """
-    Data type representing a time duration.
+    """Data type representing a time duration.
 
     Arguments:
         time_unit: Unit of time. Defaults to `'us'` (microseconds).
@@ -169,15 +439,34 @@ class Duration(TemporalType):
         return f"{class_name}(time_unit={self.time_unit!r})"
 
 
-class Categorical(DType): ...
+class Categorical(DType):
+    """A categorical encoding of a set of strings.
+
+    Examples:
+       >>> import pandas as pd
+       >>> import polars as pl
+       >>> import pyarrow as pa
+       >>> import narwhals as nw
+       >>> data = ["beluga", "narwhal", "orca", "vaquita"]
+       >>> ser_pd = pd.Series(data)
+       >>> ser_pl = pl.Series(data)
+       >>> ser_pa = pa.chunked_array([data])
+
+
+       >>> nw.from_native(ser_pd, series_only=True).cast(nw.Categorical).dtype
+       Categorical
+       >>> nw.from_native(ser_pl, series_only=True).cast(nw.Categorical).dtype
+       Categorical
+       >>> nw.from_native(ser_pa, series_only=True).cast(nw.Categorical).dtype
+       Categorical
+    """
 
 
 class Enum(DType): ...
 
 
 class Field:
-    """
-    Definition of a single field within a `Struct` DataType.
+    """Definition of a single field within a `Struct` DataType.
 
     Arguments:
         name: The name of the field within its parent `Struct`.
@@ -204,11 +493,24 @@ class Field:
 
 
 class Struct(DType):
-    """
-    Struct composite type.
+    """Struct composite type.
 
     Arguments:
         fields: The fields that make up the struct. Can be either a sequence of Field objects or a mapping of column names to data types.
+
+    Examples:
+       >>> import polars as pl
+       >>> import pyarrow as pa
+       >>> import narwhals as nw
+       >>> data = [{"a": 1, "b": ["narwhal", "beluga"]}, {"a": 2, "b": ["orca"]}]
+       >>> ser_pl = pl.Series(data)
+       >>> ser_pa = pa.chunked_array([data])
+
+
+       >>> nw.from_native(ser_pl, series_only=True).dtype
+       Struct({'a': Int64, 'b': List(String)})
+       >>> nw.from_native(ser_pa, series_only=True).dtype
+       Struct({'a': Int64, 'b': List(String)})
     """
 
     fields: list[Field]
@@ -254,6 +556,26 @@ class Struct(DType):
 
 
 class List(DType):
+    """Variable length list type.
+
+    Examples:
+       >>> import pandas as pd
+       >>> import polars as pl
+       >>> import pyarrow as pa
+       >>> import narwhals as nw
+       >>> data = [["narwhal", "orca"], ["beluga", "vaquita"]]
+       >>> ser_pd = pd.Series(data, dtype=pd.ArrowDtype(pa.large_list(pa.large_string())))
+       >>> ser_pl = pl.Series(data)
+       >>> ser_pa = pa.chunked_array([data])
+
+       >>> nw.from_native(ser_pd, series_only=True).dtype
+       List(String)
+       >>> nw.from_native(ser_pl, series_only=True).dtype
+       List(String)
+       >>> nw.from_native(ser_pa, series_only=True).dtype
+       List(String)
+    """
+
     def __init__(self, inner: DType | type[DType]) -> None:
         self.inner = inner
 
diff --git a/narwhals/expr.py b/narwhals/expr.py
index 7a280c02..9865a04e 100644
--- a/narwhals/expr.py
+++ b/narwhals/expr.py
@@ -42,12 +42,14 @@ class Expr:
 
     # --- convert ---
     def alias(self, name: str) -> Self:
-        """
-        Rename the expression.
+        """Rename the expression.
 
         Arguments:
             name: The new name.
 
+        Returns:
+            A new expression.
+
         Examples:
             >>> import pandas as pd
             >>> import polars as pl
@@ -89,8 +91,10 @@ class Expr:
         return self.__class__(lambda plx: self._call(plx).alias(name))
 
     def pipe(self, function: Callable[[Any], Self], *args: Any, **kwargs: Any) -> Self:
-        """
-        Pipe function call.
+        """Pipe function call.
+
+        Returns:
+            A new expression.
 
         Examples:
             >>> import polars as pl
@@ -137,12 +141,14 @@ class Expr:
         return function(self, *args, **kwargs)
 
     def cast(self: Self, dtype: DType | type[DType]) -> Self:
-        """
-        Redefine an object's data type.
+        """Redefine an object's data type.
 
         Arguments:
             dtype: Data type that the object will be cast into.
 
+        Returns:
+            A new expression.
+
         Examples:
             >>> import pandas as pd
             >>> import polars as pl
@@ -317,8 +323,10 @@ class Expr:
         return self.__class__(lambda plx: self._call(plx).__invert__())
 
     def any(self) -> Self:
-        """
-        Return whether any of the values in the column are `True`
+        """Return whether any of the values in the column are `True`.
+
+        Returns:
+            A new expression.
 
         Examples:
             >>> import pandas as pd
@@ -360,8 +368,10 @@ class Expr:
         return self.__class__(lambda plx: self._call(plx).any())
 
     def all(self) -> Self:
-        """
-        Return whether all values in the column are `True`.
+        """Return whether all values in the column are `True`.
+
+        Returns:
+            A new expression.
 
         Examples:
             >>> import pandas as pd
@@ -403,8 +413,10 @@ class Expr:
         return self.__class__(lambda plx: self._call(plx).all())
 
     def mean(self) -> Self:
-        """
-        Get mean value.
+        """Get mean value.
+
+        Returns:
+            A new expression.
 
         Examples:
             >>> import polars as pl
@@ -446,8 +458,10 @@ class Expr:
         return self.__class__(lambda plx: self._call(plx).mean())
 
     def median(self) -> Self:
-        """
-        Get median value.
+        """Get median value.
+
+        Returns:
+            A new expression.
 
         Notes:
             Results might slightly differ across backends due to differences in the underlying algorithms used to compute the median.
@@ -492,13 +506,15 @@ class Expr:
         return self.__class__(lambda plx: self._call(plx).median())
 
     def std(self, *, ddof: int = 1) -> Self:
-        """
-        Get standard deviation.
+        """Get standard deviation.
 
         Arguments:
             ddof: “Delta Degrees of Freedom”: the divisor used in the calculation is N - ddof,
                      where N represents the number of elements. By default ddof is 1.
 
+        Returns:
+            A new expression.
+
         Examples:
             >>> import polars as pl
             >>> import pandas as pd
@@ -544,17 +560,20 @@ class Expr:
         function: Callable[[Any], Self],
         return_dtype: DType | None = None,
     ) -> Self:
-        """
-        Apply a custom python function to a whole Series or sequence of Series.
+        """Apply a custom python function to a whole Series or sequence of Series.
 
         The output of this custom function is presumed to be either a Series,
         or a NumPy array (in which case it will be automatically converted into
         a Series).
 
         Arguments:
+            function: Function to apply to Series.
             return_dtype: Dtype of the output Series.
-                          If not set, the dtype will be inferred based on the first non-null value
-                          that is returned by the function.
+                If not set, the dtype will be inferred based on the first non-null value
+                that is returned by the function.
+
+        Returns:
+            A new expression.
 
         Examples:
             >>> import polars as pl
@@ -609,8 +628,10 @@ class Expr:
         )
 
     def sum(self) -> Expr:
-        """
-        Return the sum value.
+        """Return the sum value.
+
+        Returns:
+            A new expression.
 
         Examples:
             >>> import pandas as pd
@@ -652,8 +673,10 @@ class Expr:
         return self.__class__(lambda plx: self._call(plx).sum())
 
     def min(self) -> Self:
-        """
-        Returns the minimum value(s) from a column(s).
+        """Returns the minimum value(s) from a column(s).
+
+        Returns:
+            A new expression.
 
         Examples:
             >>> import pandas as pd
@@ -695,8 +718,10 @@ class Expr:
         return self.__class__(lambda plx: self._call(plx).min())
 
     def max(self) -> Self:
-        """
-        Returns the maximum value(s) from a column(s).
+        """Returns the maximum value(s) from a column(s).
+
+        Returns:
+            A new expression.
 
         Examples:
             >>> import polars as pl
@@ -738,8 +763,10 @@ class Expr:
         return self.__class__(lambda plx: self._call(plx).max())
 
     def count(self) -> Self:
-        """
-        Returns the number of non-null elements in the column.
+        """Returns the number of non-null elements in the column.
+
+        Returns:
+            A new expression.
 
         Examples:
             >>> import polars as pl
@@ -781,8 +808,10 @@ class Expr:
         return self.__class__(lambda plx: self._call(plx).count())
 
     def n_unique(self) -> Self:
-        """
-         Returns count of unique values
+        """Returns count of unique values.
+
+        Returns:
+            A new expression.
 
         Examples:
             >>> import polars as pl
@@ -824,14 +853,16 @@ class Expr:
         return self.__class__(lambda plx: self._call(plx).n_unique())
 
     def unique(self, *, maintain_order: bool = False) -> Self:
-        """
-        Return unique values of this expression.
+        """Return unique values of this expression.
 
         Arguments:
             maintain_order: Keep the same order as the original expression. This may be more
                 expensive to compute. Settings this to `True` blocks the possibility
                 to run on the streaming engine for Polars.
 
+        Returns:
+            A new expression.
+
         Examples:
             >>> import polars as pl
             >>> import pandas as pd
@@ -878,8 +909,10 @@ class Expr:
         )
 
     def abs(self) -> Self:
-        """
-        Return absolute value of each element.
+        """Return absolute value of each element.
+
+        Returns:
+            A new expression.
 
         Examples:
             >>> import polars as pl
@@ -924,8 +957,10 @@ class Expr:
         return self.__class__(lambda plx: self._call(plx).abs())
 
     def cum_sum(self) -> Self:
-        """
-        Return cumulative sum.
+        """Return cumulative sum.
+
+        Returns:
+            A new expression.
 
         Examples:
             >>> import polars as pl
@@ -975,8 +1010,10 @@ class Expr:
         return self.__class__(lambda plx: self._call(plx).cum_sum())
 
     def diff(self) -> Self:
-        """
-        Returns the difference between each element and the previous one.
+        """Returns the difference between each element and the previous one.
+
+        Returns:
+            A new expression.
 
         Notes:
             pandas may change the dtype here, for example when introducing missing
@@ -1033,8 +1070,10 @@ class Expr:
         return self.__class__(lambda plx: self._call(plx).diff())
 
     def shift(self, n: int) -> Self:
-        """
-        Shift values by `n` positions.
+        """Shift values by `n` positions.
+
+        Returns:
+            A new expression.
 
         Notes:
             pandas may change the dtype here, for example when introducing missing
@@ -1097,8 +1136,7 @@ class Expr:
         *,
         return_dtype: DType | type[DType] | None = None,
     ) -> Self:
-        """
-        Replace all values by different values.
+        """Replace all values by different values.
 
         This function must replace all non-null input values (else it raises an error).
 
@@ -1111,6 +1149,9 @@ class Expr:
                 (default), the data type is determined automatically based on the other
                 inputs.
 
+        Returns:
+            A new expression.
+
         Examples:
             >>> import narwhals as nw
             >>> import pandas as pd
@@ -1175,13 +1216,15 @@ class Expr:
         )
 
     def sort(self, *, descending: bool = False, nulls_last: bool = False) -> Self:
-        """
-        Sort this column. Place null values first.
+        """Sort this column. Place null values first.
 
         Arguments:
             descending: Sort in descending order.
             nulls_last: Place null values last instead of first.
 
+        Returns:
+            A new expression.
+
         Examples:
             >>> import narwhals as nw
             >>> import pandas as pd
@@ -1260,8 +1303,7 @@ class Expr:
     def is_between(
         self, lower_bound: Any, upper_bound: Any, closed: str = "both"
     ) -> Self:
-        """
-        Check if this expression is between the given lower and upper bounds.
+        """Check if this expression is between the given lower and upper bounds.
 
         Arguments:
             lower_bound: Lower bound value.
@@ -1270,6 +1312,9 @@ class Expr:
 
             closed: Define which sides of the interval are closed (inclusive).
 
+        Returns:
+            A new expression.
+
         Examples:
             >>> import pandas as pd
             >>> import polars as pl
@@ -1318,12 +1363,14 @@ class Expr:
         )
 
     def is_in(self, other: Any) -> Self:
-        """
-        Check if elements of this expression are present in the other iterable.
+        """Check if elements of this expression are present in the other iterable.
 
         Arguments:
             other: iterable
 
+        Returns:
+            A new expression.
+
         Examples:
             >>> import pandas as pd
             >>> import polars as pl
@@ -1375,8 +1422,10 @@ class Expr:
             raise NotImplementedError(msg)
 
     def filter(self, *predicates: Any) -> Self:
-        """
-        Filters elements based on a condition, returning a new expression.
+        """Filters elements based on a condition, returning a new expression.
+
+        Returns:
+            A new expression.
 
         Examples:
             >>> import polars as pl
@@ -1429,8 +1478,10 @@ class Expr:
         )
 
     def is_null(self) -> Self:
-        """
-        Returns a boolean Series indicating which values are null.
+        """Returns a boolean Series indicating which values are null.
+
+        Returns:
+            A new expression.
 
         Notes:
             pandas and Polars handle null values differently. Polars distinguishes
@@ -1498,8 +1549,10 @@ class Expr:
         return self.__class__(lambda plx: self._call(plx).is_null())
 
     def arg_true(self) -> Self:
-        """
-        Find elements where boolean expression is True.
+        """Find elements where boolean expression is True.
+
+        Returns:
+            A new expression.
 
         Examples:
             >>> import pandas as pd
@@ -1547,8 +1600,7 @@ class Expr:
         strategy: Literal["forward", "backward"] | None = None,
         limit: int | None = None,
     ) -> Self:
-        """
-        Fill null values with given value.
+        """Fill null values with given value.
 
         Arguments:
             value: Value used to fill null values.
@@ -1557,6 +1609,9 @@ class Expr:
 
             limit: Number of consecutive null values to fill when using the 'forward' or 'backward' strategy.
 
+        Returns:
+            A new expression.
+
         Notes:
             pandas and Polars handle null values differently. Polars distinguishes
             between NaN and Null, whereas pandas doesn't.
@@ -1688,8 +1743,10 @@ class Expr:
 
     # --- partial reduction ---
     def drop_nulls(self) -> Self:
-        """
-        Remove missing values.
+        """Remove missing values.
+
+        Returns:
+            A new expression.
 
         Notes:
             pandas and Polars handle null values differently. Polars distinguishes
@@ -1748,8 +1805,7 @@ class Expr:
         with_replacement: bool = False,
         seed: int | None = None,
     ) -> Self:
-        """
-        Sample randomly from this expression.
+        """Sample randomly from this expression.
 
         Arguments:
             n: Number of items to return. Cannot be used with fraction.
@@ -1758,6 +1814,9 @@ class Expr:
             seed: Seed for the random number generator. If set to None (default), a random
                 seed is generated for each sample operation.
 
+        Returns:
+            A new expression.
+
         Examples:
             >>> import narwhals as nw
             >>> import pandas as pd
@@ -1804,14 +1863,16 @@ class Expr:
         )
 
     def over(self, *keys: str | Iterable[str]) -> Self:
-        """
-        Compute expressions over the given groups.
+        """Compute expressions over the given groups.
 
         Arguments:
             keys: Names of columns to compute window expression over.
                   Must be names of columns, as opposed to expressions -
                   so, this is a bit less flexible than Polars' `Expr.over`.
 
+        Returns:
+            A new expression.
+
         Examples:
             >>> import narwhals as nw
             >>> import pandas as pd
@@ -1859,8 +1920,10 @@ class Expr:
         return self.__class__(lambda plx: self._call(plx).over(flatten(keys)))
 
     def is_duplicated(self) -> Self:
-        r"""
-        Return a boolean mask indicating duplicated values.
+        r"""Return a boolean mask indicating duplicated values.
+
+        Returns:
+            A new expression.
 
         Examples:
             >>> import narwhals as nw
@@ -1909,8 +1972,10 @@ class Expr:
         return self.__class__(lambda plx: self._call(plx).is_duplicated())
 
     def is_unique(self) -> Self:
-        r"""
-        Return a boolean mask indicating unique values.
+        r"""Return a boolean mask indicating unique values.
+
+        Returns:
+            A new expression.
 
         Examples:
             >>> import narwhals as nw
@@ -1959,8 +2024,10 @@ class Expr:
         return self.__class__(lambda plx: self._call(plx).is_unique())
 
     def null_count(self) -> Self:
-        r"""
-        Count null values.
+        r"""Count null values.
+
+        Returns:
+            A new expression.
 
         Notes:
             pandas and Polars handle null values differently. Polars distinguishes
@@ -2007,8 +2074,10 @@ class Expr:
         return self.__class__(lambda plx: self._call(plx).null_count())
 
     def is_first_distinct(self) -> Self:
-        r"""
-        Return a boolean mask indicating the first occurrence of each distinct value.
+        r"""Return a boolean mask indicating the first occurrence of each distinct value.
+
+        Returns:
+            A new expression.
 
         Examples:
             >>> import narwhals as nw
@@ -2059,6 +2128,9 @@ class Expr:
     def is_last_distinct(self) -> Self:
         r"""Return a boolean mask indicating the last occurrence of each distinct value.
 
+        Returns:
+            A new expression.
+
         Examples:
             >>> import narwhals as nw
             >>> import pandas as pd
@@ -2112,15 +2184,20 @@ class Expr:
     ) -> Self:
         r"""Get quantile value.
 
-        Note:
-            - pandas and Polars may have implementation differences for a given interpolation method.
-            - [dask](https://docs.dask.org/en/stable/generated/dask.dataframe.Series.quantile.html) has its own method to approximate quantile and it doesn't implement 'nearest', 'higher', 'lower', 'midpoint'
-            as interpolation method - use 'linear' which is closest to the native 'dask' - method.
-
         Arguments:
             quantile: Quantile between 0.0 and 1.0.
             interpolation: Interpolation method.
 
+        Returns:
+            A new expression.
+
+        Note:
+            - pandas and Polars may have implementation differences for a given interpolation method.
+            - [dask](https://docs.dask.org/en/stable/generated/dask.dataframe.Series.quantile.html) has
+                its own method to approximate quantile and it doesn't implement 'nearest', 'higher',
+                'lower', 'midpoint' as interpolation method - use 'linear' which is closest to the
+                native 'dask' - method.
+
         Examples:
             >>> import narwhals as nw
             >>> import pandas as pd
@@ -2165,12 +2242,14 @@ class Expr:
         )
 
     def head(self, n: int = 10) -> Self:
-        r"""
-        Get the first `n` rows.
+        r"""Get the first `n` rows.
 
         Arguments:
             n: Number of rows to return.
 
+        Returns:
+            A new expression.
+
         Examples:
             >>> import narwhals as nw
             >>> import pandas as pd
@@ -2214,12 +2293,14 @@ class Expr:
         return self.__class__(lambda plx: self._call(plx).head(n))
 
     def tail(self, n: int = 10) -> Self:
-        r"""
-        Get the last `n` rows.
+        r"""Get the last `n` rows.
 
         Arguments:
             n: Number of rows to return.
 
+        Returns:
+            A new expression.
+
         Examples:
             >>> import narwhals as nw
             >>> import pandas as pd
@@ -2263,12 +2344,15 @@ class Expr:
         return self.__class__(lambda plx: self._call(plx).tail(n))
 
     def round(self, decimals: int = 0) -> Self:
-        r"""
-        Round underlying floating point data by `decimals` digits.
+        r"""Round underlying floating point data by `decimals` digits.
 
         Arguments:
             decimals: Number of decimals to round by.
 
+        Returns:
+            A new expression.
+
+
         Notes:
             For values exactly halfway between rounded decimal values pandas behaves differently than Polars and Arrow.
 
@@ -2277,7 +2361,6 @@ class Expr:
 
             Polars and Arrow round away from 0 (e.g. -0.5 to -1.0, 0.5 to 1.0, 1.5 to 2.0, 2.5 to 3.0, etc..).
 
-
         Examples:
             >>> import narwhals as nw
             >>> import pandas as pd
@@ -2321,11 +2404,13 @@ class Expr:
         return self.__class__(lambda plx: self._call(plx).round(decimals))
 
     def len(self) -> Self:
-        r"""
-        Return the number of elements in the column.
+        r"""Return the number of elements in the column.
 
         Null values count towards the total.
 
+        Returns:
+            A new expression.
+
         Examples:
             >>> import narwhals as nw
             >>> import pandas as pd
@@ -2370,13 +2455,15 @@ class Expr:
         return self.__class__(lambda plx: self._call(plx).len())
 
     def gather_every(self: Self, n: int, offset: int = 0) -> Self:
-        r"""
-        Take every nth value in the Series and return as new Series.
+        r"""Take every nth value in the Series and return as new Series.
 
         Arguments:
             n: Gather every *n*-th row.
             offset: Starting index.
 
+        Returns:
+            A new expression.
+
         Examples:
             >>> import narwhals as nw
             >>> import pandas as pd
@@ -2427,13 +2514,15 @@ class Expr:
         lower_bound: Any | None = None,
         upper_bound: Any | None = None,
     ) -> Self:
-        r"""
-        Clip values in the Series.
+        r"""Clip values in the Series.
 
         Arguments:
             lower_bound: Lower bound value.
             upper_bound: Upper bound value.
 
+        Returns:
+            A new expression.
+
         Examples:
             >>> import pandas as pd
             >>> import polars as pl
@@ -2555,6 +2644,9 @@ class Expr:
 
         Can return multiple values.
 
+        Returns:
+            A new expression.
+
         Examples:
             >>> import pandas as pd
             >>> import polars as pl
@@ -2599,6 +2691,63 @@ class Expr:
         """
         return self.__class__(lambda plx: self._call(plx).mode())
 
+    def cum_count(self: Self, *, reverse: bool = False) -> Self:
+        r"""
+        Return the cumulative count of the non-null values in the column.
+
+        Arguments:
+            reverse: reverse the operation
+
+        Examples:
+            >>> import narwhals as nw
+            >>> import pandas as pd
+            >>> import polars as pl
+            >>> import pyarrow as pa
+            >>> data = {"a": ["x", "k", None, "d"]}
+
+            We define a library agnostic function:
+
+            >>> @nw.narwhalify
+            ... def func(df):
+            ...     return df.with_columns(
+            ...         nw.col("a").cum_count().alias("cum_count"),
+            ...         nw.col("a").cum_count(reverse=True).alias("cum_count_reverse"),
+            ...     )
+
+            We can then pass any supported library such as Pandas, Polars, or PyArrow to `func`:
+
+            >>> func(pd.DataFrame(data))
+                  a  cum_count  cum_count_reverse
+            0     x          1                  3
+            1     k          2                  2
+            2  None          2                  1
+            3     d          3                  1
+
+            >>> func(pl.DataFrame(data))
+            shape: (4, 3)
+            ┌──────┬───────────┬───────────────────┐
+            │ a    ┆ cum_count ┆ cum_count_reverse │
+            │ ---  ┆ ---       ┆ ---               │
+            │ str  ┆ u32       ┆ u32               │
+            ╞══════╪═══════════╪═══════════════════╡
+            │ x    ┆ 1         ┆ 3                 │
+            │ k    ┆ 2         ┆ 2                 │
+            │ null ┆ 2         ┆ 1                 │
+            │ d    ┆ 3         ┆ 1                 │
+            └──────┴───────────┴───────────────────┘
+
+            >>> func(pa.table(data))
+            pyarrow.Table
+            a: string
+            cum_count: uint32
+            cum_count_reverse: int64
+            ----
+            a: [["x","k",null,"d"]]
+            cum_count: [[1,2,2,3]]
+            cum_count_reverse: [[3,2,1,1]]
+        """
+        return self.__class__(lambda plx: self._call(plx).cum_count(reverse=reverse))
+
     @property
     def str(self: Self) -> ExprStringNamespace[Self]:
         return ExprStringNamespace(self)
@@ -2624,8 +2773,10 @@ class ExprCatNamespace(Generic[T]):
         self._expr = expr
 
     def get_categories(self: Self) -> T:
-        """
-        Get unique categories from column.
+        """Get unique categories from column.
+
+        Returns:
+            A new expression.
 
         Examples:
             Let's create some dataframes:
@@ -2671,8 +2822,10 @@ class ExprStringNamespace(Generic[T]):
         self._expr = expr
 
     def len_chars(self: Self) -> T:
-        r"""
-        Return the length of each string as the number of characters.
+        r"""Return the length of each string as the number of characters.
+
+        Returns:
+            A new expression.
 
         Examples:
             >>> import pandas as pd
@@ -2717,8 +2870,7 @@ class ExprStringNamespace(Generic[T]):
     def replace(
         self, pattern: str, value: str, *, literal: bool = False, n: int = 1
     ) -> T:
-        r"""
-        Replace first matching regex/literal substring with a new string value.
+        r"""Replace first matching regex/literal substring with a new string value.
 
         Arguments:
             pattern: A valid regular expression pattern.
@@ -2726,6 +2878,9 @@ class ExprStringNamespace(Generic[T]):
             literal: Treat `pattern` as a literal string.
             n: Number of matches to replace.
 
+        Returns:
+            A new expression.
+
         Examples:
             >>> import pandas as pd
             >>> import polars as pl
@@ -2757,14 +2912,16 @@ class ExprStringNamespace(Generic[T]):
         )
 
     def replace_all(self: Self, pattern: str, value: str, *, literal: bool = False) -> T:
-        r"""
-        Replace all matching regex/literal substring with a new string value.
+        r"""Replace all matching regex/literal substring with a new string value.
 
         Arguments:
             pattern: A valid regular expression pattern.
             value: String that will replace the matched substring.
             literal: Treat `pattern` as a literal string.
 
+        Returns:
+            A new expression.
+
         Examples:
             >>> import pandas as pd
             >>> import polars as pl
@@ -2796,12 +2953,14 @@ class ExprStringNamespace(Generic[T]):
         )
 
     def strip_chars(self: Self, characters: str | None = None) -> T:
-        r"""
-        Remove leading and trailing characters.
+        r"""Remove leading and trailing characters.
 
         Arguments:
             characters: The set of characters to be removed. All combinations of this set of characters will be stripped from the start and end of the string. If set to None (default), all leading and trailing whitespace is removed instead.
 
+        Returns:
+            A new expression.
+
         Examples:
             >>> import pandas as pd
             >>> import polars as pl
@@ -2830,12 +2989,14 @@ class ExprStringNamespace(Generic[T]):
         )
 
     def starts_with(self: Self, prefix: str) -> T:
-        r"""
-        Check if string values start with a substring.
+        r"""Check if string values start with a substring.
 
         Arguments:
             prefix: prefix substring
 
+        Returns:
+            A new expression.
+
         Examples:
             >>> import pandas as pd
             >>> import polars as pl
@@ -2875,12 +3036,14 @@ class ExprStringNamespace(Generic[T]):
         )
 
     def ends_with(self: Self, suffix: str) -> T:
-        r"""
-        Check if string values end with a substring.
+        r"""Check if string values end with a substring.
 
         Arguments:
             suffix: suffix substring
 
+        Returns:
+            A new expression.
+
         Examples:
             >>> import pandas as pd
             >>> import polars as pl
@@ -2920,14 +3083,16 @@ class ExprStringNamespace(Generic[T]):
         )
 
     def contains(self: Self, pattern: str, *, literal: bool = False) -> T:
-        r"""
-        Check if string contains a substring that matches a pattern.
+        r"""Check if string contains a substring that matches a pattern.
 
         Arguments:
             pattern: A Character sequence or valid regular expression pattern.
             literal: If True, treats the pattern as a literal string.
                      If False, assumes the pattern is a regular expression.
 
+        Returns:
+            A new expression.
+
         Examples:
             >>> import pandas as pd
             >>> import polars as pl
@@ -2976,14 +3141,16 @@ class ExprStringNamespace(Generic[T]):
         )
 
     def slice(self: Self, offset: int, length: int | None = None) -> T:
-        r"""
-        Create subslices of the string values of an expression.
+        r"""Create subslices of the string values of an expression.
 
         Arguments:
             offset: Start index. Negative indexing is supported.
             length: Length of the slice. If set to `None` (default), the slice is taken to the
                 end of the string.
 
+        Returns:
+            A new expression.
+
         Examples:
             >>> import pandas as pd
             >>> import polars as pl
@@ -3051,12 +3218,14 @@ class ExprStringNamespace(Generic[T]):
         )
 
     def head(self: Self, n: int = 5) -> T:
-        r"""
-        Take the first n elements of each string.
+        r"""Take the first n elements of each string.
 
         Arguments:
             n: Number of elements to take. Negative indexing is **not** supported.
 
+        Returns:
+            A new expression.
+
         Notes:
             If the length of the string has fewer than `n` characters, the full string is returned.
 
@@ -3099,12 +3268,14 @@ class ExprStringNamespace(Generic[T]):
         return self._expr.__class__(lambda plx: self._expr._call(plx).str.slice(0, n))
 
     def tail(self: Self, n: int = 5) -> T:
-        r"""
-        Take the last n elements of each string.
+        r"""Take the last n elements of each string.
 
         Arguments:
             n: Number of elements to take. Negative indexing is **not** supported.
 
+        Returns:
+            A new expression.
+
         Notes:
             If the length of the string has fewer than `n` characters, the full string is returned.
 
@@ -3147,14 +3318,7 @@ class ExprStringNamespace(Generic[T]):
         return self._expr.__class__(lambda plx: self._expr._call(plx).str.slice(-n))
 
     def to_datetime(self: Self, format: str | None = None) -> T:  # noqa: A002
-        """
-        Convert to Datetime dtype.
-
-        Notes:
-            pandas defaults to nanosecond time unit, Polars to microsecond.
-            Prior to pandas 2.0, nanoseconds were the only time unit supported
-            in pandas, with no ability to set any other one. The ability to
-            set the time unit in pandas, if the version permits, will arrive.
+        """Convert to Datetime dtype.
 
         Warning:
             As different backends auto-infer format in different ways, if `format=None`
@@ -3164,6 +3328,15 @@ class ExprStringNamespace(Generic[T]):
             format: Format to use for conversion. If set to None (default), the format is
                 inferred from the data.
 
+        Returns:
+            A new expression.
+
+        Notes:
+            pandas defaults to nanosecond time unit, Polars to microsecond.
+            Prior to pandas 2.0, nanoseconds were the only time unit supported
+            in pandas, with no ability to set any other one. The ability to
+            set the time unit in pandas, if the version permits, will arrive.
+
         Examples:
             >>> import pandas as pd
             >>> import polars as pl
@@ -3207,8 +3380,10 @@ class ExprStringNamespace(Generic[T]):
         )
 
     def to_uppercase(self: Self) -> T:
-        r"""
-        Transform string to uppercase variant.
+        r"""Transform string to uppercase variant.
+
+        Returns:
+            A new expression.
 
         Notes:
             The PyArrow backend will convert 'ß' to 'ẞ' instead of 'SS'.
@@ -3253,8 +3428,10 @@ class ExprStringNamespace(Generic[T]):
         return self._expr.__class__(lambda plx: self._expr._call(plx).str.to_uppercase())
 
     def to_lowercase(self: Self) -> T:
-        r"""
-        Transform string to lowercase variant.
+        r"""Transform string to lowercase variant.
+
+        Returns:
+            A new expression.
 
         Examples:
             >>> import pandas as pd
@@ -3298,8 +3475,10 @@ class ExprDateTimeNamespace(Generic[T]):
         self._expr = expr
 
     def date(self: Self) -> T:
-        """
-        Extract the date from underlying DateTime representation.
+        """Extract the date from underlying DateTime representation.
+
+        Returns:
+            A new expression.
 
         Raises:
             NotImplementedError: If pandas default backend is being used.
@@ -3340,11 +3519,13 @@ class ExprDateTimeNamespace(Generic[T]):
         return self._expr.__class__(lambda plx: self._expr._call(plx).dt.date())
 
     def year(self: Self) -> T:
-        """
-        Extract year from underlying DateTime representation.
+        """Extract year from underlying DateTime representation.
 
         Returns the year number in the calendar date.
 
+        Returns:
+            A new expression.
+
         Examples:
             >>> import pandas as pd
             >>> import polars as pl
@@ -3388,11 +3569,13 @@ class ExprDateTimeNamespace(Generic[T]):
         return self._expr.__class__(lambda plx: self._expr._call(plx).dt.year())
 
     def month(self: Self) -> T:
-        """
-        Extract month from underlying DateTime representation.
+        """Extract month from underlying DateTime representation.
 
         Returns the month number starting from 1. The return value ranges from 1 to 12.
 
+        Returns:
+            A new expression.
+
         Examples:
             >>> import pandas as pd
             >>> import polars as pl
@@ -3439,11 +3622,13 @@ class ExprDateTimeNamespace(Generic[T]):
         return self._expr.__class__(lambda plx: self._expr._call(plx).dt.month())
 
     def day(self: Self) -> T:
-        """
-        Extract day from underlying DateTime representation.
+        """Extract day from underlying DateTime representation.
 
         Returns the day of month starting from 1. The return value ranges from 1 to 31. (The last day of month differs by months.)
 
+        Returns:
+            A new expression.
+
         Examples:
             >>> import pandas as pd
             >>> import polars as pl
@@ -3491,11 +3676,13 @@ class ExprDateTimeNamespace(Generic[T]):
         return self._expr.__class__(lambda plx: self._expr._call(plx).dt.day())
 
     def hour(self: Self) -> T:
-        """
-        Extract hour from underlying DateTime representation.
+        """Extract hour from underlying DateTime representation.
 
         Returns the hour number from 0 to 23.
 
+        Returns:
+            A new expression.
+
         Examples:
             >>> import pandas as pd
             >>> import polars as pl
@@ -3539,11 +3726,13 @@ class ExprDateTimeNamespace(Generic[T]):
         return self._expr.__class__(lambda plx: self._expr._call(plx).dt.hour())
 
     def minute(self: Self) -> T:
-        """
-        Extract minutes from underlying DateTime representation.
+        """Extract minutes from underlying DateTime representation.
 
         Returns the minute number from 0 to 59.
 
+        Returns:
+            A new expression.
+
         Examples:
             >>> import pandas as pd
             >>> import polars as pl
@@ -3590,8 +3779,10 @@ class ExprDateTimeNamespace(Generic[T]):
         return self._expr.__class__(lambda plx: self._expr._call(plx).dt.minute())
 
     def second(self: Self) -> T:
-        """
-        Extract seconds from underlying DateTime representation.
+        """Extract seconds from underlying DateTime representation.
+
+        Returns:
+            A new expression.
 
         Examples:
             >>> import pandas as pd
@@ -3640,8 +3831,10 @@ class ExprDateTimeNamespace(Generic[T]):
         return self._expr.__class__(lambda plx: self._expr._call(plx).dt.second())
 
     def millisecond(self: Self) -> T:
-        """
-        Extract milliseconds from underlying DateTime representation.
+        """Extract milliseconds from underlying DateTime representation.
+
+        Returns:
+            A new expression.
 
         Examples:
             >>> import pandas as pd
@@ -3691,8 +3884,10 @@ class ExprDateTimeNamespace(Generic[T]):
         return self._expr.__class__(lambda plx: self._expr._call(plx).dt.millisecond())
 
     def microsecond(self: Self) -> T:
-        """
-        Extract microseconds from underlying DateTime representation.
+        """Extract microseconds from underlying DateTime representation.
+
+        Returns:
+            A new expression.
 
         Examples:
             >>> import pandas as pd
@@ -3742,8 +3937,10 @@ class ExprDateTimeNamespace(Generic[T]):
         return self._expr.__class__(lambda plx: self._expr._call(plx).dt.microsecond())
 
     def nanosecond(self: Self) -> T:
-        """
-        Extract Nanoseconds from underlying DateTime representation
+        """Extract Nanoseconds from underlying DateTime representation.
+
+        Returns:
+            A new expression.
 
         Examples:
             >>> import pandas as pd
@@ -3793,8 +3990,10 @@ class ExprDateTimeNamespace(Generic[T]):
         return self._expr.__class__(lambda plx: self._expr._call(plx).dt.nanosecond())
 
     def ordinal_day(self: Self) -> T:
-        """
-        Get ordinal day.
+        """Get ordinal day.
+
+        Returns:
+            A new expression.
 
         Examples:
             >>> import pandas as pd
@@ -3831,8 +4030,10 @@ class ExprDateTimeNamespace(Generic[T]):
         return self._expr.__class__(lambda plx: self._expr._call(plx).dt.ordinal_day())
 
     def total_minutes(self: Self) -> T:
-        """
-        Get total minutes.
+        """Get total minutes.
+
+        Returns:
+            A new expression.
 
         Notes:
             The function outputs the total minutes in the int dtype by default,
@@ -3874,8 +4075,10 @@ class ExprDateTimeNamespace(Generic[T]):
         return self._expr.__class__(lambda plx: self._expr._call(plx).dt.total_minutes())
 
     def total_seconds(self: Self) -> T:
-        """
-        Get total seconds.
+        """Get total seconds.
+
+        Returns:
+            A new expression.
 
         Notes:
             The function outputs the total seconds in the int dtype by default,
@@ -3917,8 +4120,10 @@ class ExprDateTimeNamespace(Generic[T]):
         return self._expr.__class__(lambda plx: self._expr._call(plx).dt.total_seconds())
 
     def total_milliseconds(self: Self) -> T:
-        """
-        Get total milliseconds.
+        """Get total milliseconds.
+
+        Returns:
+            A new expression.
 
         Notes:
             The function outputs the total milliseconds in the int dtype by default,
@@ -3969,8 +4174,10 @@ class ExprDateTimeNamespace(Generic[T]):
         )
 
     def total_microseconds(self: Self) -> T:
-        """
-        Get total microseconds.
+        """Get total microseconds.
+
+        Returns:
+            A new expression.
 
         Notes:
             The function outputs the total microseconds in the int dtype by default,
@@ -4021,8 +4228,10 @@ class ExprDateTimeNamespace(Generic[T]):
         )
 
     def total_nanoseconds(self: Self) -> T:
-        """
-        Get total nanoseconds.
+        """Get total nanoseconds.
+
+        Returns:
+            A new expression.
 
         Notes:
             The function outputs the total nanoseconds in the int dtype by default,
@@ -4070,8 +4279,13 @@ class ExprDateTimeNamespace(Generic[T]):
         )
 
     def to_string(self: Self, format: str) -> T:  # noqa: A002
-        """
-        Convert a Date/Time/Datetime column into a String column with the given format.
+        """Convert a Date/Time/Datetime column into a String column with the given format.
+
+        Arguments:
+            format: Format to format temporal column with.
+
+        Returns:
+            A new expression.
 
         Notes:
             Unfortunately, different libraries interpret format directives a bit
@@ -4148,12 +4362,14 @@ class ExprDateTimeNamespace(Generic[T]):
         )
 
     def replace_time_zone(self: Self, time_zone: str | None) -> T:
-        """
-        Replace time zone.
+        """Replace time zone.
 
         Arguments:
             time_zone: Target time zone.
 
+        Returns:
+            A new expression.
+
         Examples:
             >>> from datetime import datetime, timezone
             >>> import narwhals as nw
@@ -4203,8 +4419,7 @@ class ExprDateTimeNamespace(Generic[T]):
         )
 
     def convert_time_zone(self: Self, time_zone: str) -> T:
-        """
-        Convert to a new time zone.
+        """Convert to a new time zone.
 
         If converting from a time-zone-naive column, then conversion happens
         as if converting from UTC.
@@ -4212,6 +4427,9 @@ class ExprDateTimeNamespace(Generic[T]):
         Arguments:
             time_zone: Target time zone.
 
+        Returns:
+            A new expression.
+
         Examples:
             >>> from datetime import datetime, timezone
             >>> import narwhals as nw
@@ -4264,13 +4482,15 @@ class ExprDateTimeNamespace(Generic[T]):
         )
 
     def timestamp(self: Self, time_unit: Literal["ns", "us", "ms"] = "us") -> T:
-        """
-        Return a timestamp in the given time unit.
+        """Return a timestamp in the given time unit.
 
         Arguments:
             time_unit: {'ns', 'us', 'ms'}
                 Time unit.
 
+        Returns:
+            A new expression.
+
         Examples:
             >>> from datetime import date
             >>> import narwhals as nw
@@ -4335,8 +4555,10 @@ class ExprNameNamespace(Generic[T]):
         self._expr = expr
 
     def keep(self: Self) -> T:
-        r"""
-        Keep the original root name of the expression.
+        r"""Keep the original root name of the expression.
+
+        Returns:
+            A new expression.
 
         Notes:
             This will undo any previous renaming operations on the expression.
@@ -4367,12 +4589,14 @@ class ExprNameNamespace(Generic[T]):
         return self._expr.__class__(lambda plx: self._expr._call(plx).name.keep())
 
     def map(self: Self, function: Callable[[str], str]) -> T:
-        r"""
-        Rename the output of an expression by mapping a function over the root name.
+        r"""Rename the output of an expression by mapping a function over the root name.
 
         Arguments:
             function: Function that maps a root name to a new name.
 
+        Returns:
+            A new expression.
+
         Notes:
             This will undo any previous renaming operations on the expression.
             Due to implementation constraints, this method can only be called as the last
@@ -4403,12 +4627,14 @@ class ExprNameNamespace(Generic[T]):
         return self._expr.__class__(lambda plx: self._expr._call(plx).name.map(function))
 
     def prefix(self: Self, prefix: str) -> T:
-        r"""
-        Add a prefix to the root column name of the expression.
+        r"""Add a prefix to the root column name of the expression.
 
         Arguments:
             prefix: Prefix to add to the root column name.
 
+        Returns:
+            A new expression.
+
         Notes:
             This will undo any previous renaming operations on the expression.
             Due to implementation constraints, this method can only be called as the last
@@ -4439,12 +4665,14 @@ class ExprNameNamespace(Generic[T]):
         return self._expr.__class__(lambda plx: self._expr._call(plx).name.prefix(prefix))
 
     def suffix(self: Self, suffix: str) -> T:
-        r"""
-        Add a suffix to the root column name of the expression.
+        r"""Add a suffix to the root column name of the expression.
 
         Arguments:
             suffix: Suffix to add to the root column name.
 
+        Returns:
+            A new expression.
+
         Notes:
             This will undo any previous renaming operations on the expression.
             Due to implementation constraints, this method can only be called as the last
@@ -4474,8 +4702,10 @@ class ExprNameNamespace(Generic[T]):
         return self._expr.__class__(lambda plx: self._expr._call(plx).name.suffix(suffix))
 
     def to_lowercase(self: Self) -> T:
-        r"""
-        Make the root column name lowercase.
+        r"""Make the root column name lowercase.
+
+        Returns:
+            A new expression.
 
         Notes:
             This will undo any previous renaming operations on the expression.
@@ -4506,8 +4736,10 @@ class ExprNameNamespace(Generic[T]):
         return self._expr.__class__(lambda plx: self._expr._call(plx).name.to_lowercase())
 
     def to_uppercase(self: Self) -> T:
-        r"""
-        Make the root column name uppercase.
+        r"""Make the root column name uppercase.
+
+        Returns:
+            A new expression.
 
         Notes:
             This will undo any previous renaming operations on the expression.
@@ -4538,12 +4770,14 @@ class ExprNameNamespace(Generic[T]):
 
 
 def col(*names: str | Iterable[str]) -> Expr:
-    """
-    Creates an expression that references one or more columns by their name(s).
+    """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.
 
+    Returns:
+        A new expression.
+
     Examples:
         >>> import pandas as pd
         >>> import polars as pl
@@ -4589,8 +4823,7 @@ def col(*names: str | Iterable[str]) -> Expr:
 
 
 def nth(*indices: int | Sequence[int]) -> Expr:
-    """
-    Creates an expression that references one or more columns by their index(es).
+    """Creates an expression that references one or more columns by their index(es).
 
     Notes:
         `nth` is not supported for Polars version<1.0.0. Please use [`col`](/api-reference/narwhals/#narwhals.col) instead.
@@ -4598,6 +4831,9 @@ def nth(*indices: int | Sequence[int]) -> Expr:
     Arguments:
         indices: One or more indices representing the columns to retrieve.
 
+    Returns:
+        A new expression.
+
     Examples:
         >>> import pandas as pd
         >>> import polars as pl
@@ -4645,8 +4881,10 @@ def nth(*indices: int | Sequence[int]) -> Expr:
 
 # Add underscore so it doesn't conflict with builtin `all`
 def all_() -> Expr:
-    """
-    Instantiate an expression representing all columns.
+    """Instantiate an expression representing all columns.
+
+    Returns:
+        A new expression.
 
     Examples:
         >>> import polars as pl
@@ -4694,8 +4932,10 @@ def all_() -> Expr:
 
 # Add underscore so it doesn't conflict with builtin `len`
 def len_() -> Expr:
-    """
-    Return the number of rows.
+    """Return the number of rows.
+
+    Returns:
+        A new expression.
 
     Examples:
         >>> import polars as pl
@@ -4740,8 +4980,7 @@ def len_() -> Expr:
 
 
 def sum(*columns: str) -> Expr:
-    """
-    Sum all values.
+    """Sum all values.
 
     Note:
         Syntactic sugar for ``nw.col(columns).sum()``
@@ -4749,6 +4988,9 @@ def sum(*columns: str) -> Expr:
     Arguments:
         columns: Name(s) of the columns to use in the aggregation function
 
+    Returns:
+        A new expression.
+
     Examples:
         >>> import pandas as pd
         >>> import polars as pl
@@ -4784,13 +5026,11 @@ def sum(*columns: str) -> Expr:
         ----
         a: [[3]]
     """
-
     return Expr(lambda plx: plx.sum(*columns))
 
 
 def mean(*columns: str) -> Expr:
-    """
-    Get the mean value.
+    """Get the mean value.
 
     Note:
         Syntactic sugar for ``nw.col(columns).mean()``
@@ -4798,6 +5038,9 @@ def mean(*columns: str) -> Expr:
     Arguments:
         columns: Name(s) of the columns to use in the aggregation function
 
+    Returns:
+        A new expression.
+
     Examples:
         >>> import pandas as pd
         >>> import polars as pl
@@ -4833,13 +5076,11 @@ def mean(*columns: str) -> Expr:
         ----
         a: [[4]]
     """
-
     return Expr(lambda plx: plx.mean(*columns))
 
 
 def median(*columns: str) -> Expr:
-    """
-    Get the median value.
+    """Get the median value.
 
     Notes:
         - Syntactic sugar for ``nw.col(columns).median()``
@@ -4848,6 +5089,9 @@ def median(*columns: str) -> Expr:
     Arguments:
         columns: Name(s) of the columns to use in the aggregation function
 
+    Returns:
+        A new expression.
+
     Examples:
         >>> import pandas as pd
         >>> import polars as pl
@@ -4883,13 +5127,11 @@ def median(*columns: str) -> Expr:
         ----
         a: [[4]]
     """
-
     return Expr(lambda plx: plx.median(*columns))
 
 
 def min(*columns: str) -> Expr:
-    """
-    Return the minimum value.
+    """Return the minimum value.
 
     Note:
        Syntactic sugar for ``nw.col(columns).min()``.
@@ -4897,6 +5139,9 @@ def min(*columns: str) -> Expr:
     Arguments:
         columns: Name(s) of the columns to use in the aggregation function.
 
+    Returns:
+        A new expression.
+
     Examples:
         >>> import polars as pl
         >>> import pandas as pd
@@ -4936,8 +5181,7 @@ def min(*columns: str) -> Expr:
 
 
 def max(*columns: str) -> Expr:
-    """
-    Return the maximum value.
+    """Return the maximum value.
 
     Note:
        Syntactic sugar for ``nw.col(columns).max()``.
@@ -4945,6 +5189,9 @@ def max(*columns: str) -> Expr:
     Arguments:
         columns: Name(s) of the columns to use in the aggregation function.
 
+    Returns:
+        A new expression.
+
     Examples:
         >>> import polars as pl
         >>> import pandas as pd
@@ -4984,8 +5231,7 @@ def max(*columns: str) -> Expr:
 
 
 def sum_horizontal(*exprs: IntoExpr | Iterable[IntoExpr]) -> Expr:
-    """
-    Sum all values horizontally across columns.
+    """Sum all values horizontally across columns.
 
     Warning:
         Unlike Polars, we support horizontal sum over numeric columns only.
@@ -4994,6 +5240,9 @@ def sum_horizontal(*exprs: IntoExpr | Iterable[IntoExpr]) -> Expr:
         exprs: Name(s) of the columns to use in the aggregation function. Accepts
             expression input.
 
+    Returns:
+        A new expression.
+
     Examples:
         >>> import pandas as pd
         >>> import polars as pl
@@ -5045,8 +5294,7 @@ def sum_horizontal(*exprs: IntoExpr | Iterable[IntoExpr]) -> Expr:
 
 
 def min_horizontal(*exprs: IntoExpr | Iterable[IntoExpr]) -> Expr:
-    """
-    Get the minimum value horizontally across columns.
+    """Get the minimum value horizontally across columns.
 
     Notes:
         We support `min_horizontal` over numeric columns only.
@@ -5055,6 +5303,9 @@ def min_horizontal(*exprs: IntoExpr | Iterable[IntoExpr]) -> Expr:
         exprs: Name(s) of the columns to use in the aggregation function. Accepts
             expression input.
 
+    Returns:
+        A new expression.
+
     Examples:
         >>> import narwhals as nw
         >>> import pandas as pd
@@ -5108,8 +5359,7 @@ def min_horizontal(*exprs: IntoExpr | Iterable[IntoExpr]) -> Expr:
 
 
 def max_horizontal(*exprs: IntoExpr | Iterable[IntoExpr]) -> Expr:
-    """
-    Get the maximum value horizontally across columns.
+    """Get the maximum value horizontally across columns.
 
     Notes:
         We support `max_horizontal` over numeric columns only.
@@ -5118,6 +5368,9 @@ def max_horizontal(*exprs: IntoExpr | Iterable[IntoExpr]) -> Expr:
         exprs: Name(s) of the columns to use in the aggregation function. Accepts
             expression input.
 
+    Returns:
+        A new expression.
+
     Examples:
         >>> import narwhals as nw
         >>> import pandas as pd
@@ -5194,15 +5447,25 @@ class Then(Expr):
 
 
 def when(*predicates: IntoExpr | Iterable[IntoExpr]) -> When:
-    """
-    Start a `when-then-otherwise` expression.
-
-    Expression similar to an `if-else` statement in Python. Always initiated by a `pl.when(<condition>).then(<value if condition>)`., and optionally followed by chaining one or more `.when(<condition>).then(<value>)` statements.
-    Chained when-then operations should be read as Python `if, elif, ... elif` blocks, not as `if, if, ... if`, i.e. the first condition that evaluates to `True` will be picked.
-    If none of the conditions are `True`, an optional `.otherwise(<value if all statements are false>)` can be appended at the end. If not appended, and none of the conditions are `True`, `None` will be returned.
+    """Start a `when-then-otherwise` expression.
+
+    Expression similar to an `if-else` statement in Python. Always initiated by a
+    `pl.when(<condition>).then(<value if condition>)`, and optionally followed by
+    chaining one or more `.when(<condition>).then(<value>)` statements.
+    Chained when-then operations should be read as Python `if, elif, ... elif`
+    blocks, not as `if, if, ... if`, i.e. the first condition that evaluates to
+    `True` will be picked.
+    If none of the conditions are `True`, an optional
+    `.otherwise(<value if all statements are false>)` can be appended at the end.
+    If not appended, and none of the conditions are `True`, `None` will be returned.
 
     Arguments:
-        predicates: Condition(s) that must be met in order to apply the subsequent statement. Accepts one or more boolean expressions, which are implicitly combined with `&`. String input is parsed as a column name.
+        predicates: Condition(s) that must be met in order to apply the subsequent statement.
+            Accepts one or more boolean expressions, which are implicitly combined with `&`.
+            String input is parsed as a column name.
+
+    Returns:
+        A "when" object, which `.then` can be called on.
 
     Examples:
         >>> import pandas as pd
@@ -5253,12 +5516,14 @@ def when(*predicates: IntoExpr | Iterable[IntoExpr]) -> When:
 
 
 def all_horizontal(*exprs: IntoExpr | Iterable[IntoExpr]) -> Expr:
-    r"""
-    Compute the bitwise AND horizontally across columns.
+    r"""Compute the bitwise AND horizontally across columns.
 
     Arguments:
         exprs: Name(s) of the columns to use in the aggregation function. Accepts expression input.
 
+    Returns:
+        A new expression.
+
     Notes:
         pandas and Polars handle null values differently.
 
@@ -5328,13 +5593,15 @@ def all_horizontal(*exprs: IntoExpr | Iterable[IntoExpr]) -> Expr:
 
 
 def lit(value: Any, dtype: DType | None = None) -> Expr:
-    """
-    Return an expression representing a literal value.
+    """Return an expression representing a literal value.
 
     Arguments:
         value: The value to use as literal.
         dtype: The data type of the literal value. If not provided, the data type will be inferred.
 
+    Returns:
+        A new expression.
+
     Examples:
         >>> import pandas as pd
         >>> import polars as pl
@@ -5389,12 +5656,14 @@ def lit(value: Any, dtype: DType | None = None) -> Expr:
 
 
 def any_horizontal(*exprs: IntoExpr | Iterable[IntoExpr]) -> Expr:
-    r"""
-    Compute the bitwise OR horizontally across columns.
+    r"""Compute the bitwise OR horizontally across columns.
 
     Arguments:
         exprs: Name(s) of the columns to use in the aggregation function. Accepts expression input.
 
+    Returns:
+        A new expression.
+
     Notes:
         pandas and Polars handle null values differently.
 
@@ -5464,13 +5733,15 @@ def any_horizontal(*exprs: IntoExpr | Iterable[IntoExpr]) -> Expr:
 
 
 def mean_horizontal(*exprs: IntoExpr | Iterable[IntoExpr]) -> Expr:
-    """
-    Compute the mean of all values horizontally across columns.
+    """Compute the mean of all values horizontally across columns.
 
     Arguments:
         exprs: Name(s) of the columns to use in the aggregation function. Accepts
             expression input.
 
+    Returns:
+        A new expression.
+
     Examples:
         >>> import pandas as pd
         >>> import polars as pl
@@ -5534,8 +5805,7 @@ def concat_str(
     separator: str = "",
     ignore_nulls: bool = False,
 ) -> Expr:
-    r"""
-    Horizontally concatenate columns into a single string column.
+    r"""Horizontally concatenate columns into a single string column.
 
     Arguments:
         exprs: Columns to concatenate into a single string column. Accepts expression
@@ -5548,6 +5818,9 @@ def concat_str(
             If set to `False`, null values will be propagated and if the row contains any
             null values, the output is null.
 
+    Returns:
+        A new expression.
+
     Examples:
         >>> import narwhals as nw
         >>> import pandas as pd
diff --git a/narwhals/functions.py b/narwhals/functions.py
index 9d7d0553..bfc7c759 100644
--- a/narwhals/functions.py
+++ b/narwhals/functions.py
@@ -42,8 +42,7 @@ def concat(
     *,
     how: Literal["horizontal", "vertical"] = "vertical",
 ) -> FrameT:
-    """
-    Concatenate multiple DataFrames, LazyFrames into a single entity.
+    """Concatenate multiple DataFrames, LazyFrames into a single entity.
 
     Arguments:
         items: DataFrames, LazyFrames to concatenate.
@@ -61,7 +60,6 @@ def concat(
         NotImplementedError: The items to concatenate should either all be eager, or all lazy
 
     Examples:
-
         Let's take an example of vertical concatenation:
 
         >>> import pandas as pd
@@ -140,7 +138,6 @@ def concat(
         └─────┴─────┴──────┴──────┘
 
     """
-
     if how not in ("horizontal", "vertical"):  # pragma: no cover
         msg = "Only horizontal and vertical concatenations are supported"
         raise NotImplementedError(msg)
@@ -163,8 +160,7 @@ def new_series(
     *,
     native_namespace: ModuleType,
 ) -> Series:
-    """
-    Instantiate Narwhals Series from iterable (e.g. list or array).
+    """Instantiate Narwhals Series from iterable (e.g. list or array).
 
     Arguments:
         name: Name of resulting Series.
@@ -288,8 +284,7 @@ def from_dict(
     *,
     native_namespace: ModuleType | None = None,
 ) -> DataFrame[Any]:
-    """
-    Instantiate DataFrame from dictionary.
+    """Instantiate DataFrame from dictionary.
 
     Notes:
         For pandas-like dataframes, conversion to schema is applied after dataframe
@@ -439,8 +434,7 @@ def _from_dict_impl(
 def from_arrow(
     native_frame: ArrowStreamExportable, *, native_namespace: ModuleType
 ) -> DataFrame[Any]:
-    """
-    Construct a DataFrame from an object which supports the PyCapsule Interface.
+    """Construct a DataFrame from an object which supports the PyCapsule Interface.
 
     Arguments:
         native_frame: Object which implements `__arrow_c_stream__`.
@@ -534,7 +528,7 @@ def from_arrow(
 
 
 def _get_sys_info() -> dict[str, str]:
-    """System information
+    """System information.
 
     Returns system and Python version information
 
@@ -553,7 +547,7 @@ def _get_sys_info() -> dict[str, str]:
 
 
 def _get_deps_info() -> dict[str, str]:
-    """Overview of the installed version of main dependencies
+    """Overview of the installed version of main dependencies.
 
     This function does not import the modules to collect the version numbers
     but instead relies on standard Python package metadata.
@@ -590,15 +584,12 @@ def _get_deps_info() -> dict[str, str]:
 
 
 def show_versions() -> None:
-    """
-    Print useful debugging information
+    """Print useful debugging information.
 
     Examples:
-
         >>> from narwhals import show_versions
         >>> show_versions()  # doctest: +SKIP
     """
-
     sys_info = _get_sys_info()
     deps_info = _get_deps_info()
 
@@ -614,8 +605,7 @@ def show_versions() -> None:
 def get_level(
     obj: DataFrame[Any] | LazyFrame[Any] | Series,
 ) -> Literal["full", "interchange"]:
-    """
-    Level of support Narwhals has for current object.
+    """Level of support Narwhals has for current object.
 
     This can be one of:
 
diff --git a/narwhals/group_by.py b/narwhals/group_by.py
index 9ec14c4d..82054da6 100644
--- a/narwhals/group_by.py
+++ b/narwhals/group_by.py
@@ -30,8 +30,7 @@ class GroupBy(Generic[DataFrameT]):
     def agg(
         self, *aggs: IntoExpr | Iterable[IntoExpr], **named_aggs: IntoExpr
     ) -> DataFrameT:
-        """
-        Compute aggregations for each group of a group by operation.
+        """Compute aggregations for each group of a group by operation.
 
         Arguments:
             aggs: Aggregations to compute for each group of the group by operation,
diff --git a/narwhals/schema.py b/narwhals/schema.py
index 9eb8e381..0d760c8a 100644
--- a/narwhals/schema.py
+++ b/narwhals/schema.py
@@ -1,6 +1,7 @@
-"""
+"""Schema.
+
 Adapted from Polars implementation at:
-https://github.com/pola-rs/polars/blob/main/py-polars/polars/schema.py
+https://github.com/pola-rs/polars/blob/main/py-polars/polars/schema.py.
 """
 
 from __future__ import annotations
@@ -22,8 +23,7 @@ __all__ = ["Schema"]
 
 
 class Schema(BaseSchema):
-    """
-    Ordered mapping of column names to their data type.
+    """Ordered mapping of column names to their data type.
 
     Arguments:
         schema: Mapping[str, DType] | Iterable[tuple[str, DType]] | None
diff --git a/narwhals/selectors.py b/narwhals/selectors.py
index 7c06a79c..b9c484a3 100644
--- a/narwhals/selectors.py
+++ b/narwhals/selectors.py
@@ -10,8 +10,7 @@ class Selector(Expr): ...
 
 
 def by_dtype(*dtypes: Any) -> Expr:
-    """
-    Select columns based on their dtype.
+    """Select columns based on their dtype.
 
     Arguments:
         dtypes: one or data types to select
@@ -54,8 +53,7 @@ def by_dtype(*dtypes: Any) -> Expr:
 
 
 def numeric() -> Expr:
-    """
-    Select numeric columns.
+    """Select numeric columns.
 
     Examples:
         >>> import narwhals as nw
@@ -95,8 +93,7 @@ def numeric() -> Expr:
 
 
 def boolean() -> Expr:
-    """
-    Select boolean columns.
+    """Select boolean columns.
 
     Examples:
         >>> import narwhals as nw
@@ -136,8 +133,7 @@ def boolean() -> Expr:
 
 
 def string() -> Expr:
-    """
-    Select string columns.
+    """Select string columns.
 
     Examples:
         >>> import narwhals as nw
@@ -177,8 +173,7 @@ def string() -> Expr:
 
 
 def categorical() -> Expr:
-    """
-    Select categorical columns.
+    """Select categorical columns.
 
     Examples:
         >>> import narwhals as nw
@@ -218,8 +213,7 @@ def categorical() -> Expr:
 
 
 def all() -> Expr:
-    """
-    Select all columns.
+    """Select all columns.
 
     Examples:
         >>> import narwhals as nw
diff --git a/narwhals/series.py b/narwhals/series.py
index 0b5dfcc1..360db996 100644
--- a/narwhals/series.py
+++ b/narwhals/series.py
@@ -26,8 +26,7 @@ if TYPE_CHECKING:
 
 
 class Series:
-    """
-    Narwhals Series, backed by a native series.
+    """Narwhals Series, backed by a native series.
 
     The native series might be pandas.Series, polars.Series, ...
 
@@ -73,8 +72,7 @@ class Series:
         return self._compliant_series.__native_namespace__()  # type: ignore[no-any-return]
 
     def __arrow_c_stream__(self, requested_schema: object | None = None) -> object:
-        """
-        Export a Series via the Arrow PyCapsule Interface.
+        """Export a Series via the Arrow PyCapsule Interface.
 
         Narwhals doesn't implement anything itself here:
 
@@ -99,8 +97,7 @@ class Series:
         return ca.__arrow_c_stream__(requested_schema=requested_schema)
 
     def to_native(self) -> Any:
-        """
-        Convert Narwhals series to native series.
+        """Convert Narwhals series to native series.
 
         Returns:
             Series of class that user started with.
@@ -138,8 +135,7 @@ class Series:
         return self._compliant_series._native_series
 
     def scatter(self, indices: int | Sequence[int], values: Any) -> Self:
-        """
-        Set value(s) at given position(s).
+        """Set value(s) at given position(s).
 
         Arguments:
             indices: Position(s) to set items at.
@@ -205,8 +201,7 @@ class Series:
 
     @property
     def shape(self) -> tuple[int]:
-        """
-        Get the shape of the Series.
+        """Get the shape of the Series.
 
         Examples:
             >>> import pandas as pd
@@ -245,8 +240,7 @@ class Series:
         )
 
     def pipe(self, function: Callable[[Any], Self], *args: Any, **kwargs: Any) -> Self:
-        """
-        Pipe function call.
+        """Pipe function call.
 
         Examples:
             >>> import polars as pl
@@ -300,8 +294,7 @@ class Series:
         return len(self._compliant_series)
 
     def len(self) -> int:
-        r"""
-        Return the number of elements in the Series.
+        r"""Return the number of elements in the Series.
 
         Null values count towards the total.
 
@@ -330,8 +323,7 @@ class Series:
 
     @property
     def dtype(self: Self) -> DType:
-        """
-        Get the data type of the Series.
+        """Get the data type of the Series.
 
         Examples:
             >>> import pandas as pd
@@ -358,8 +350,7 @@ class Series:
 
     @property
     def name(self) -> str:
-        """
-        Get the name of the Series.
+        """Get the name of the Series.
 
         Examples:
             >>> import pandas as pd
@@ -385,8 +376,7 @@ class Series:
         return self._compliant_series.name  # type: ignore[no-any-return]
 
     def cast(self: Self, dtype: DType | type[DType]) -> Self:
-        """
-        Cast between data types.
+        """Cast between data types.
 
         Arguments:
             dtype: Data type that the object will be cast into.
@@ -424,8 +414,7 @@ class Series:
         return self._from_compliant_series(self._compliant_series.cast(dtype))
 
     def to_frame(self) -> DataFrame[Any]:
-        """
-        Convert to dataframe.
+        """Convert to dataframe.
 
         Examples:
             >>> import pandas as pd
@@ -466,8 +455,7 @@ class Series:
         )
 
     def to_list(self) -> list[Any]:
-        """
-        Convert to list.
+        """Convert to list.
 
         Notes:
             This function converts to Python scalars. It's typically
@@ -499,8 +487,7 @@ class Series:
         return self._compliant_series.to_list()  # type: ignore[no-any-return]
 
     def mean(self) -> Any:
-        """
-        Reduce this Series to the mean value.
+        """Reduce this Series to the mean value.
 
         Examples:
             >>> import pandas as pd
@@ -526,8 +513,7 @@ class Series:
         return self._compliant_series.mean()
 
     def median(self) -> Any:
-        """
-        Reduce this Series to the median value.
+        """Reduce this Series to the median value.
 
         Notes:
             Results might slightly differ across backends due to differences in the underlying algorithms used to compute the median.
@@ -560,8 +546,7 @@ class Series:
         return self._compliant_series.median()
 
     def count(self) -> Any:
-        """
-        Returns the number of non-null elements in the Series.
+        """Returns the number of non-null elements in the Series.
 
         Examples:
             >>> import pandas as pd
@@ -588,8 +573,7 @@ class Series:
         return self._compliant_series.count()
 
     def any(self) -> Any:
-        """
-        Return whether any of the values in the Series are True.
+        """Return whether any of the values in the Series are True.
 
         Notes:
           Only works on Series of data type Boolean.
@@ -618,8 +602,7 @@ class Series:
         return self._compliant_series.any()
 
     def all(self) -> Any:
-        """
-        Return whether all values in the Series are True.
+        """Return whether all values in the Series are True.
 
         Examples:
             >>> import pandas as pd
@@ -646,8 +629,7 @@ class Series:
         return self._compliant_series.all()
 
     def min(self) -> Any:
-        """
-        Get the minimal value in this Series.
+        """Get the minimal value in this Series.
 
         Examples:
             >>> import pandas as pd
@@ -673,8 +655,7 @@ class Series:
         return self._compliant_series.min()
 
     def max(self) -> Any:
-        """
-        Get the maximum value in this Series.
+        """Get the maximum value in this Series.
 
         Examples:
             >>> import pandas as pd
@@ -700,8 +681,7 @@ class Series:
         return self._compliant_series.max()
 
     def sum(self) -> Any:
-        """
-        Reduce this Series to the sum value.
+        """Reduce this Series to the sum value.
 
         Examples:
             >>> import pandas as pd
@@ -727,8 +707,7 @@ class Series:
         return self._compliant_series.sum()
 
     def std(self, *, ddof: int = 1) -> Any:
-        """
-        Get the standard deviation of this Series.
+        """Get the standard deviation of this Series.
 
         Arguments:
             ddof: “Delta Degrees of Freedom”: the divisor used in the calculation is N - ddof,
@@ -760,8 +739,7 @@ class Series:
     def clip(
         self, lower_bound: Any | None = None, upper_bound: Any | None = None
     ) -> Self:
-        r"""
-        Clip values in the Series.
+        r"""Clip values in the Series.
 
         Arguments:
             lower_bound: Lower bound value.
@@ -859,8 +837,7 @@ class Series:
         )
 
     def is_in(self, other: Any) -> Self:
-        """
-        Check if the elements of this Series are in the other sequence.
+        """Check if the elements of this Series are in the other sequence.
 
         Arguments:
             other: Sequence of primitive type.
@@ -899,8 +876,7 @@ class Series:
         )
 
     def arg_true(self) -> Self:
-        """
-        Find elements where boolean Series is True.
+        """Find elements where boolean Series is True.
 
         Examples:
             >>> import pandas as pd
@@ -933,8 +909,7 @@ class Series:
         return self._from_compliant_series(self._compliant_series.arg_true())
 
     def drop_nulls(self) -> Self:
-        """
-        Drop all null values.
+        """Drop all null values.
 
         Notes:
           pandas and Polars handle null values differently. Polars distinguishes
@@ -975,8 +950,7 @@ class Series:
         return self._from_compliant_series(self._compliant_series.drop_nulls())
 
     def abs(self) -> Self:
-        """
-        Calculate the absolute value of each element.
+        """Calculate the absolute value of each element.
 
         Examples:
             >>> import pandas as pd
@@ -1011,8 +985,7 @@ class Series:
         return self._from_compliant_series(self._compliant_series.abs())
 
     def cum_sum(self) -> Self:
-        """
-        Calculate the cumulative sum.
+        """Calculate the cumulative sum.
 
         Examples:
             >>> import pandas as pd
@@ -1047,8 +1020,7 @@ class Series:
         return self._from_compliant_series(self._compliant_series.cum_sum())
 
     def unique(self, *, maintain_order: bool = False) -> Self:
-        """
-        Returns unique values of the series.
+        """Returns unique values of the series.
 
         Arguments:
             maintain_order: Keep the same order as the original series. This may be more
@@ -1090,8 +1062,7 @@ class Series:
         )
 
     def diff(self) -> Self:
-        """
-        Calculate the difference with the previous element, for each element.
+        """Calculate the difference with the previous element, for each element.
 
         Notes:
             pandas may change the dtype here, for example when introducing missing
@@ -1135,8 +1106,7 @@ class Series:
         return self._from_compliant_series(self._compliant_series.diff())
 
     def shift(self, n: int) -> Self:
-        """
-        Shift values by `n` positions.
+        """Shift values by `n` positions.
 
         Arguments:
             n: Number of indices to shift forward. If a negative value is passed,
@@ -1191,8 +1161,7 @@ class Series:
         with_replacement: bool = False,
         seed: int | None = None,
     ) -> Self:
-        """
-        Sample randomly from this Series.
+        """Sample randomly from this Series.
 
         Arguments:
             n: Number of items to return. Cannot be used with fraction.
@@ -1245,8 +1214,7 @@ class Series:
         )
 
     def alias(self, name: str) -> Self:
-        """
-        Rename the Series.
+        """Rename the Series.
 
         Notes:
             This method is very cheap, but does not guarantee that data
@@ -1314,8 +1282,7 @@ class Series:
         return self._from_compliant_series(self._compliant_series.alias(name=name))
 
     def rename(self, name: str) -> Self:
-        """
-        Rename the Series.
+        """Rename the Series.
 
         Alias for `Series.alias()`.
 
@@ -1391,8 +1358,7 @@ class Series:
         *,
         return_dtype: DType | type[DType] | None = None,
     ) -> Self:
-        """
-        Replace all values by different values.
+        """Replace all values by different values.
 
         This function must replace all non-null input values (else it raises an error).
 
@@ -1463,8 +1429,7 @@ class Series:
         )
 
     def sort(self, *, descending: bool = False, nulls_last: bool = False) -> Self:
-        """
-        Sort this Series. Place null values first.
+        """Sort this Series. Place null values first.
 
         Arguments:
             descending: Sort in descending order.
@@ -1526,8 +1491,7 @@ class Series:
         )
 
     def is_null(self) -> Self:
-        """
-        Returns a boolean Series indicating which values are null.
+        """Returns a boolean Series indicating which values are null.
 
         Notes:
             pandas and Polars handle null values differently. Polars distinguishes
@@ -1571,8 +1535,7 @@ class Series:
         strategy: Literal["forward", "backward"] | None = None,
         limit: int | None = None,
     ) -> Self:
-        """
-        Fill null values using the specified value.
+        """Fill null values using the specified value.
 
         Arguments:
             value: Value used to fill null values.
@@ -1652,8 +1615,7 @@ class Series:
     def is_between(
         self, lower_bound: Any, upper_bound: Any, closed: str = "both"
     ) -> Self:
-        """
-        Get a boolean mask of the values that are between the given lower/upper bounds.
+        """Get a boolean mask of the values that are between the given lower/upper bounds.
 
         Arguments:
             lower_bound: Lower bound value.
@@ -1704,8 +1666,7 @@ class Series:
         )
 
     def n_unique(self) -> int:
-        """
-        Count the number of unique values.
+        """Count the number of unique values.
 
         Examples:
             >>> import pandas as pd
@@ -1731,8 +1692,7 @@ class Series:
         return self._compliant_series.n_unique()  # type: ignore[no-any-return]
 
     def to_numpy(self) -> np.ndarray:
-        """
-        Convert to numpy.
+        """Convert to numpy.
 
         Examples:
             >>> import pandas as pd
@@ -1758,8 +1718,7 @@ class Series:
         return self._compliant_series.to_numpy()
 
     def to_pandas(self) -> pd.Series:
-        """
-        Convert to pandas.
+        """Convert to pandas.
 
         Examples:
             >>> import pandas as pd
@@ -1905,8 +1864,7 @@ class Series:
         return self._from_compliant_series(self._compliant_series.__invert__())
 
     def filter(self, other: Any) -> Self:
-        """
-        Filter elements in the Series based on a condition.
+        """Filter elements in the Series based on a condition.
 
         Examples:
             >>> import pandas as pd
@@ -1944,8 +1902,7 @@ class Series:
 
     # --- descriptive ---
     def is_duplicated(self: Self) -> Self:
-        r"""
-        Get a mask of all duplicated rows in the Series.
+        r"""Get a mask of all duplicated rows in the Series.
 
         Examples:
             >>> import narwhals as nw
@@ -1981,8 +1938,7 @@ class Series:
         return self._from_compliant_series(self._compliant_series.is_duplicated())
 
     def is_empty(self: Self) -> bool:
-        r"""
-        Check if the series is empty.
+        r"""Check if the series is empty.
 
         Examples:
             >>> import narwhals as nw
@@ -2011,8 +1967,7 @@ class Series:
         return self._compliant_series.is_empty()  # type: ignore[no-any-return]
 
     def is_unique(self: Self) -> Self:
-        r"""
-        Get a mask of all unique rows in the Series.
+        r"""Get a mask of all unique rows in the Series.
 
         Examples:
             >>> import narwhals as nw
@@ -2049,8 +2004,7 @@ class Series:
         return self._from_compliant_series(self._compliant_series.is_unique())
 
     def null_count(self: Self) -> int:
-        r"""
-        Create a new Series that shows the null counts per column.
+        r"""Create a new Series that shows the null counts per column.
 
         Notes:
             pandas and Polars handle null values differently. Polars distinguishes
@@ -2079,8 +2033,7 @@ class Series:
         return self._compliant_series.null_count()  # type: ignore[no-any-return]
 
     def is_first_distinct(self: Self) -> Self:
-        r"""
-        Return a boolean mask indicating the first occurrence of each distinct value.
+        r"""Return a boolean mask indicating the first occurrence of each distinct value.
 
         Examples:
             >>> import narwhals as nw
@@ -2119,8 +2072,7 @@ class Series:
         return self._from_compliant_series(self._compliant_series.is_first_distinct())
 
     def is_last_distinct(self: Self) -> Self:
-        r"""
-        Return a boolean mask indicating the last occurrence of each distinct value.
+        r"""Return a boolean mask indicating the last occurrence of each distinct value.
 
         Examples:
             >>> import narwhals as nw
@@ -2159,8 +2111,7 @@ class Series:
         return self._from_compliant_series(self._compliant_series.is_last_distinct())
 
     def is_sorted(self: Self, *, descending: bool = False) -> bool:
-        r"""
-        Check if the Series is sorted.
+        r"""Check if the Series is sorted.
 
         Arguments:
             descending: Check if the Series is sorted in descending order.
@@ -2199,8 +2150,7 @@ class Series:
         name: str | None = None,
         normalize: bool = False,
     ) -> DataFrame[Any]:
-        r"""
-        Count the occurrences of unique values.
+        r"""Count the occurrences of unique values.
 
         Arguments:
             sort: Sort the output by count in descending order. If set to False (default),
@@ -2255,8 +2205,7 @@ class Series:
         quantile: float,
         interpolation: Literal["nearest", "higher", "lower", "midpoint", "linear"],
     ) -> Any:
-        """
-        Get quantile value of the series.
+        """Get quantile value of the series.
 
         Note:
             pandas and Polars may have implementation differences for a given interpolation method.
@@ -2295,8 +2244,7 @@ class Series:
         )
 
     def zip_with(self: Self, mask: Self, other: Self) -> Self:
-        """
-        Take values from self or other based on the given mask.
+        """Take values from self or other based on the given mask.
 
         Where mask evaluates true, take values from self. Where mask evaluates false,
         take values from other.
@@ -2349,8 +2297,7 @@ class Series:
         )
 
     def item(self: Self, index: int | None = None) -> Any:
-        r"""
-        Return the Series as a scalar, or return the element at the given index.
+        r"""Return the Series as a scalar, or return the element at the given index.
 
         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]`.
@@ -2377,8 +2324,7 @@ class Series:
         return self._compliant_series.item(index=index)
 
     def head(self: Self, n: int = 10) -> Self:
-        r"""
-        Get the first `n` rows.
+        r"""Get the first `n` rows.
 
         Arguments:
             n: Number of rows to return.
@@ -2417,8 +2363,7 @@ class Series:
         return self._from_compliant_series(self._compliant_series.head(n))
 
     def tail(self: Self, n: int = 10) -> Self:
-        r"""
-        Get the last `n` rows.
+        r"""Get the last `n` rows.
 
         Arguments:
             n: Number of rows to return.
@@ -2456,8 +2401,7 @@ class Series:
         return self._from_compliant_series(self._compliant_series.tail(n))
 
     def round(self: Self, decimals: int = 0) -> Self:
-        r"""
-        Round underlying floating point data by `decimals` digits.
+        r"""Round underlying floating point data by `decimals` digits.
 
         Arguments:
             decimals: Number of decimals to round by.
@@ -2506,8 +2450,7 @@ class Series:
     def to_dummies(
         self: Self, *, separator: str = "_", drop_first: bool = False
     ) -> DataFrame[Any]:
-        r"""
-        Get dummy/indicator variables.
+        r"""Get dummy/indicator variables.
 
         Arguments:
             separator: Separator/delimiter used when generating column names.
@@ -2574,8 +2517,7 @@ class Series:
         )
 
     def gather_every(self: Self, n: int, offset: int = 0) -> Self:
-        r"""
-        Take every nth value in the Series and return as new Series.
+        r"""Take every nth value in the Series and return as new Series.
 
         Arguments:
             n: Gather every *n*-th row.
@@ -2614,8 +2556,7 @@ class Series:
         )
 
     def to_arrow(self: Self) -> pa.Array:
-        r"""
-        Convert to arrow.
+        r"""Convert to arrow.
 
         Examples:
             >>> import narwhals as nw
@@ -2652,8 +2593,7 @@ class Series:
         return self._compliant_series.to_arrow()
 
     def mode(self: Self) -> Self:
-        r"""
-        Compute the most occurring value(s).
+        r"""Compute the most occurring value(s).
 
         Can return multiple values.
 
@@ -2689,6 +2629,59 @@ class Series:
         """
         return self._from_compliant_series(self._compliant_series.mode())
 
+    def cum_count(self: Self, *, reverse: bool = False) -> Self:
+        r"""
+        Return the cumulative count of the non-null values in the series.
+
+        Arguments:
+            reverse: reverse the operation
+
+        Examples:
+            >>> import narwhals as nw
+            >>> import pandas as pd
+            >>> import polars as pl
+            >>> import pyarrow as pa
+            >>> data = ["x", "k", None, "d"]
+
+            We define a library agnostic function:
+
+            >>> @nw.narwhalify
+            ... def func(s):
+            ...     return s.cum_count(reverse=True)
+
+            We can then pass any supported library such as Pandas, Polars, or PyArrow to `func`:
+
+            >>> func(pd.Series(data))
+            0    3
+            1    2
+            2    1
+            3    1
+            dtype: int64
+            >>> func(pl.Series(data))  # doctest:+NORMALIZE_WHITESPACE
+            shape: (4,)
+            Series: '' [u32]
+            [
+                3
+                2
+                1
+                1
+            ]
+            >>> func(pa.chunked_array([data]))  # doctest:+ELLIPSIS
+            <pyarrow.lib.ChunkedArray object at ...>
+            [
+              [
+                3,
+                2,
+                1,
+                1
+              ]
+            ]
+
+        """
+        return self._from_compliant_series(
+            self._compliant_series.cum_count(reverse=reverse)
+        )
+
     def __iter__(self: Self) -> Iterator[Any]:
         yield from self._compliant_series.__iter__()
 
@@ -2713,8 +2706,7 @@ class SeriesCatNamespace(Generic[T]):
         self._narwhals_series = series
 
     def get_categories(self: Self) -> T:
-        """
-        Get unique categories from column.
+        """Get unique categories from column.
 
         Examples:
             Let's create some series:
@@ -2757,8 +2749,7 @@ class SeriesStringNamespace(Generic[T]):
         self._narwhals_series = series
 
     def len_chars(self: Self) -> T:
-        r"""
-        Return the length of each string as the number of characters.
+        r"""Return the length of each string as the number of characters.
 
         Examples:
             >>> import pandas as pd
@@ -2802,8 +2793,7 @@ class SeriesStringNamespace(Generic[T]):
     def replace(
         self: Self, pattern: str, value: str, *, literal: bool = False, n: int = 1
     ) -> T:
-        r"""
-        Replace first matching regex/literal substring with a new string value.
+        r"""Replace first matching regex/literal substring with a new string value.
 
         Arguments:
             pattern: A valid regular expression pattern.
@@ -2841,8 +2831,7 @@ class SeriesStringNamespace(Generic[T]):
         )
 
     def replace_all(self: Self, pattern: str, value: str, *, literal: bool = False) -> T:
-        r"""
-        Replace all matching regex/literal substring with a new string value.
+        r"""Replace all matching regex/literal substring with a new string value.
 
         Arguments:
             pattern: A valid regular expression pattern.
@@ -2879,8 +2868,7 @@ class SeriesStringNamespace(Generic[T]):
         )
 
     def strip_chars(self: Self, characters: str | None = None) -> T:
-        r"""
-        Remove leading and trailing characters.
+        r"""Remove leading and trailing characters.
 
         Arguments:
             characters: The set of characters to be removed. All combinations of this set of characters will be stripped from the start and end of the string. If set to None (default), all leading and trailing whitespace is removed instead.
@@ -2913,8 +2901,7 @@ class SeriesStringNamespace(Generic[T]):
         )
 
     def starts_with(self: Self, prefix: str) -> T:
-        r"""
-        Check if string values start with a substring.
+        r"""Check if string values start with a substring.
 
         Arguments:
             prefix: prefix substring
@@ -2955,8 +2942,7 @@ class SeriesStringNamespace(Generic[T]):
         )
 
     def ends_with(self: Self, suffix: str) -> T:
-        r"""
-        Check if string values end with a substring.
+        r"""Check if string values end with a substring.
 
         Arguments:
             suffix: suffix substring
@@ -2997,8 +2983,7 @@ class SeriesStringNamespace(Generic[T]):
         )
 
     def contains(self: Self, pattern: str, *, literal: bool = False) -> T:
-        r"""
-        Check if string contains a substring that matches a pattern.
+        r"""Check if string contains a substring that matches a pattern.
 
         Arguments:
             pattern: A Character sequence or valid regular expression pattern.
@@ -3045,8 +3030,7 @@ class SeriesStringNamespace(Generic[T]):
         )
 
     def slice(self: Self, offset: int, length: int | None = None) -> T:
-        r"""
-        Create subslices of the string values of a Series.
+        r"""Create subslices of the string values of a Series.
 
         Arguments:
             offset: Start index. Negative indexing is supported.
@@ -3116,8 +3100,7 @@ class SeriesStringNamespace(Generic[T]):
         )
 
     def head(self: Self, n: int = 5) -> T:
-        r"""
-        Take the first n elements of each string.
+        r"""Take the first n elements of each string.
 
         Arguments:
             n: Number of elements to take. Negative indexing is supported (see note (1.))
@@ -3164,8 +3147,7 @@ class SeriesStringNamespace(Generic[T]):
         )
 
     def tail(self: Self, n: int = 5) -> T:
-        r"""
-        Take the last n elements of each string.
+        r"""Take the last n elements of each string.
 
         Arguments:
             n: Number of elements to take. Negative indexing is supported (see note (1.))
@@ -3212,8 +3194,7 @@ class SeriesStringNamespace(Generic[T]):
         )
 
     def to_uppercase(self) -> T:
-        r"""
-        Transform string to uppercase variant.
+        r"""Transform string to uppercase variant.
 
         Notes:
             The PyArrow backend will convert 'ß' to 'ẞ' instead of 'SS'.
@@ -3260,8 +3241,7 @@ class SeriesStringNamespace(Generic[T]):
         )
 
     def to_lowercase(self) -> T:
-        r"""
-        Transform string to lowercase variant.
+        r"""Transform string to lowercase variant.
 
         Examples:
             >>> import pandas as pd
@@ -3303,8 +3283,7 @@ class SeriesStringNamespace(Generic[T]):
         )
 
     def to_datetime(self: Self, format: str | None = None) -> T:  # noqa: A002
-        """
-        Parse Series with strings to a Series with Datetime dtype.
+        """Parse Series with strings to a Series with Datetime dtype.
 
         Notes:
             pandas defaults to nanosecond time unit, Polars to microsecond.
@@ -3368,8 +3347,7 @@ class SeriesDateTimeNamespace(Generic[T]):
         self._narwhals_series = series
 
     def date(self: Self) -> T:
-        """
-        Get the date in a datetime series.
+        """Get the date in a datetime series.
 
         Raises:
             NotImplementedError: If pandas default backend is being used.
@@ -3409,8 +3387,7 @@ class SeriesDateTimeNamespace(Generic[T]):
         )
 
     def year(self: Self) -> T:
-        """
-        Get the year in a datetime series.
+        """Get the year in a datetime series.
 
         Examples:
             >>> import pandas as pd
@@ -3446,8 +3423,7 @@ class SeriesDateTimeNamespace(Generic[T]):
         )
 
     def month(self: Self) -> T:
-        """
-        Gets the month in a datetime series.
+        """Gets the month in a datetime series.
 
         Examples:
             >>> import pandas as pd
@@ -3483,8 +3459,7 @@ class SeriesDateTimeNamespace(Generic[T]):
         )
 
     def day(self: Self) -> T:
-        """
-        Extracts the day in a datetime series.
+        """Extracts the day in a datetime series.
 
         Examples:
             >>> import pandas as pd
@@ -3520,8 +3495,7 @@ class SeriesDateTimeNamespace(Generic[T]):
         )
 
     def hour(self: Self) -> T:
-        """
-         Extracts the hour in a datetime series.
+        """Extracts the hour in a datetime series.
 
         Examples:
             >>> import pandas as pd
@@ -3557,8 +3531,7 @@ class SeriesDateTimeNamespace(Generic[T]):
         )
 
     def minute(self: Self) -> T:
-        """
-        Extracts the minute in a datetime series.
+        """Extracts the minute in a datetime series.
 
         Examples:
             >>> import pandas as pd
@@ -3594,8 +3567,7 @@ class SeriesDateTimeNamespace(Generic[T]):
         )
 
     def second(self: Self) -> T:
-        """
-        Extracts the second(s) in a datetime series.
+        """Extracts the seconds in a datetime series.
 
         Examples:
             >>> import pandas as pd
@@ -3631,8 +3603,7 @@ class SeriesDateTimeNamespace(Generic[T]):
         )
 
     def millisecond(self: Self) -> T:
-        """
-        Extracts the milliseconds in a datetime series.
+        """Extracts the milliseconds in a datetime series.
 
         Examples:
             >>> import pandas as pd
@@ -3681,8 +3652,7 @@ class SeriesDateTimeNamespace(Generic[T]):
         )
 
     def microsecond(self: Self) -> T:
-        """
-        Extracts the microseconds in a datetime series.
+        """Extracts the microseconds in a datetime series.
 
         Examples:
             >>> import pandas as pd
@@ -3731,8 +3701,7 @@ class SeriesDateTimeNamespace(Generic[T]):
         )
 
     def nanosecond(self: Self) -> T:
-        """
-        Extracts the nanosecond(s) in a date series.
+        """Extract the nanoseconds in a date series.
 
         Examples:
             >>> import pandas as pd
@@ -3771,8 +3740,7 @@ class SeriesDateTimeNamespace(Generic[T]):
         )
 
     def ordinal_day(self: Self) -> T:
-        """
-        Get ordinal day.
+        """Get ordinal day.
 
         Examples:
             >>> import pandas as pd
@@ -3808,8 +3776,7 @@ class SeriesDateTimeNamespace(Generic[T]):
         )
 
     def total_minutes(self: Self) -> T:
-        """
-        Get total minutes.
+        """Get total minutes.
 
         Notes:
             The function outputs the total minutes in the int dtype by default,
@@ -3850,8 +3817,7 @@ class SeriesDateTimeNamespace(Generic[T]):
         )
 
     def total_seconds(self: Self) -> T:
-        """
-        Get total seconds.
+        """Get total seconds.
 
         Notes:
             The function outputs the total seconds in the int dtype by default,
@@ -3892,8 +3858,7 @@ class SeriesDateTimeNamespace(Generic[T]):
         )
 
     def total_milliseconds(self: Self) -> T:
-        """
-        Get total milliseconds.
+        """Get total milliseconds.
 
         Notes:
             The function outputs the total milliseconds in the int dtype by default,
@@ -3937,8 +3902,7 @@ class SeriesDateTimeNamespace(Generic[T]):
         )
 
     def total_microseconds(self: Self) -> T:
-        """
-        Get total microseconds.
+        """Get total microseconds.
 
         Notes:
             The function outputs the total microseconds in the int dtype by default,
@@ -3982,8 +3946,7 @@ class SeriesDateTimeNamespace(Generic[T]):
         )
 
     def total_nanoseconds(self: Self) -> T:
-        """
-        Get total nanoseconds.
+        """Get total nanoseconds.
 
         Notes:
             The function outputs the total nanoseconds in the int dtype by default,
@@ -4024,8 +3987,7 @@ class SeriesDateTimeNamespace(Generic[T]):
         )
 
     def to_string(self: Self, format: str) -> T:  # noqa: A002
-        """
-        Convert a Date/Time/Datetime series into a String series with the given format.
+        """Convert a Date/Time/Datetime series into a String series with the given format.
 
         Notes:
             Unfortunately, different libraries interpret format directives a bit
@@ -4099,8 +4061,7 @@ class SeriesDateTimeNamespace(Generic[T]):
         )
 
     def replace_time_zone(self: Self, time_zone: str | None) -> T:
-        """
-        Replace time zone.
+        """Replace time zone.
 
         Arguments:
             time_zone: Target time zone.
@@ -4152,8 +4113,7 @@ class SeriesDateTimeNamespace(Generic[T]):
         )
 
     def convert_time_zone(self: Self, time_zone: str) -> T:
-        """
-        Convert time zone.
+        """Convert time zone.
 
         If converting from a time-zone-naive column, then conversion happens
         as if converting from UTC.
@@ -4211,8 +4171,7 @@ class SeriesDateTimeNamespace(Generic[T]):
         )
 
     def timestamp(self: Self, time_unit: Literal["ns", "us", "ms"] = "us") -> T:
-        """
-        Return a timestamp in the given time unit.
+        """Return a timestamp in the given time unit.
 
         Arguments:
             time_unit: {'ns', 'us', 'ms'}
diff --git a/narwhals/stable/v1/__init__.py b/narwhals/stable/v1/__init__.py
index 738f19ec..3d97fa3d 100644
--- a/narwhals/stable/v1/__init__.py
+++ b/narwhals/stable/v1/__init__.py
@@ -77,8 +77,7 @@ T = TypeVar("T")
 
 
 class DataFrame(NwDataFrame[IntoDataFrameT]):
-    """
-    Narwhals DataFrame, backed by a native dataframe.
+    """Narwhals DataFrame, backed by a native dataframe.
 
     The native dataframe might be pandas.DataFrame, polars.DataFrame, ...
 
@@ -135,8 +134,7 @@ class DataFrame(NwDataFrame[IntoDataFrameT]):
         return super().__getitem__(item)
 
     def lazy(self) -> LazyFrame[Any]:
-        """
-        Lazify the DataFrame (if possible).
+        """Lazify the DataFrame (if possible).
 
         If a library does not support lazy execution, then this is a no-op.
 
@@ -190,8 +188,7 @@ class DataFrame(NwDataFrame[IntoDataFrameT]):
     def to_dict(
         self, *, as_series: bool = True
     ) -> dict[str, Series] | dict[str, list[Any]]:
-        """
-        Convert DataFrame to a dictionary mapping column name to values.
+        """Convert DataFrame to a dictionary mapping column name to values.
 
         Arguments:
             as_series: If set to true ``True``, then the values are Narwhals Series,
@@ -231,8 +228,7 @@ class DataFrame(NwDataFrame[IntoDataFrameT]):
         return super().to_dict(as_series=as_series)  # type: ignore[return-value]
 
     def is_duplicated(self: Self) -> Series:
-        r"""
-        Get a mask of all duplicated rows in this DataFrame.
+        r"""Get a mask of all duplicated rows in this DataFrame.
 
         Examples:
             >>> import narwhals as nw
@@ -279,8 +275,7 @@ class DataFrame(NwDataFrame[IntoDataFrameT]):
         return super().is_duplicated()  # type: ignore[return-value]
 
     def is_unique(self: Self) -> Series:
-        r"""
-        Get a mask of all unique rows in this DataFrame.
+        r"""Get a mask of all unique rows in this DataFrame.
 
         Examples:
             >>> import narwhals as nw
@@ -332,8 +327,7 @@ class DataFrame(NwDataFrame[IntoDataFrameT]):
 
 
 class LazyFrame(NwLazyFrame[IntoFrameT]):
-    """
-    Narwhals DataFrame, backed by a native dataframe.
+    """Narwhals DataFrame, backed by a native dataframe.
 
     The native dataframe might be pandas.DataFrame, polars.LazyFrame, ...
 
@@ -346,8 +340,7 @@ class LazyFrame(NwLazyFrame[IntoFrameT]):
         return DataFrame
 
     def collect(self) -> DataFrame[Any]:
-        r"""
-        Materialize this LazyFrame into a DataFrame.
+        r"""Materialize this LazyFrame into a DataFrame.
 
         Returns:
             DataFrame
@@ -389,8 +382,7 @@ class LazyFrame(NwLazyFrame[IntoFrameT]):
 
 
 class Series(NwSeries):
-    """
-    Narwhals Series, backed by a native series.
+    """Narwhals Series, backed by a native series.
 
     The native series might be pandas.Series, polars.Series, ...
 
@@ -407,8 +399,7 @@ class Series(NwSeries):
         return DataFrame
 
     def to_frame(self) -> DataFrame[Any]:
-        """
-        Convert to dataframe.
+        """Convert to dataframe.
 
         Examples:
             >>> import pandas as pd
@@ -453,8 +444,7 @@ class Series(NwSeries):
         name: str | None = None,
         normalize: bool = False,
     ) -> DataFrame[Any]:
-        r"""
-        Count the occurrences of unique values.
+        r"""Count the occurrences of unique values.
 
         Arguments:
             sort: Sort the output by count in descending order. If set to False (default),
@@ -508,8 +498,7 @@ class Expr(NwExpr):
 
 
 class Schema(NwSchema):
-    """
-    Ordered mapping of column names to their data type.
+    """Ordered mapping of column names to their data type.
 
     Arguments:
         schema: Mapping[str, DType] | Iterable[tuple[str, DType]] | None
@@ -704,11 +693,7 @@ def from_native(
     eager_or_interchange_only: Literal[True],
     series_only: None = ...,
     allow_series: None = ...,
-) -> DataFrame[IntoDataFrameT]:
-    """
-    from_native(df, strict=True, eager_or_interchange_only=True)
-    from_native(df, eager_or_interchange_only=True)
-    """
+) -> DataFrame[IntoDataFrameT]: ...
 
 
 @overload
@@ -720,11 +705,7 @@ def from_native(
     eager_or_interchange_only: None = ...,
     series_only: None = ...,
     allow_series: None = ...,
-) -> DataFrame[IntoDataFrameT]:
-    """
-    from_native(df, strict=True, eager_only=True)
-    from_native(df, eager_only=True)
-    """
+) -> DataFrame[IntoDataFrameT]: ...
 
 
 @overload
@@ -736,11 +717,7 @@ def from_native(
     eager_or_interchange_only: None = ...,
     series_only: None = ...,
     allow_series: Literal[True],
-) -> DataFrame[Any] | LazyFrame[Any] | Series:
-    """
-    from_native(df, strict=True, allow_series=True)
-    from_native(df, allow_series=True)
-    """
+) -> DataFrame[Any] | LazyFrame[Any] | Series: ...
 
 
 @overload
@@ -752,11 +729,7 @@ def from_native(
     eager_or_interchange_only: None = ...,
     series_only: Literal[True],
     allow_series: None = ...,
-) -> Series:
-    """
-    from_native(df, strict=True, series_only=True)
-    from_native(df, series_only=True)
-    """
+) -> Series: ...
 
 
 @overload
@@ -768,11 +741,7 @@ def from_native(
     eager_or_interchange_only: None = ...,
     series_only: None = ...,
     allow_series: None = ...,
-) -> DataFrame[IntoFrameT] | LazyFrame[IntoFrameT]:
-    """
-    from_native(df, strict=True)
-    from_native(df)
-    """
+) -> DataFrame[IntoFrameT] | LazyFrame[IntoFrameT]: ...
 
 
 @overload
@@ -904,11 +873,7 @@ def from_native(
     eager_or_interchange_only: Literal[True],
     series_only: None = ...,
     allow_series: None = ...,
-) -> DataFrame[IntoDataFrameT]:
-    """
-    from_native(df, pass_through=False, eager_or_interchange_only=True)
-    from_native(df, eager_or_interchange_only=True)
-    """
+) -> DataFrame[IntoDataFrameT]: ...
 
 
 @overload
@@ -920,11 +885,7 @@ def from_native(
     eager_or_interchange_only: None = ...,
     series_only: None = ...,
     allow_series: None = ...,
-) -> DataFrame[IntoDataFrameT]:
-    """
-    from_native(df, pass_through=False, eager_only=True)
-    from_native(df, eager_only=True)
-    """
+) -> DataFrame[IntoDataFrameT]: ...
 
 
 @overload
@@ -936,11 +897,7 @@ def from_native(
     eager_or_interchange_only: None = ...,
     series_only: None = ...,
     allow_series: Literal[True],
-) -> DataFrame[Any] | LazyFrame[Any] | Series:
-    """
-    from_native(df, pass_through=False, allow_series=True)
-    from_native(df, allow_series=True)
-    """
+) -> DataFrame[Any] | LazyFrame[Any] | Series: ...
 
 
 @overload
@@ -952,11 +909,7 @@ def from_native(
     eager_or_interchange_only: None = ...,
     series_only: Literal[True],
     allow_series: None = ...,
-) -> Series:
-    """
-    from_native(df, pass_through=False, series_only=True)
-    from_native(df, series_only=True)
-    """
+) -> Series: ...
 
 
 @overload
@@ -968,11 +921,7 @@ def from_native(
     eager_or_interchange_only: None = ...,
     series_only: None = ...,
     allow_series: None = ...,
-) -> DataFrame[IntoFrameT] | LazyFrame[IntoFrameT]:
-    """
-    from_native(df, pass_through=False)
-    from_native(df)
-    """
+) -> DataFrame[IntoFrameT] | LazyFrame[IntoFrameT]: ...
 
 
 # All params passed in as variables
@@ -998,8 +947,7 @@ def from_native(
     series_only: bool | None = None,
     allow_series: bool | None = None,
 ) -> Any:
-    """
-    Convert dataframe/series to Narwhals DataFrame, LazyFrame, or Series.
+    """Convert dataframe/series to Narwhals DataFrame, LazyFrame, or Series.
 
     Arguments:
         native_object: Raw object from user.
@@ -1090,12 +1038,23 @@ def to_native(
     strict: bool | None = None,
     pass_through: bool | None = None,
 ) -> IntoFrameT | Any:
-    """
-    Convert Narwhals object to native one.
+    """Convert Narwhals object to native one.
 
     Arguments:
         narwhals_object: Narwhals object.
-        strict: whether to raise on non-Narwhals input.
+        strict: Determine what happens if the object isn't supported by Narwhals:
+
+            - `True` (default): raise an error
+            - `False`: pass object through as-is
+
+            **Deprecated** (v1.13.0):
+                Please use `pass_through` instead. Note that `strict` is still available
+                (and won't emit a deprecation warning) if you use `narwhals.stable.v1`,
+                see [perfect backwards compatibility policy](https://narwhals-dev.github.io/narwhals/backcompat/).
+        pass_through: Determine what happens if the object isn't supported by Narwhals:
+
+            - `False` (default): raise an error
+            - `True`: pass object through as-is
 
     Returns:
         Object of class that user started with.
@@ -1129,8 +1088,7 @@ def narwhalify(
     series_only: bool | None = False,
     allow_series: bool | None = True,
 ) -> Callable[..., Any]:
-    """
-    Decorate function so it becomes dataframe-agnostic.
+    """Decorate function so it becomes dataframe-agnostic.
 
     `narwhalify` will try to convert any dataframe/series-like object into the narwhal
     respective DataFrame/Series, while leaving the other parameters as they are.
@@ -1139,7 +1097,7 @@ def narwhalify(
     converted back to the original dataframe/series type, while if the output is another
     type it will be left as is.
 
-    By setting `strict=True`, then every input and every output will be required to be a
+    By setting `pass_through=False`, then every input and every output will be required to be a
     dataframe/series-like object.
 
     Instead of writing
@@ -1149,7 +1107,7 @@ def narwhalify(
 
 
     def func(df):
-        df = nw.from_native(df, strict=False)
+        df = nw.from_native(df, pass_through=True)
         df = df.group_by("a").agg(nw.col("b").sum())
         return nw.to_native(df)
     ```
@@ -1175,15 +1133,25 @@ def narwhalify(
 
     Arguments:
         func: Function to wrap in a `from_native`-`to_native` block.
-        strict: Whether to raise if object can't be converted or to just leave it as-is
-            (default).
+        strict: Determine what happens if the object isn't supported by Narwhals:
+
+            - `True` (default): raise an error
+            - `False`: pass object through as-is
+
+            **Deprecated** (v1.13.0):
+                Please use `pass_through` instead. Note that `strict` is still available
+                (and won't emit a deprecation warning) if you use `narwhals.stable.v1`,
+                see [perfect backwards compatibility policy](https://narwhals-dev.github.io/narwhals/backcompat/).
+        pass_through: Determine what happens if the object isn't supported by Narwhals:
+
+            - `False` (default): raise an error
+            - `True`: pass object through as-is
         eager_only: Whether to only allow eager objects.
         eager_or_interchange_only: Whether to only allow eager objects or objects which
             implement the Dataframe Interchange Protocol.
         series_only: Whether to only allow series.
         allow_series: Whether to allow series (default is only dataframe / lazyframe).
     """
-
     pass_through = validate_strict_and_pass_though(
         strict, pass_through, pass_through_default=True, emit_deprecation_warning=False
     )
@@ -1239,8 +1207,10 @@ def narwhalify(
 
 
 def all() -> Expr:
-    """
-    Instantiate an expression representing all columns.
+    """Instantiate an expression representing all columns.
+
+    Returns:
+        A new expression.
 
     Examples:
         >>> import polars as pl
@@ -1287,12 +1257,14 @@ def all() -> Expr:
 
 
 def col(*names: str | Iterable[str]) -> Expr:
-    """
-    Creates an expression that references one or more columns by their name(s).
+    """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.
 
+    Returns:
+        A new expression.
+
     Examples:
         >>> import pandas as pd
         >>> import polars as pl
@@ -1334,8 +1306,7 @@ def col(*names: str | Iterable[str]) -> Expr:
 
 
 def nth(*indices: int | Sequence[int]) -> Expr:
-    """
-    Creates an expression that references one or more columns by their index(es).
+    """Creates an expression that references one or more columns by their index(es).
 
     Notes:
         `nth` is not supported for Polars version<1.0.0. Please use [`col`](/api-reference/narwhals/#narwhals.col) instead.
@@ -1343,6 +1314,9 @@ def nth(*indices: int | Sequence[int]) -> Expr:
     Arguments:
         indices: One or more indices representing the columns to retrieve.
 
+    Returns:
+        A new expression.
+
     Examples:
         >>> import pandas as pd
         >>> import polars as pl
@@ -1385,8 +1359,10 @@ def nth(*indices: int | Sequence[int]) -> Expr:
 
 
 def len() -> Expr:
-    """
-    Return the number of rows.
+    """Return the number of rows.
+
+    Returns:
+        A new expression.
 
     Examples:
         >>> import polars as pl
@@ -1427,13 +1403,15 @@ def len() -> Expr:
 
 
 def lit(value: Any, dtype: DType | None = None) -> Expr:
-    """
-    Return an expression representing a literal value.
+    """Return an expression representing a literal value.
 
     Arguments:
         value: The value to use as literal.
         dtype: The data type of the literal value. If not provided, the data type will be inferred.
 
+    Returns:
+        A new expression.
+
     Examples:
         >>> import pandas as pd
         >>> import polars as pl
@@ -1477,8 +1455,7 @@ def lit(value: Any, dtype: DType | None = None) -> Expr:
 
 
 def min(*columns: str) -> Expr:
-    """
-    Return the minimum value.
+    """Return the minimum value.
 
     Note:
        Syntactic sugar for ``nw.col(columns).min()``.
@@ -1486,6 +1463,9 @@ def min(*columns: str) -> Expr:
     Arguments:
         columns: Name(s) of the columns to use in the aggregation function.
 
+    Returns:
+        A new expression.
+
     Examples:
         >>> import polars as pl
         >>> import pandas as pd
@@ -1525,8 +1505,7 @@ def min(*columns: str) -> Expr:
 
 
 def max(*columns: str) -> Expr:
-    """
-    Return the maximum value.
+    """Return the maximum value.
 
     Note:
        Syntactic sugar for ``nw.col(columns).max()``.
@@ -1534,6 +1513,9 @@ def max(*columns: str) -> Expr:
     Arguments:
         columns: Name(s) of the columns to use in the aggregation function.
 
+    Returns:
+        A new expression.
+
     Examples:
         >>> import polars as pl
         >>> import pandas as pd
@@ -1573,8 +1555,7 @@ def max(*columns: str) -> Expr:
 
 
 def mean(*columns: str) -> Expr:
-    """
-    Get the mean value.
+    """Get the mean value.
 
     Note:
         Syntactic sugar for ``nw.col(columns).mean()``
@@ -1582,6 +1563,9 @@ def mean(*columns: str) -> Expr:
     Arguments:
         columns: Name(s) of the columns to use in the aggregation function
 
+    Returns:
+        A new expression.
+
     Examples:
         >>> import pandas as pd
         >>> import polars as pl
@@ -1621,8 +1605,7 @@ def mean(*columns: str) -> Expr:
 
 
 def median(*columns: str) -> Expr:
-    """
-    Get the median value.
+    """Get the median value.
 
     Notes:
         - Syntactic sugar for ``nw.col(columns).median()``
@@ -1631,6 +1614,9 @@ def median(*columns: str) -> Expr:
     Arguments:
         columns: Name(s) of the columns to use in the aggregation function
 
+    Returns:
+        A new expression.
+
     Examples:
         >>> import pandas as pd
         >>> import polars as pl
@@ -1670,8 +1656,7 @@ def median(*columns: str) -> Expr:
 
 
 def sum(*columns: str) -> Expr:
-    """
-    Sum all values.
+    """Sum all values.
 
     Note:
         Syntactic sugar for ``nw.col(columns).sum()``
@@ -1679,6 +1664,9 @@ def sum(*columns: str) -> Expr:
     Arguments:
         columns: Name(s) of the columns to use in the aggregation function
 
+    Returns:
+        A new expression.
+
     Examples:
         >>> import pandas as pd
         >>> import polars as pl
@@ -1718,8 +1706,7 @@ def sum(*columns: str) -> Expr:
 
 
 def sum_horizontal(*exprs: IntoExpr | Iterable[IntoExpr]) -> Expr:
-    """
-    Sum all values horizontally across columns.
+    """Sum all values horizontally across columns.
 
     Warning:
         Unlike Polars, we support horizontal sum over numeric columns only.
@@ -1728,6 +1715,9 @@ def sum_horizontal(*exprs: IntoExpr | Iterable[IntoExpr]) -> Expr:
         exprs: Name(s) of the columns to use in the aggregation function. Accepts
             expression input.
 
+    Returns:
+        A new expression.
+
     Examples:
         >>> import pandas as pd
         >>> import polars as pl
@@ -1772,12 +1762,14 @@ def sum_horizontal(*exprs: IntoExpr | Iterable[IntoExpr]) -> Expr:
 
 
 def all_horizontal(*exprs: IntoExpr | Iterable[IntoExpr]) -> Expr:
-    r"""
-    Compute the bitwise AND horizontally across columns.
+    r"""Compute the bitwise AND horizontally across columns.
 
     Arguments:
         exprs: Name(s) of the columns to use in the aggregation function. Accepts expression input.
 
+    Returns:
+        A new expression.
+
     Notes:
         pandas and Polars handle null values differently.
 
@@ -1840,12 +1832,14 @@ def all_horizontal(*exprs: IntoExpr | Iterable[IntoExpr]) -> Expr:
 
 
 def any_horizontal(*exprs: IntoExpr | Iterable[IntoExpr]) -> Expr:
-    r"""
-    Compute the bitwise OR horizontally across columns.
+    r"""Compute the bitwise OR horizontally across columns.
 
     Arguments:
         exprs: Name(s) of the columns to use in the aggregation function. Accepts expression input.
 
+    Returns:
+        A new expression.
+
     Notes:
         pandas and Polars handle null values differently.
 
@@ -1908,13 +1902,15 @@ def any_horizontal(*exprs: IntoExpr | Iterable[IntoExpr]) -> Expr:
 
 
 def mean_horizontal(*exprs: IntoExpr | Iterable[IntoExpr]) -> Expr:
-    """
-    Compute the mean of all values horizontally across columns.
+    """Compute the mean of all values horizontally across columns.
 
     Arguments:
         exprs: Name(s) of the columns to use in the aggregation function. Accepts
             expression input.
 
+    Returns:
+        A new expression.
+
     Examples:
         >>> import pandas as pd
         >>> import polars as pl
@@ -1966,8 +1962,7 @@ def mean_horizontal(*exprs: IntoExpr | Iterable[IntoExpr]) -> Expr:
 
 
 def min_horizontal(*exprs: IntoExpr | Iterable[IntoExpr]) -> Expr:
-    """
-    Get the minimum value horizontally across columns.
+    """Get the minimum value horizontally across columns.
 
     Notes:
         We support `min_horizontal` over numeric columns only.
@@ -1976,6 +1971,9 @@ def min_horizontal(*exprs: IntoExpr | Iterable[IntoExpr]) -> Expr:
         exprs: Name(s) of the columns to use in the aggregation function. Accepts
             expression input.
 
+    Returns:
+        A new expression.
+
     Examples:
         >>> import narwhals as nw
         >>> import pandas as pd
@@ -2022,8 +2020,7 @@ def min_horizontal(*exprs: IntoExpr | Iterable[IntoExpr]) -> Expr:
 
 
 def max_horizontal(*exprs: IntoExpr | Iterable[IntoExpr]) -> Expr:
-    """
-    Get the maximum value horizontally across columns.
+    """Get the maximum value horizontally across columns.
 
     Notes:
         We support `max_horizontal` over numeric columns only.
@@ -2032,6 +2029,9 @@ def max_horizontal(*exprs: IntoExpr | Iterable[IntoExpr]) -> Expr:
         exprs: Name(s) of the columns to use in the aggregation function. Accepts
             expression input.
 
+    Returns:
+        A new expression.
+
     Examples:
         >>> import narwhals as nw
         >>> import pandas as pd
@@ -2098,8 +2098,7 @@ def concat(
     *,
     how: Literal["horizontal", "vertical"] = "vertical",
 ) -> DataFrame[Any] | LazyFrame[Any]:
-    """
-    Concatenate multiple DataFrames, LazyFrames into a single entity.
+    """Concatenate multiple DataFrames, LazyFrames into a single entity.
 
     Arguments:
         items: DataFrames, LazyFrames to concatenate.
@@ -2117,7 +2116,6 @@ def concat(
         NotImplementedError: The items to concatenate should either all be eager, or all lazy
 
     Examples:
-
         Let's take an example of vertical concatenation:
 
         >>> import pandas as pd
@@ -2205,8 +2203,7 @@ def concat_str(
     separator: str = "",
     ignore_nulls: bool = False,
 ) -> Expr:
-    r"""
-    Horizontally concatenate columns into a single string column.
+    r"""Horizontally concatenate columns into a single string column.
 
     Arguments:
         exprs: Columns to concatenate into a single string column. Accepts expression
@@ -2219,6 +2216,9 @@ def concat_str(
             If set to `False`, null values will be propagated and if the row contains any
             null values, the output is null.
 
+    Returns:
+        A new expression.
+
     Examples:
         >>> import narwhals as nw
         >>> import pandas as pd
@@ -2272,7 +2272,6 @@ def concat_str(
         ----
         full_sentence: [["2 dogs play","4 cats swim",null]]
     """
-
     return _stableify(
         nw.concat_str(exprs, *more_exprs, separator=separator, ignore_nulls=ignore_nulls)
     )
@@ -2297,15 +2296,25 @@ class Then(NwThen, Expr):
 
 
 def when(*predicates: IntoExpr | Iterable[IntoExpr]) -> When:
-    """
-    Start a `when-then-otherwise` expression.
-
-    Expression similar to an `if-else` statement in Python. Always initiated by a `pl.when(<condition>).then(<value if condition>)`., and optionally followed by chaining one or more `.when(<condition>).then(<value>)` statements.
-    Chained when-then operations should be read as Python `if, elif, ... elif` blocks, not as `if, if, ... if`, i.e. the first condition that evaluates to `True` will be picked.
-    If none of the conditions are `True`, an optional `.otherwise(<value if all statements are false>)` can be appended at the end. If not appended, and none of the conditions are `True`, `None` will be returned.
+    """Start a `when-then-otherwise` expression.
+
+    Expression similar to an `if-else` statement in Python. Always initiated by a
+    `pl.when(<condition>).then(<value if condition>)`, and optionally followed by
+    chaining one or more `.when(<condition>).then(<value>)` statements.
+    Chained when-then operations should be read as Python `if, elif, ... elif`
+    blocks, not as `if, if, ... if`, i.e. the first condition that evaluates to
+    `True` will be picked.
+    If none of the conditions are `True`, an optional
+    `.otherwise(<value if all statements are false>)` can be appended at the end.
+    If not appended, and none of the conditions are `True`, `None` will be returned.
 
     Arguments:
-        predicates: Condition(s) that must be met in order to apply the subsequent statement. Accepts one or more boolean expressions, which are implicitly combined with `&`. String input is parsed as a column name.
+        predicates: Condition(s) that must be met in order to apply the subsequent statement.
+            Accepts one or more boolean expressions, which are implicitly combined with `&`.
+            String input is parsed as a column name.
+
+    Returns:
+        A "when" object, which `.then` can be called on.
 
     Examples:
         >>> import pandas as pd
@@ -2362,8 +2371,7 @@ def new_series(
     *,
     native_namespace: ModuleType,
 ) -> Series:
-    """
-    Instantiate Narwhals Series from iterable (e.g. list or array).
+    """Instantiate Narwhals Series from iterable (e.g. list or array).
 
     Arguments:
         name: Name of resulting Series.
@@ -2426,8 +2434,7 @@ def new_series(
 def from_arrow(
     native_frame: ArrowStreamExportable, *, native_namespace: ModuleType
 ) -> DataFrame[Any]:
-    """
-    Construct a DataFrame from an object which supports the PyCapsule Interface.
+    """Construct a DataFrame from an object which supports the PyCapsule Interface.
 
     Arguments:
         native_frame: Object which implements `__arrow_c_stream__`.
@@ -2478,8 +2485,7 @@ def from_dict(
     *,
     native_namespace: ModuleType | None = None,
 ) -> DataFrame[Any]:
-    """
-    Instantiate DataFrame from dictionary.
+    """Instantiate DataFrame from dictionary.
 
     Notes:
         For pandas-like dataframes, conversion to schema is applied after dataframe
diff --git a/narwhals/stable/v1/_dtypes.py b/narwhals/stable/v1/_dtypes.py
index 459441d6..19748dae 100644
--- a/narwhals/stable/v1/_dtypes.py
+++ b/narwhals/stable/v1/_dtypes.py
@@ -28,8 +28,7 @@ from narwhals.dtypes import Unknown
 
 
 class Datetime(NwDatetime):
-    """
-    Data type representing a calendar date and time of day.
+    """Data type representing a calendar date and time of day.
 
     Arguments:
         time_unit: Unit of time. Defaults to `'us'` (microseconds).
@@ -46,8 +45,7 @@ class Datetime(NwDatetime):
 
 
 class Duration(NwDuration):
-    """
-    Data type representing a time duration.
+    """Data type representing a time duration.
 
     Arguments:
         time_unit: Unit of time. Defaults to `'us'` (microseconds).
diff --git a/narwhals/translate.py b/narwhals/translate.py
index 129fe671..54653b1e 100644
--- a/narwhals/translate.py
+++ b/narwhals/translate.py
@@ -75,12 +75,23 @@ def to_native(
     strict: bool | None = None,
     pass_through: bool | None = None,
 ) -> IntoFrameT | Any:
-    """
-    Convert Narwhals object to native one.
+    """Convert Narwhals object to native one.
 
     Arguments:
         narwhals_object: Narwhals object.
-        strict: whether to raise on non-Narwhals input.
+        strict: Determine what happens if the object isn't supported by Narwhals:
+
+            - `True` (default): raise an error
+            - `False`: pass object through as-is
+
+            **Deprecated** (v1.13.0):
+                Please use `pass_through` instead. Note that `strict` is still available
+                (and won't emit a deprecation warning) if you use `narwhals.stable.v1`,
+                see [perfect backwards compatibility policy](https://narwhals-dev.github.io/narwhals/backcompat/).
+        pass_through: Determine what happens if the object isn't supported by Narwhals:
+
+            - `False` (default): raise an error
+            - `True`: pass object through as-is
 
     Returns:
         Object of class that user started with.
@@ -233,11 +244,7 @@ def from_native(
     eager_or_interchange_only: Literal[True],
     series_only: None = ...,
     allow_series: None = ...,
-) -> DataFrame[IntoDataFrameT]:
-    """
-    from_native(df, pass_through=False, eager_or_interchange_only=True)
-    from_native(df, eager_or_interchange_only=True)
-    """
+) -> DataFrame[IntoDataFrameT]: ...
 
 
 @overload
@@ -249,11 +256,7 @@ def from_native(
     eager_or_interchange_only: None = ...,
     series_only: None = ...,
     allow_series: None = ...,
-) -> DataFrame[IntoDataFrameT]:
-    """
-    from_native(df, pass_through=False, eager_only=True)
-    from_native(df, eager_only=True)
-    """
+) -> DataFrame[IntoDataFrameT]: ...
 
 
 @overload
@@ -265,11 +268,7 @@ def from_native(
     eager_or_interchange_only: None = ...,
     series_only: None = ...,
     allow_series: Literal[True],
-) -> DataFrame[Any] | LazyFrame[Any] | Series:
-    """
-    from_native(df, pass_through=False, allow_series=True)
-    from_native(df, allow_series=True)
-    """
+) -> DataFrame[Any] | LazyFrame[Any] | Series: ...
 
 
 @overload
@@ -281,11 +280,7 @@ def from_native(
     eager_or_interchange_only: None = ...,
     series_only: Literal[True],
     allow_series: None = ...,
-) -> Series:
-    """
-    from_native(df, pass_through=False, series_only=True)
-    from_native(df, series_only=True)
-    """
+) -> Series: ...
 
 
 @overload
@@ -297,11 +292,7 @@ def from_native(
     eager_or_interchange_only: None = ...,
     series_only: None = ...,
     allow_series: None = ...,
-) -> DataFrame[IntoFrameT] | LazyFrame[IntoFrameT]:
-    """
-    from_native(df, pass_through=False)
-    from_native(df)
-    """
+) -> DataFrame[IntoFrameT] | LazyFrame[IntoFrameT]: ...
 
 
 # All params passed in as variables
@@ -327,8 +318,7 @@ def from_native(
     series_only: bool | None = None,
     allow_series: bool | None = None,
 ) -> Any:
-    """
-    Convert dataframe/series to Narwhals DataFrame, LazyFrame, or Series.
+    """Convert dataframe/series to Narwhals DataFrame, LazyFrame, or Series.
 
     Arguments:
         native_object: Raw object from user.
@@ -724,8 +714,7 @@ def _from_native_impl(  # noqa: PLR0915
 
 
 def get_native_namespace(obj: Any) -> Any:
-    """
-    Get native namespace from object.
+    """Get native namespace from object.
 
     Examples:
         >>> import polars as pl
@@ -751,8 +740,7 @@ def narwhalify(
     series_only: bool | None = False,
     allow_series: bool | None = True,
 ) -> Callable[..., Any]:
-    """
-    Decorate function so it becomes dataframe-agnostic.
+    """Decorate function so it becomes dataframe-agnostic.
 
     `narwhalify` will try to convert any dataframe/series-like object into the narwhal
     respective DataFrame/Series, while leaving the other parameters as they are.
@@ -761,7 +749,7 @@ def narwhalify(
     converted back to the original dataframe/series type, while if the output is another
     type it will be left as is.
 
-    By setting `strict=True`, then every input and every output will be required to be a
+    By setting `pass_through=False`, then every input and every output will be required to be a
     dataframe/series-like object.
 
     Instead of writing
@@ -771,7 +759,7 @@ def narwhalify(
 
 
     def func(df):
-        df = nw.from_native(df, strict=False)
+        df = nw.from_native(df, pass_through=True)
         df = df.group_by("a").agg(nw.col("b").sum())
         return nw.to_native(df)
     ```
@@ -797,8 +785,19 @@ def narwhalify(
 
     Arguments:
         func: Function to wrap in a `from_native`-`to_native` block.
-        strict: Whether to raise if object can't be converted or to just leave it as-is
-            (default).
+        strict: Determine what happens if the object isn't supported by Narwhals:
+
+            - `True` (default): raise an error
+            - `False`: pass object through as-is
+
+            **Deprecated** (v1.13.0):
+                Please use `pass_through` instead. Note that `strict` is still available
+                (and won't emit a deprecation warning) if you use `narwhals.stable.v1`,
+                see [perfect backwards compatibility policy](https://narwhals-dev.github.io/narwhals/backcompat/).
+        pass_through: Determine what happens if the object isn't supported by Narwhals:
+
+            - `False` (default): raise an error
+            - `True`: pass object through as-is
         eager_only: Whether to only allow eager objects.
         eager_or_interchange_only: Whether to only allow eager objects or objects which
             implement the Dataframe Interchange Protocol.
diff --git a/narwhals/utils.py b/narwhals/utils.py
index e1a7b812..ee522594 100644
--- a/narwhals/utils.py
+++ b/narwhals/utils.py
@@ -155,8 +155,7 @@ def validate_laziness(items: Iterable[Any]) -> None:
 
 
 def maybe_align_index(lhs: T, rhs: Series | BaseFrame[Any]) -> T:
-    """
-    Align `lhs` to the Index of `rhs`, if they're both pandas-like.
+    """Align `lhs` to the Index of `rhs`, if they're both pandas-like.
 
     Notes:
         This is only really intended for backwards-compatibility purposes,
@@ -244,8 +243,7 @@ def maybe_align_index(lhs: T, rhs: Series | BaseFrame[Any]) -> T:
 
 
 def maybe_get_index(obj: T) -> Any | None:
-    """
-    Get the index of a DataFrame or a Series, if it's pandas-like.
+    """Get the index of a DataFrame or a Series, if it's pandas-like.
 
     Notes:
         This is only really intended for backwards-compatibility purposes,
@@ -280,8 +278,7 @@ def maybe_set_index(
     *,
     index: Series | list[Series] | None = None,
 ) -> T:
-    """
-    Set the index of a DataFrame or a Series, if it's pandas-like.
+    """Set the index of a DataFrame or a Series, if it's pandas-like.
 
     Arguments:
         obj: object for which maybe set the index (can be either a Narwhals `DataFrame`
@@ -319,7 +316,6 @@ def maybe_set_index(
         4  1
         5  2
     """
-
     df_any = cast(Any, obj)
     native_obj = to_native(df_any)
 
@@ -365,8 +361,7 @@ def maybe_set_index(
 
 
 def maybe_reset_index(obj: T) -> T:
-    """
-    Reset the index to the default integer index of a DataFrame or a Series, if it's pandas-like.
+    """Reset the index to the default integer index of a DataFrame or a Series, if it's pandas-like.
 
     Notes:
         This is only really intended for backwards-compatibility purposes,
@@ -424,8 +419,7 @@ def _has_default_index(
 
 
 def maybe_convert_dtypes(obj: T, *args: bool, **kwargs: bool | str) -> T:
-    """
-    Convert columns or series to the best possible dtypes using dtypes supporting ``pd.NA``, if df is pandas-like.
+    """Convert columns or series to the best possible dtypes using dtypes supporting ``pd.NA``, if df is pandas-like.
 
     Arguments:
         obj: DataFrame or Series.
@@ -471,8 +465,7 @@ def maybe_convert_dtypes(obj: T, *args: bool, **kwargs: bool | str) -> T:
 
 
 def is_ordered_categorical(series: Series) -> bool:
-    """
-    Return whether indices of categories are semantically meaningful.
+    """Return whether indices of categories are semantically meaningful.
 
     This is a convenience function to accessing what would otherwise be
     the `is_ordered` property from the DataFrame Interchange Protocol,
@@ -547,8 +540,7 @@ def generate_unique_token(n_bytes: int, columns: list[str]) -> str:  # pragma: n
 
 
 def generate_temporary_column_name(n_bytes: int, columns: list[str]) -> str:
-    """Generates a unique token of specified `n_bytes` that is not present in the given
-    list of columns.
+    """Generates a unique column name that is not present in the given list of columns.
 
     It relies on [python secrets token_hex](https://docs.python.org/3/library/secrets.html#secrets.token_hex)
     function to return a string nbytes random bytes.
@@ -607,8 +599,7 @@ def is_sequence_but_not_str(sequence: Any) -> TypeGuard[Sequence[Any]]:
 
 
 def find_stacklevel() -> int:
-    """
-    Find the first place in the stack that is not inside narwhals.
+    """Find the first place in the stack that is not inside narwhals.
 
     Taken from:
     https://github.com/pandas-dev/pandas/blob/ab89c53f48df67709a533b6a95ce3d911871a0a8/pandas/util/_exceptions.py#L30-L51
@@ -647,8 +638,7 @@ def find_stacklevel() -> int:
 
 
 def issue_deprecation_warning(message: str, _version: str) -> None:
-    """
-    Issue a deprecation warning.
+    """Issue a deprecation warning.
 
     Parameters
     ----------
diff --git a/pyproject.toml b/pyproject.toml
index 9f20bcf7..05232bb0 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
 
 [project]
 name = "narwhals"
-version = "1.13.4"
+version = "1.13.5"
 authors = [
   { name="Marco Gorelli", email="33491632+MarcoGorelli@users.noreply.github.com" },
 ]
@@ -58,7 +58,13 @@ lint.ignore = [
   "ANN401",
   "C901",
   "COM812",
-  "D",
+  "D100",
+  "D101",
+  "D102",
+  "D103",
+  "D104",
+  "D105",
+  "D107",
   "DTZ001",
   "E501",
   "FIX",
diff --git a/utils/check_for_no_build_errors.py b/utils/check_for_no_build_errors.py
index 48b5a931..fa8caaa2 100644
--- a/utils/check_for_no_build_errors.py
+++ b/utils/check_for_no_build_errors.py
@@ -1,6 +1,6 @@
-"""
-Check that `output.txt` doesn't contain the string
-"exited with errors". If it does, exit with status 1.
+"""Check that `output.txt` doesn't contain the string "exited with errors".
+
+If it does, exit with status 1.
 
 This is just used in CI.
 """


Test patch:
diff --git a/tests/expr_and_series/cum_count_test.py b/tests/expr_and_series/cum_count_test.py
new file mode 100644
index 00000000..3b8e7e09
--- /dev/null
+++ b/tests/expr_and_series/cum_count_test.py
@@ -0,0 +1,41 @@
+from __future__ import annotations
+
+import pytest
+
+import narwhals.stable.v1 as nw
+from tests.utils import Constructor
+from tests.utils import ConstructorEager
+from tests.utils import assert_equal_data
+
+data = {
+    "a": ["x", "y", None, "z"],
+}
+
+
+def test_cum_count_expr(request: pytest.FixtureRequest, constructor: Constructor) -> None:
+    if "dask" in str(constructor):
+        request.applymarker(pytest.mark.xfail)
+
+    df = nw.from_native(constructor(data))
+    result = df.select(
+        cum_count=nw.col("a").cum_count(),
+        reverse_cum_count=nw.col("a").cum_count(reverse=True),
+    )
+    expected = {
+        "cum_count": [1, 2, 2, 3],
+        "reverse_cum_count": [3, 2, 1, 1],
+    }
+    assert_equal_data(result, expected)
+
+
+def test_cum_count_series(constructor_eager: ConstructorEager) -> None:
+    df = nw.from_native(constructor_eager(data), eager_only=True)
+    result = df.select(
+        cum_count=df["a"].cum_count(),
+        reverse_cum_count=df["a"].cum_count(reverse=True),
+    )
+    expected = {
+        "cum_count": [1, 2, 2, 3],
+        "reverse_cum_count": [3, 2, 1, 1],
+    }
+    assert_equal_data(result, expected)
diff --git a/tests/expr_and_series/cum_sum_test.py b/tests/expr_and_series/cum_sum_test.py
index b60e3606..5ea01a58 100644
--- a/tests/expr_and_series/cum_sum_test.py
+++ b/tests/expr_and_series/cum_sum_test.py
@@ -6,7 +6,7 @@ from tests.utils import ConstructorEager
 from tests.utils import assert_equal_data
 
 data = {
-    "a": [0, 1, 2, 3, 4],
+    "a": [0, 1, 2, None, 4],
     "b": [1, 2, 3, 5, 3],
     "c": [5, 4, 3, 2, 1],
 }
@@ -16,7 +16,7 @@ def test_cum_sum_simple(constructor: Constructor) -> None:
     df = nw.from_native(constructor(data))
     result = df.select(nw.col("a", "b", "c").cum_sum())
     expected = {
-        "a": [0, 1, 3, 6, 10],
+        "a": [0, 1, 3, None, 7],
         "b": [1, 3, 6, 11, 14],
         "c": [5, 9, 12, 14, 15],
     }
@@ -26,7 +26,7 @@ def test_cum_sum_simple(constructor: Constructor) -> None:
 def test_cum_sum_simple_series(constructor_eager: ConstructorEager) -> None:
     df = nw.from_native(constructor_eager(data), eager_only=True)
     expected = {
-        "a": [0, 1, 3, 6, 10],
+        "a": [0, 1, 3, None, 7],
         "b": [1, 3, 6, 11, 14],
         "c": [5, 9, 12, 14, 15],
     }
diff --git a/tests/hypothesis/getitem_test.py b/tests/hypothesis/getitem_test.py
index 17e653fd..f227bcd6 100644
--- a/tests/hypothesis/getitem_test.py
+++ b/tests/hypothesis/getitem_test.py
@@ -49,9 +49,7 @@ def string_slice(
     draw: st.DrawFn,
     strs: Sequence[str],
 ) -> slice:
-    """
-    Return slices such as `"a":`, `"a":"c"`, `"a":"c":2`, etc.
-    """
+    """Return slices such as `"a":`, `"a":"c"`, `"a":"c":2`, etc."""
     n_cols = len(strs)
     index_slice = draw(
         st.slices(n_cols).filter(
@@ -145,10 +143,7 @@ def test_getitem(
     constructor: Any,
     selector: Any,
 ) -> None:
-    """
-    Compare __getitem__ against polars.
-    """
-
+    """Compare __getitem__ against polars."""
     # TODO(PR - clean up): documenting current differences
     # These assume(...) lines each filter out a known difference.