Repository: narwhals-dev/narwhals (python)

Issue:
test: xfail some tests for cuDF (release blocker)
Currently these tests are failing for cuDF:

```
FAILED tests/expr_and_series/rolling_mean_test.py::test_rolling_mean_expr_lazy_grouped[cudf-expected_a0-2-None-False] - NotImplementedError: Unsupported function: rolling_mean in `over` context.
FAILED tests/expr_and_series/rolling_mean_test.py::test_rolling_mean_expr_lazy_grouped[cudf-expected_a1-2-2-False] - NotImplementedError: Unsupported function: rolling_mean in `over` context.
FAILED tests/expr_and_series/rolling_mean_test.py::test_rolling_mean_expr_lazy_grouped[cudf-expected_a2-3-2-False] - NotImplementedError: Unsupported function: rolling_mean in `over` context.
FAILED tests/expr_and_series/rolling_mean_test.py::test_rolling_mean_expr_lazy_grouped[cudf-expected_a3-3-1-False] - NotImplementedError: Unsupported function: rolling_mean in `over` context.
FAILED tests/expr_and_series/rolling_std_test.py::test_rolling_std_expr_lazy_grouped[cudf-expected_a0-2-None-False-0] - NotImplementedError: Unsupported function: rolling_std in `over` context.
FAILED tests/expr_and_series/rolling_std_test.py::test_rolling_std_expr_lazy_grouped[cudf-expected_a1-2-2-False-1] - NotImplementedError: Unsupported function: rolling_std in `over` context.
FAILED tests/expr_and_series/rolling_std_test.py::test_rolling_std_expr_lazy_grouped[cudf-expected_a2-3-2-False-1] - NotImplementedError: Unsupported function: rolling_std in `over` context.
FAILED tests/expr_and_series/rolling_std_test.py::test_rolling_std_expr_lazy_grouped[cudf-expected_a3-3-1-False-0] - NotImplementedError: Unsupported function: rolling_std in `over` context.
FAILED tests/expr_and_series/rolling_var_test.py::test_rolling_var_expr_lazy_grouped[cudf-expected_a0-2-None-False-0] - NotImplementedError: Unsupported function: rolling_var in `over` context.
FAILED tests/expr_and_series/rolling_var_test.py::test_rolling_var_expr_lazy_grouped[cudf-expected_a1-2-2-False-1] - NotImplementedError: Unsupported function: rolling_var in `over` context.
FAILED tests/expr_and_series/rolling_var_test.py::test_rolling_var_expr_lazy_grouped[cudf-expected_a2-3-2-False-1] - NotImplementedError: Unsupported function: rolling_var in `over` context.
FAILED tests/expr_and_series/rolling_var_test.py::test_rolling_var_expr_lazy_grouped[cudf-expected_a3-3-1-False-0] - NotImplementedError: Unsupported function: rolling_var in `over` context.
```

We're probably just missing `'cudf'` in the list of xfails for those tests

If anyone fancies making a PR, this would unblock the next release 🚀 

Pull request:
ci: fixup cudf test failures, support rolling_std/rolling_var/rolling_mean for pandas in grouped context
closes #2320 

<!--
# Thanks for contributing a pull request! 
## Please make sure you see our contribution guidelines: https://github.com/narwhals-dev/narwhals/blob/main/CONTRIBUTING.md
-->

## What type of PR is this? (check all applicable)

- [ ] 💾 Refactor
- [ ] ✨ Feature
- [ ] 🐛 Bug Fix
- [ ] 🔧 Optimization
- [ ] 📝 Documentation
- [ ] ✅ Test
- [ ] 🐳 Other

## Related issues

- Related issue #\<issue number\>
- Closes #\<issue number\>

## Checklist

- [ ] 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/narwhals/_compliant/expr.py b/narwhals/_compliant/expr.py
index bb0582c1..910b110a 100644
--- a/narwhals/_compliant/expr.py
+++ b/narwhals/_compliant/expr.py
@@ -775,9 +775,7 @@ class EagerExpr(
     def is_finite(self) -> Self:
         return self._reuse_series("is_finite")
 
-    def rolling_mean(
-        self, window_size: int, *, min_samples: int | None, center: bool
-    ) -> Self:
+    def rolling_mean(self, window_size: int, *, min_samples: int, center: bool) -> Self:
         return self._reuse_series(
             "rolling_mean",
             window_size=window_size,
@@ -786,7 +784,7 @@ class EagerExpr(
         )
 
     def rolling_std(
-        self, window_size: int, *, min_samples: int | None, center: bool, ddof: int
+        self, window_size: int, *, min_samples: int, center: bool, ddof: int
     ) -> Self:
         return self._reuse_series(
             "rolling_std",
@@ -802,7 +800,7 @@ class EagerExpr(
         )
 
     def rolling_var(
-        self, window_size: int, *, min_samples: int | None, center: bool, ddof: int
+        self, window_size: int, *, min_samples: int, center: bool, ddof: int
     ) -> Self:
         return self._reuse_series(
             "rolling_var",
diff --git a/narwhals/_pandas_like/expr.py b/narwhals/_pandas_like/expr.py
index 085aaac2..3f041234 100644
--- a/narwhals/_pandas_like/expr.py
+++ b/narwhals/_pandas_like/expr.py
@@ -33,6 +33,9 @@ WINDOW_FUNCTIONS_TO_PANDAS_EQUIVALENT = {
     # So, instead of using "cumcount" we use "cumsum" on notna() to get the same result
     "cum_count": "cumsum",
     "rolling_sum": "sum",
+    "rolling_mean": "mean",
+    "rolling_std": "std",
+    "rolling_var": "var",
     "shift": "shift",
     "rank": "rank",
     "diff": "diff",
@@ -265,7 +268,12 @@ class PandasLikeExpr(EagerExpr["PandasLikeDataFrame", PandasLikeSeries]):
                         list(output_names)
                     ].rolling(**pandas_kwargs)
                     assert pandas_function_name is not None  # help mypy  # noqa: S101
-                    res_native = getattr(rolling, pandas_function_name)()
+                    if pandas_function_name in {"std", "var"}:
+                        res_native = getattr(rolling, pandas_function_name)(
+                            ddof=self._call_kwargs["ddof"]
+                        )
+                    else:
+                        res_native = getattr(rolling, pandas_function_name)()
                 else:
                     res_native = df._native_frame.groupby(partition_by)[
                         list(output_names)
@@ -317,6 +325,44 @@ class PandasLikeExpr(EagerExpr["PandasLikeDataFrame", PandasLikeSeries]):
             },
         )
 
+    def rolling_mean(
+        self: Self, window_size: int, *, min_samples: int, center: bool
+    ) -> Self:
+        return self._reuse_series(
+            "rolling_mean",
+            call_kwargs={
+                "window_size": window_size,
+                "min_samples": min_samples,
+                "center": center,
+            },
+        )
+
+    def rolling_std(
+        self: Self, window_size: int, *, min_samples: int, center: bool, ddof: int
+    ) -> Self:
+        return self._reuse_series(
+            "rolling_std",
+            call_kwargs={
+                "window_size": window_size,
+                "min_samples": min_samples,
+                "center": center,
+                "ddof": ddof,
+            },
+        )
+
+    def rolling_var(
+        self: Self, window_size: int, *, min_samples: int, center: bool, ddof: int
+    ) -> Self:
+        return self._reuse_series(
+            "rolling_var",
+            call_kwargs={
+                "window_size": window_size,
+                "min_samples": min_samples,
+                "center": center,
+                "ddof": ddof,
+            },
+        )
+
     def rank(
         self: Self,
         method: Literal["average", "min", "max", "dense", "ordinal"],


Test patch:
diff --git a/tests/expr_and_series/rolling_std_test.py b/tests/expr_and_series/rolling_std_test.py
index 4c352313..797955ea 100644
--- a/tests/expr_and_series/rolling_std_test.py
+++ b/tests/expr_and_series/rolling_std_test.py
@@ -7,6 +7,7 @@ import pytest
 
 import narwhals.stable.v1 as nw
 from tests.utils import DUCKDB_VERSION
+from tests.utils import PANDAS_VERSION
 from tests.utils import POLARS_VERSION
 from tests.utils import Constructor
 from tests.utils import ConstructorEager
@@ -319,12 +320,12 @@ def test_rolling_std_expr_lazy_grouped(
     center: bool,
     ddof: int,
 ) -> None:
-    if ("polars" in str(constructor) and POLARS_VERSION < (1, 10)) or (
-        "duckdb" in str(constructor) and DUCKDB_VERSION < (1, 3)
+    if (
+        ("polars" in str(constructor) and POLARS_VERSION < (1, 10))
+        or ("duckdb" in str(constructor) and DUCKDB_VERSION < (1, 3))
+        or ("pandas" in str(constructor) and PANDAS_VERSION < (1, 2))
     ):
         pytest.skip()
-    if "pandas" in str(constructor):
-        pytest.skip()
     if any(x in str(constructor) for x in ("dask", "pyarrow_table")):
         request.applymarker(pytest.mark.xfail)
     if "cudf" in str(constructor) and center:
diff --git a/tests/expr_and_series/rolling_var_test.py b/tests/expr_and_series/rolling_var_test.py
index 09383e7d..8460e710 100644
--- a/tests/expr_and_series/rolling_var_test.py
+++ b/tests/expr_and_series/rolling_var_test.py
@@ -281,12 +281,12 @@ def test_rolling_var_expr_lazy_grouped(
     center: bool,
     ddof: int,
 ) -> None:
-    if ("polars" in str(constructor) and POLARS_VERSION < (1, 10)) or (
-        "duckdb" in str(constructor) and DUCKDB_VERSION < (1, 3)
+    if (
+        ("polars" in str(constructor) and POLARS_VERSION < (1, 10))
+        or ("duckdb" in str(constructor) and DUCKDB_VERSION < (1, 3))
+        or ("pandas" in str(constructor) and PANDAS_VERSION < (1, 2))
     ):
         pytest.skip()
-    if "pandas" in str(constructor):
-        pytest.skip()
     if any(x in str(constructor) for x in ("dask", "pyarrow_table")):
         request.applymarker(pytest.mark.xfail)
     if "cudf" in str(constructor) and center: