Repository: narwhals-dev/narwhals

Issue:
`n_unique`: pandas skips nulls, polars counts
we may need some additional logic under the hood to align them here:
```python
In [30]: pl.Series([1., None, None, 2.]).n_unique()
Out[30]: 3

In [31]: pd.Series([1., None, None, 2.]).nunique()
Out[31]: 2
```

Discussion:

Patch:
diff --git a/narwhals/_pandas_like/series.py b/narwhals/_pandas_like/series.py
index 854ba161..897e7ffc 100644
--- a/narwhals/_pandas_like/series.py
+++ b/narwhals/_pandas_like/series.py
@@ -346,7 +346,7 @@ class PandasSeries:
 
     def n_unique(self) -> int:
         ser = self._series
-        return ser.nunique()  # type: ignore[no-any-return]
+        return ser.nunique(dropna=False)  # type: ignore[no-any-return]
 
     def sample(
         self,


Test patch:
diff --git a/tests/expr/n_unique_test.py b/tests/expr/n_unique_test.py
new file mode 100644
index 00000000..5a9f7ff2
--- /dev/null
+++ b/tests/expr/n_unique_test.py
@@ -0,0 +1,27 @@
+from typing import Any
+
+import pandas as pd
+import polars as pl
+import pytest
+
+import narwhals as nw
+from tests.utils import compare_dicts
+
+data = {
+    "a": [1.0, None, None, 3.0],
+    "b": [1.0, None, 4, 5.0],
+}
+
+
+@pytest.mark.parametrize("constructor", [pd.DataFrame, pl.DataFrame])
+def test_over_single(constructor: Any) -> None:
+    df = nw.from_native(constructor(data), eager_only=True)
+    result = df.select(nw.all().n_unique())
+    expected = {
+        "a": [3],
+        "b": [4],
+    }
+    compare_dicts(result, expected)
+    assert df["a"].n_unique() == 3
+    assert df["b"].n_unique() == 4
+    compare_dicts(result, expected)