Repository: narwhals-dev/narwhals

Issue:
[Bug]: `nw.all()` doesn't work in `sum_horizontal` if native df is `pd.DataFrame`
### Describe the bug

`nw.all()` doesn't work in `sum_horizontal` if native df is `pd.DataFrame`
It works well for `pl.DataFrame` and it works for `pd.DataFrame` with columns names.

### Steps or code to reproduce the bug

```
import pandas as pd
import polars as pl
import narwhals as nw 

data = {"a": [1, 2, 3], "b": [10, 20, 30]}
df_pd = pd.DataFrame(data)
df_pl = pl.DataFrame(data)

#this works fine
pl_nw = nw.from_native(df_pl)
hor_sum = pl_nw.select(nw.sum_horizontal(nw.all()))
nw.to_native(hor_sum)
a
i64
11
22
33


#this also works fine
pd_nw = nw.from_native(df_pd)
hor_sum = pd_nw.select(nw.sum_horizontal("a", "b"))
nw.to_native(hor_sum)
	a
0	11
1	22
2	33

#this doesn't work
pd_nw = nw.from_native(df_pd)
hor_sum = pd_nw.select(nw.sum_horizontal(nw.all()))
nw.to_native(hor_sum)
	a	b
0	1	10
1	2	20
2	3	30
```

### Expected results

```
pd_nw = nw.from_native(df_pd)
hor_sum = pd_nw.select(nw.sum_horizontal(nw.all()))
nw.to_native(hor_sum)

        a
0	11
1	22
2	33
```

### Actual results

```
pd_nw = nw.from_native(df_pd)
hor_sum = pd_nw.select(nw.sum_horizontal(nw.all()))
nw.to_native(hor_sum)
	a	b
0	1	10
1	2	20
2	3	30
```

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

```shell
System:
    python: 3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]
executable: /usr/bin/python3
   machine: Linux-6.8.0-45-generic-x86_64-with-glibc2.35

Python dependencies:
     narwhals: 1.8.3
       pandas: 2.2.3
       polars: 1.8.1
         cudf: 
        modin: 
      pyarrow: 16.1.0
        numpy: 2.1.1
```


### Relevant log output

_No response_

Discussion:
Cheukting: @MarcoGorelli we still have to find a way to make it work for Dask, but do you want to have a look at the implementation of `pandas_like` to see if it is what you was thinking?

Patch:
diff --git a/docs/api-reference/series.md b/docs/api-reference/series.md
index 5adaa57a..e8572dda 100644
--- a/docs/api-reference/series.md
+++ b/docs/api-reference/series.md
@@ -43,6 +43,7 @@
         - null_count
         - pipe
         - quantile
+        - rename
         - round
         - sample
         - scatter
diff --git a/narwhals/_arrow/namespace.py b/narwhals/_arrow/namespace.py
index f4c3c9ee..e7cbbae2 100644
--- a/narwhals/_arrow/namespace.py
+++ b/narwhals/_arrow/namespace.py
@@ -13,6 +13,7 @@ from narwhals._arrow.selectors import ArrowSelectorNamespace
 from narwhals._arrow.series import ArrowSeries
 from narwhals._arrow.utils import horizontal_concat
 from narwhals._arrow.utils import vertical_concat
+from narwhals._expression_parsing import combine_root_names
 from narwhals._expression_parsing import parse_into_exprs
 from narwhals.utils import Implementation
 
@@ -175,15 +176,54 @@ class ArrowNamespace:
         )
 
     def all_horizontal(self, *exprs: IntoArrowExpr) -> ArrowExpr:
-        return reduce(lambda x, y: x & y, parse_into_exprs(*exprs, namespace=self))
+        parsed_exprs = parse_into_exprs(*exprs, namespace=self)
+
+        def func(df: ArrowDataFrame) -> list[ArrowSeries]:
+            series = []
+            for _expr in parsed_exprs:
+                series.extend(list(_expr._call(df)))
+            return [reduce(lambda x, y: x & y, series)]
+
+        return self._create_expr_from_callable(
+            func=func,
+            depth=max(x._depth for x in parsed_exprs) + 1,
+            function_name="all_horizontal",
+            root_names=combine_root_names(parsed_exprs),
+            output_names=parsed_exprs[0]._output_names,
+        )
 
     def any_horizontal(self, *exprs: IntoArrowExpr) -> ArrowExpr:
-        return reduce(lambda x, y: x | y, parse_into_exprs(*exprs, namespace=self))
+        parsed_exprs = parse_into_exprs(*exprs, namespace=self)
+
+        def func(df: ArrowDataFrame) -> list[ArrowSeries]:
+            series = []
+            for _expr in parsed_exprs:
+                series.extend(list(_expr._call(df)))
+            return [reduce(lambda x, y: x | y, series)]
+
+        return self._create_expr_from_callable(
+            func=func,
+            depth=max(x._depth for x in parsed_exprs) + 1,
+            function_name="any_horizontal",
+            root_names=combine_root_names(parsed_exprs),
+            output_names=parsed_exprs[0]._output_names,
+        )
 
     def sum_horizontal(self, *exprs: IntoArrowExpr) -> ArrowExpr:
-        return reduce(
-            lambda x, y: x + y,
-            [expr.fill_null(0) for expr in parse_into_exprs(*exprs, namespace=self)],
+        parsed_exprs = parse_into_exprs(*exprs, namespace=self)
+
+        def func(df: ArrowDataFrame) -> list[ArrowSeries]:
+            series = []
+            for _expr in parsed_exprs:
+                series.extend([_series.fill_null(0) for _series in _expr._call(df)])
+            return [reduce(lambda x, y: x + y, series)]
+
+        return self._create_expr_from_callable(
+            func=func,
+            depth=max(x._depth for x in parsed_exprs) + 1,
+            function_name="sum_horizontal",
+            root_names=combine_root_names(parsed_exprs),
+            output_names=parsed_exprs[0]._output_names,
         )
 
     def mean_horizontal(self, *exprs: IntoArrowExpr) -> IntoArrowExpr:
diff --git a/narwhals/_dask/group_by.py b/narwhals/_dask/group_by.py
index d5fbaaf9..d79c95d7 100644
--- a/narwhals/_dask/group_by.py
+++ b/narwhals/_dask/group_by.py
@@ -47,6 +47,7 @@ class DaskLazyGroupBy:
         self._grouped = self._df._native_frame.groupby(
             list(self._keys),
             dropna=False,
+            observed=True,
         )
 
     def agg(
diff --git a/narwhals/_dask/namespace.py b/narwhals/_dask/namespace.py
index ead7cf10..0a1314a5 100644
--- a/narwhals/_dask/namespace.py
+++ b/narwhals/_dask/namespace.py
@@ -14,6 +14,7 @@ from narwhals._dask.expr import DaskExpr
 from narwhals._dask.selectors import DaskSelectorNamespace
 from narwhals._dask.utils import reverse_translate_dtype
 from narwhals._dask.utils import validate_comparand
+from narwhals._expression_parsing import combine_root_names
 from narwhals._expression_parsing import parse_into_exprs
 
 if TYPE_CHECKING:
@@ -52,7 +53,7 @@ class DaskNamespace:
         self._backend_version = backend_version
 
     def all(self) -> DaskExpr:
-        def func(df: DaskLazyFrame) -> list[Any]:
+        def func(df: DaskLazyFrame) -> list[dask_expr.Series]:
             return [df._native_frame.loc[:, column_name] for column_name in df.columns]
 
         return DaskExpr(
@@ -125,7 +126,7 @@ class DaskNamespace:
         import dask.dataframe as dd  # ignore-banned-import
         import pandas as pd  # ignore-banned-import
 
-        def func(df: DaskLazyFrame) -> list[Any]:
+        def func(df: DaskLazyFrame) -> list[dask_expr.Series]:
             if not df.columns:
                 return [
                     dd.from_pandas(
@@ -147,15 +148,60 @@ class DaskNamespace:
         )
 
     def all_horizontal(self, *exprs: IntoDaskExpr) -> DaskExpr:
-        return reduce(lambda x, y: x & y, parse_into_exprs(*exprs, namespace=self))
+        parsed_exprs = parse_into_exprs(*exprs, namespace=self)
+
+        def func(df: DaskLazyFrame) -> list[dask_expr.Series]:
+            series = []
+            for _expr in parsed_exprs:
+                series.extend(list(_expr._call(df)))
+            return [reduce(lambda x, y: x & y, series).rename(series[0].name)]
+
+        return DaskExpr(
+            call=func,
+            depth=max(x._depth for x in parsed_exprs) + 1,
+            function_name="all_horizontal",
+            root_names=combine_root_names(parsed_exprs),
+            output_names=parsed_exprs[0]._output_names,
+            returns_scalar=False,
+            backend_version=self._backend_version,
+        )
 
     def any_horizontal(self, *exprs: IntoDaskExpr) -> DaskExpr:
-        return reduce(lambda x, y: x | y, parse_into_exprs(*exprs, namespace=self))
+        parsed_exprs = parse_into_exprs(*exprs, namespace=self)
+
+        def func(df: DaskLazyFrame) -> list[dask_expr.Series]:
+            series = []
+            for _expr in parsed_exprs:
+                series.extend(list(_expr._call(df)))
+            return [reduce(lambda x, y: x | y, series).rename(series[0].name)]
+
+        return DaskExpr(
+            call=func,
+            depth=max(x._depth for x in parsed_exprs) + 1,
+            function_name="any_horizontal",
+            root_names=combine_root_names(parsed_exprs),
+            output_names=parsed_exprs[0]._output_names,
+            returns_scalar=False,
+            backend_version=self._backend_version,
+        )
 
     def sum_horizontal(self, *exprs: IntoDaskExpr) -> DaskExpr:
-        return reduce(
-            lambda x, y: x + y,
-            [expr.fill_null(0) for expr in parse_into_exprs(*exprs, namespace=self)],
+        parsed_exprs = parse_into_exprs(*exprs, namespace=self)
+
+        def func(df: DaskLazyFrame) -> list[dask_expr.Series]:
+            series = []
+            for _expr in parsed_exprs:
+                series.extend([_series.fillna(0) for _series in _expr._call(df)])
+            return [reduce(lambda x, y: x + y, series).rename(series[0].name)]
+
+        return DaskExpr(
+            call=func,
+            depth=max(x._depth for x in parsed_exprs) + 1,
+            function_name="sum_horizontal",
+            root_names=combine_root_names(parsed_exprs),
+            output_names=parsed_exprs[0]._output_names,
+            returns_scalar=False,
+            backend_version=self._backend_version,
         )
 
     def concat(
@@ -263,7 +309,7 @@ class DaskWhen:
         self._otherwise_value = otherwise_value
         self._returns_scalar = returns_scalar
 
-    def __call__(self, df: DaskLazyFrame) -> list[Any]:
+    def __call__(self, df: DaskLazyFrame) -> list[dask_expr.Series]:
         from narwhals._dask.namespace import DaskNamespace
         from narwhals._expression_parsing import parse_into_expr
 
diff --git a/narwhals/_expression_parsing.py b/narwhals/_expression_parsing.py
index 3ccf906f..35d0f7ec 100644
--- a/narwhals/_expression_parsing.py
+++ b/narwhals/_expression_parsing.py
@@ -6,6 +6,7 @@ from __future__ import annotations
 from copy import copy
 from typing import TYPE_CHECKING
 from typing import Any
+from typing import Sequence
 from typing import TypeVar
 from typing import Union
 from typing import cast
@@ -306,3 +307,15 @@ def is_simple_aggregation(expr: CompliantExpr) -> bool:
     because then, we can use a fastpath in pandas.
     """
     return expr._depth < 2
+
+
+def combine_root_names(parsed_exprs: Sequence[CompliantExpr]) -> list[str] | None:
+    root_names = copy(parsed_exprs[0]._root_names)
+    for arg in parsed_exprs[1:]:
+        if root_names is not None and hasattr(arg, "__narwhals_expr__"):
+            if arg._root_names is not None:
+                root_names.extend(arg._root_names)
+            else:
+                root_names = None
+                break
+    return root_names
diff --git a/narwhals/_pandas_like/group_by.py b/narwhals/_pandas_like/group_by.py
index a0ee03f7..55c038f9 100644
--- a/narwhals/_pandas_like/group_by.py
+++ b/narwhals/_pandas_like/group_by.py
@@ -40,6 +40,7 @@ class PandasLikeGroupBy:
                 list(self._keys),
                 sort=False,
                 as_index=True,
+                observed=True,
             )
         else:
             self._grouped = self._df._native_frame.groupby(
@@ -47,6 +48,7 @@ class PandasLikeGroupBy:
                 sort=False,
                 as_index=True,
                 dropna=False,
+                observed=True,
             )
 
     def agg(
diff --git a/narwhals/_pandas_like/namespace.py b/narwhals/_pandas_like/namespace.py
index 804c0e8a..f58b07a4 100644
--- a/narwhals/_pandas_like/namespace.py
+++ b/narwhals/_pandas_like/namespace.py
@@ -8,6 +8,7 @@ from typing import Iterable
 from typing import cast
 
 from narwhals import dtypes
+from narwhals._expression_parsing import combine_root_names
 from narwhals._expression_parsing import parse_into_exprs
 from narwhals._pandas_like.dataframe import PandasLikeDataFrame
 from narwhals._pandas_like.expr import PandasLikeExpr
@@ -210,16 +211,55 @@ class PandasLikeNamespace:
 
     # --- horizontal ---
     def sum_horizontal(self, *exprs: IntoPandasLikeExpr) -> PandasLikeExpr:
-        return reduce(
-            lambda x, y: x + y,
-            [expr.fill_null(0) for expr in parse_into_exprs(*exprs, namespace=self)],
+        parsed_exprs = parse_into_exprs(*exprs, namespace=self)
+
+        def func(df: PandasLikeDataFrame) -> list[PandasLikeSeries]:
+            series = []
+            for _expr in parsed_exprs:
+                series.extend([_series.fill_null(0) for _series in _expr._call(df)])
+            return [reduce(lambda x, y: x + y, series)]
+
+        return self._create_expr_from_callable(
+            func=func,
+            depth=max(x._depth for x in parsed_exprs) + 1,
+            function_name="sum_horizontal",
+            root_names=combine_root_names(parsed_exprs),
+            output_names=parsed_exprs[0]._output_names,
         )
 
     def all_horizontal(self, *exprs: IntoPandasLikeExpr) -> PandasLikeExpr:
-        return reduce(lambda x, y: x & y, parse_into_exprs(*exprs, namespace=self))
+        parsed_exprs = parse_into_exprs(*exprs, namespace=self)
+
+        def func(df: PandasLikeDataFrame) -> list[PandasLikeSeries]:
+            series = []
+            for _expr in parsed_exprs:
+                series.extend(list(_expr._call(df)))
+            return [reduce(lambda x, y: x & y, series)]
+
+        return self._create_expr_from_callable(
+            func=func,
+            depth=max(x._depth for x in parsed_exprs) + 1,
+            function_name="all_horizontal",
+            root_names=combine_root_names(parsed_exprs),
+            output_names=parsed_exprs[0]._output_names,
+        )
 
     def any_horizontal(self, *exprs: IntoPandasLikeExpr) -> PandasLikeExpr:
-        return reduce(lambda x, y: x | y, parse_into_exprs(*exprs, namespace=self))
+        parsed_exprs = parse_into_exprs(*exprs, namespace=self)
+
+        def func(df: PandasLikeDataFrame) -> list[PandasLikeSeries]:
+            series = []
+            for _expr in parsed_exprs:
+                series.extend(list(_expr._call(df)))
+            return [reduce(lambda x, y: x | y, series)]
+
+        return self._create_expr_from_callable(
+            func=func,
+            depth=max(x._depth for x in parsed_exprs) + 1,
+            function_name="any_horizontal",
+            root_names=combine_root_names(parsed_exprs),
+            output_names=parsed_exprs[0]._output_names,
+        )
 
     def mean_horizontal(self, *exprs: IntoPandasLikeExpr) -> PandasLikeExpr:
         pandas_like_exprs = parse_into_exprs(*exprs, namespace=self)
diff --git a/narwhals/expr.py b/narwhals/expr.py
index 8ff626d5..9f5e2d57 100644
--- a/narwhals/expr.py
+++ b/narwhals/expr.py
@@ -4087,6 +4087,9 @@ def sum_horizontal(*exprs: IntoExpr | Iterable[IntoExpr]) -> Expr:
         │ 3   │
         └─────┘
     """
+    if not exprs:
+        msg = "At least one expression must be passed to `sum_horizontal`"
+        raise ValueError(msg)
     return Expr(
         lambda plx: plx.sum_horizontal(
             *[extract_compliant(plx, v) for v in flatten(exprs)]
@@ -4218,6 +4221,9 @@ def all_horizontal(*exprs: IntoExpr | Iterable[IntoExpr]) -> Expr:
         │ null  ┆ null  ┆ null  │
         └───────┴───────┴───────┘
     """
+    if not exprs:
+        msg = "At least one expression must be passed to `all_horizontal`"
+        raise ValueError(msg)
     return Expr(
         lambda plx: plx.all_horizontal(
             *[extract_compliant(plx, v) for v in flatten(exprs)]
@@ -4331,6 +4337,9 @@ def any_horizontal(*exprs: IntoExpr | Iterable[IntoExpr]) -> Expr:
         │ null  ┆ null  ┆ null  │
         └───────┴───────┴───────┘
     """
+    if not exprs:
+        msg = "At least one expression must be passed to `any_horizontal`"
+        raise ValueError(msg)
     return Expr(
         lambda plx: plx.any_horizontal(
             *[extract_compliant(plx, v) for v in flatten(exprs)]
@@ -4384,6 +4393,9 @@ def mean_horizontal(*exprs: IntoExpr | Iterable[IntoExpr]) -> Expr:
         │ 3.0 │
         └─────┘
     """
+    if not exprs:
+        msg = "At least one expression must be passed to `mean_horizontal`"
+        raise ValueError(msg)
     return Expr(
         lambda plx: plx.mean_horizontal(
             *[extract_compliant(plx, v) for v in flatten(exprs)]
diff --git a/narwhals/series.py b/narwhals/series.py
index ae78eef7..ab8b012b 100644
--- a/narwhals/series.py
+++ b/narwhals/series.py
@@ -1187,10 +1187,12 @@ class Series:
         Examples:
             >>> import pandas as pd
             >>> import polars as pl
+            >>> import pyarrow as pa
             >>> import narwhals as nw
             >>> s = [1, 2, 3]
             >>> s_pd = pd.Series(s, name="foo")
             >>> s_pl = pl.Series("foo", s)
+            >>> s_pa = pa.chunked_array([s])
 
             We define a library agnostic function:
 
@@ -1198,7 +1200,7 @@ class Series:
             ... def func(s):
             ...     return s.alias("bar")
 
-            We can then pass either pandas or Polars to `func`:
+            We can then pass any supported library such as pandas, Polars, or PyArrow:
 
             >>> func(s_pd)
             0    1
@@ -1213,9 +1215,70 @@ class Series:
                2
                3
             ]
+            >>> func(s_pa)  # doctest: +ELLIPSIS
+            <pyarrow.lib.ChunkedArray object at 0x...>
+            [
+              [
+                1,
+                2,
+                3
+              ]
+            ]
         """
         return self._from_compliant_series(self._compliant_series.alias(name=name))
 
+    def rename(self, name: str) -> Self:
+        """
+        Rename the Series.
+
+        Alias for `Series.alias()`.
+
+        Arguments:
+            name: The new name.
+
+        Examples:
+            >>> import pandas as pd
+            >>> import polars as pl
+            >>> import pyarrow as pa
+            >>> import narwhals as nw
+            >>> s = [1, 2, 3]
+            >>> s_pd = pd.Series(s, name="foo")
+            >>> s_pl = pl.Series("foo", s)
+            >>> s_pa = pa.chunked_array([s])
+
+            We define a library agnostic function:
+
+            >>> @nw.narwhalify
+            ... def func(s):
+            ...     return s.rename("bar")
+
+            We can then pass any supported library such as pandas, Polars, or PyArrow:
+
+            >>> func(s_pd)
+            0    1
+            1    2
+            2    3
+            Name: bar, dtype: int64
+            >>> func(s_pl)  # doctest: +NORMALIZE_WHITESPACE
+            shape: (3,)
+            Series: 'bar' [i64]
+            [
+               1
+               2
+               3
+            ]
+            >>> func(s_pa)  # doctest: +ELLIPSIS
+            <pyarrow.lib.ChunkedArray object at 0x...>
+            [
+              [
+                1,
+                2,
+                3
+              ]
+            ]
+        """
+        return self.alias(name=name)
+
     def sort(self, *, descending: bool = False, nulls_last: bool = False) -> Self:
         """
         Sort this Series. Place null values first.
diff --git a/utils/check_api_reference.py b/utils/check_api_reference.py
index d11c2bc9..410956e0 100644
--- a/utils/check_api_reference.py
+++ b/utils/check_api_reference.py
@@ -12,22 +12,23 @@ ret = 0
 NAMESPACES = {"dt", "str", "cat", "name"}
 EXPR_ONLY_METHODS = {"over"}
 SERIES_ONLY_METHODS = {
-    "to_arrow",
-    "to_dummies",
-    "to_pandas",
-    "to_list",
-    "to_numpy",
     "dtype",
-    "name",
-    "shape",
-    "to_frame",
     "is_empty",
     "is_sorted",
-    "value_counts",
-    "zip_with",
     "item",
+    "name",
+    "rename",
     "scatter",
+    "shape",
+    "to_arrow",
+    "to_dummies",
+    "to_frame",
+    "to_list",
     "to_native",
+    "to_numpy",
+    "to_pandas",
+    "value_counts",
+    "zip_with",
     "__iter__",
 }
 BASE_DTYPES = {"NumericType", "DType", "TemporalType"}


Test patch:
diff --git a/tests/expr_and_series/all_horizontal_test.py b/tests/expr_and_series/all_horizontal_test.py
index bc9f8035..98dc9f9f 100644
--- a/tests/expr_and_series/all_horizontal_test.py
+++ b/tests/expr_and_series/all_horizontal_test.py
@@ -1,8 +1,10 @@
 from typing import Any
 
+import polars as pl
 import pytest
 
 import narwhals.stable.v1 as nw
+from narwhals.utils import parse_version
 from tests.utils import Constructor
 from tests.utils import compare_dicts
 
@@ -31,3 +33,57 @@ def test_allh_series(constructor_eager: Any) -> None:
 
     expected = {"all": [False, False, True]}
     compare_dicts(result, expected)
+
+
+def test_allh_all(constructor: Constructor) -> None:
+    data = {
+        "a": [False, False, True],
+        "b": [False, True, True],
+    }
+    df = nw.from_native(constructor(data))
+    result = df.select(all=nw.all_horizontal(nw.all()))
+    expected = {"all": [False, False, True]}
+    compare_dicts(result, expected)
+    result = df.select(nw.all_horizontal(nw.all()))
+    expected = {"a": [False, False, True]}
+    compare_dicts(result, expected)
+
+
+def test_allh_nth(constructor: Constructor, request: pytest.FixtureRequest) -> None:
+    if "polars" in str(constructor) and parse_version(pl.__version__) < (1, 0):
+        request.applymarker(pytest.mark.xfail)
+    data = {
+        "a": [False, False, True],
+        "b": [False, True, True],
+    }
+    df = nw.from_native(constructor(data))
+    result = df.select(nw.all_horizontal(nw.nth(0, 1)))
+    expected = {"a": [False, False, True]}
+    compare_dicts(result, expected)
+    result = df.select(nw.all_horizontal(nw.col("a"), nw.nth(0)))
+    expected = {"a": [False, False, True]}
+    compare_dicts(result, expected)
+
+
+def test_horizontal_expressions_emtpy(constructor: Constructor) -> None:
+    data = {
+        "a": [False, False, True],
+        "b": [False, True, True],
+    }
+    df = nw.from_native(constructor(data))
+    with pytest.raises(
+        ValueError, match=r"At least one expression must be passed.*all_horizontal"
+    ):
+        df.select(nw.all_horizontal())
+    with pytest.raises(
+        ValueError, match=r"At least one expression must be passed.*any_horizontal"
+    ):
+        df.select(nw.any_horizontal())
+    with pytest.raises(
+        ValueError, match=r"At least one expression must be passed.*mean_horizontal"
+    ):
+        df.select(nw.mean_horizontal())
+    with pytest.raises(
+        ValueError, match=r"At least one expression must be passed.*sum_horizontal"
+    ):
+        df.select(nw.sum_horizontal())
diff --git a/tests/expr_and_series/any_horizontal_test.py b/tests/expr_and_series/any_horizontal_test.py
index 1b6dfd48..cd360bf6 100644
--- a/tests/expr_and_series/any_horizontal_test.py
+++ b/tests/expr_and_series/any_horizontal_test.py
@@ -19,3 +19,17 @@ def test_anyh(constructor: Constructor, expr1: Any, expr2: Any) -> None:
 
     expected = {"any": [False, True, True]}
     compare_dicts(result, expected)
+
+
+def test_anyh_all(constructor: Constructor) -> None:
+    data = {
+        "a": [False, False, True],
+        "b": [False, True, True],
+    }
+    df = nw.from_native(constructor(data))
+    result = df.select(any=nw.any_horizontal(nw.all()))
+    expected = {"any": [False, True, True]}
+    compare_dicts(result, expected)
+    result = df.select(nw.any_horizontal(nw.all()))
+    expected = {"a": [False, True, True]}
+    compare_dicts(result, expected)
diff --git a/tests/expr_and_series/sum_horizontal_test.py b/tests/expr_and_series/sum_horizontal_test.py
index bfaab723..e9e1e4a3 100644
--- a/tests/expr_and_series/sum_horizontal_test.py
+++ b/tests/expr_and_series/sum_horizontal_test.py
@@ -28,3 +28,18 @@ def test_sumh_nullable(constructor: Constructor) -> None:
     df = nw.from_native(constructor(data))
     result = df.select(hsum=nw.sum_horizontal("a", "b"))
     compare_dicts(result, expected)
+
+
+def test_sumh_all(constructor: Constructor) -> None:
+    data = {"a": [1, 2, 3], "b": [10, 20, 30]}
+    df = nw.from_native(constructor(data))
+    result = df.select(nw.sum_horizontal(nw.all()))
+    expected = {
+        "a": [11, 22, 33],
+    }
+    compare_dicts(result, expected)
+    result = df.select(c=nw.sum_horizontal(nw.all()))
+    expected = {
+        "c": [11, 22, 33],
+    }
+    compare_dicts(result, expected)
diff --git a/tests/series_only/alias_rename_test.py b/tests/series_only/alias_rename_test.py
new file mode 100644
index 00000000..4fa8a999
--- /dev/null
+++ b/tests/series_only/alias_rename_test.py
@@ -0,0 +1,13 @@
+import narwhals as nw
+from tests.utils import Constructor
+from tests.utils import compare_dicts
+
+
+def test_alias_rename(constructor_eager: Constructor) -> None:
+    data = [1, 2, 3]
+    expected = {"bar": data}
+    series = nw.from_native(constructor_eager({"foo": data}), eager_only=True)["foo"]
+    result = series.alias("bar").to_frame()
+    compare_dicts(result, expected)
+    result = series.rename("bar").to_frame()
+    compare_dicts(result, expected)
diff --git a/tests/test_group_by.py b/tests/test_group_by.py
index 666322c7..90cb48c2 100644
--- a/tests/test_group_by.py
+++ b/tests/test_group_by.py
@@ -260,3 +260,27 @@ def test_no_agg(constructor: Constructor) -> None:
 
     expected = {"a": [1, 3], "b": [4, 6]}
     compare_dicts(result, expected)
+
+
+def test_group_by_categorical(
+    constructor: Constructor, request: pytest.FixtureRequest
+) -> None:
+    if "pyarrow_table" in str(constructor) and parse_version(pa.__version__) < (
+        15,
+        0,
+        0,
+    ):  # pragma: no cover
+        request.applymarker(pytest.mark.xfail)
+
+    data = {"g1": ["a", "a", "b", "b"], "g2": ["x", "y", "x", "z"], "x": [1, 2, 3, 4]}
+    df = nw.from_native(constructor(data))
+    result = (
+        df.with_columns(
+            g1=nw.col("g1").cast(nw.Categorical()),
+            g2=nw.col("g2").cast(nw.Categorical()),
+        )
+        .group_by(["g1", "g2"])
+        .agg(nw.col("x").sum())
+        .sort("x")
+    )
+    compare_dicts(result, data)