Repository: narwhals-dev/narwhals
Pull request #409 (2024-07-05 07:11:06)

feat: Expr.Abs and Series.abs
<!--
# 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 # 
- Closes #

## 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/docs/api-reference/expressions.md b/docs/api-reference/expressions.md
index a717fed7..9068b583 100644
--- a/docs/api-reference/expressions.md
+++ b/docs/api-reference/expressions.md
@@ -4,6 +4,7 @@
     handler: python
     options:
       members:
+        - abs
         - alias
         - all
         - any
diff --git a/docs/api-reference/series.md b/docs/api-reference/series.md
index 2687eead..525f5af6 100644
--- a/docs/api-reference/series.md
+++ b/docs/api-reference/series.md
@@ -4,6 +4,7 @@
     handler: python
     options:
       members:
+        - abs
         - alias
         - all
         - any
diff --git a/narwhals/_arrow/expr.py b/narwhals/_arrow/expr.py
index 77516813..c0e587b6 100644
--- a/narwhals/_arrow/expr.py
+++ b/narwhals/_arrow/expr.py
@@ -71,6 +71,9 @@ class ArrowExpr:
     def cast(self, dtype: DType) -> Self:
         return reuse_series_implementation(self, "cast", dtype)  # type: ignore[type-var]
 
+    def abs(self) -> Self:
+        return reuse_series_implementation(self, "abs")  # type: ignore[type-var]
+
     def cum_sum(self) -> Self:
         return reuse_series_implementation(self, "cum_sum")  # type: ignore[type-var]
 
diff --git a/narwhals/_arrow/series.py b/narwhals/_arrow/series.py
index 35641247..d48fcd84 100644
--- a/narwhals/_arrow/series.py
+++ b/narwhals/_arrow/series.py
@@ -78,6 +78,10 @@ class ArrowSeries:
     def dtype(self) -> DType:
         return translate_dtype(self._series.type)
 
+    def abs(self) -> Self:
+        pc = get_pyarrow_compute()
+        return self._from_series(pc.abs(self._series))
+
     def cum_sum(self) -> Self:
         pc = get_pyarrow_compute()
         return self._from_series(pc.cumulative_sum(self._series))
diff --git a/narwhals/_pandas_like/expr.py b/narwhals/_pandas_like/expr.py
index 0b7a1ea7..162c0d17 100644
--- a/narwhals/_pandas_like/expr.py
+++ b/narwhals/_pandas_like/expr.py
@@ -216,6 +216,9 @@ class PandasExpr:
     def sort(self, *, descending: bool = False) -> Self:
         return reuse_series_implementation(self, "sort", descending=descending)
 
+    def abs(self) -> Self:
+        return reuse_series_implementation(self, "abs")
+
     def cum_sum(self) -> Self:
         return reuse_series_implementation(self, "cum_sum")
 
diff --git a/narwhals/_pandas_like/series.py b/narwhals/_pandas_like/series.py
index abfd49fb..3b92a5aa 100644
--- a/narwhals/_pandas_like/series.py
+++ b/narwhals/_pandas_like/series.py
@@ -401,6 +401,9 @@ class PandasSeries:
         ser = self._series
         return self._from_series(ser.sample(n=n, frac=fraction, replace=with_replacement))
 
+    def abs(self) -> PandasSeries:
+        return self._from_series(self._series.abs())
+
     def cum_sum(self) -> PandasSeries:
         return self._from_series(self._series.cumsum())
 
diff --git a/narwhals/expression.py b/narwhals/expression.py
index 9f12a3ac..68fec666 100644
--- a/narwhals/expression.py
+++ b/narwhals/expression.py
@@ -564,6 +564,43 @@ class Expr:
         """
         return self.__class__(lambda plx: self._call(plx).unique())
 
+    def abs(self) -> Self:
+        """
+        Return absolute value of each element.
+
+        Examples:
+            >>> import polars as pl
+            >>> import pandas as pd
+            >>> import narwhals as nw
+            >>> data = {"a": [1, -2], "b": [-3, 4]}
+            >>> df_pd = pd.DataFrame(data)
+            >>> df_pl = pl.DataFrame(data)
+
+            Let's define a dataframe-agnostic function:
+
+            >>> @nw.narwhalify
+            ... def func(df):
+            ...     return df.select(nw.col("a", "b").abs())
+
+            We can then pass either pandas or Polars to `func`:
+
+            >>> func(df_pd)
+               a  b
+            0  1  3
+            1  2  4
+            >>> func(df_pl)
+            shape: (2, 2)
+            ┌─────┬─────┐
+            │ a   ┆ b   │
+            │ --- ┆ --- │
+            │ i64 ┆ i64 │
+            ╞═════╪═════╡
+            │ 1   ┆ 3   │
+            │ 2   ┆ 4   │
+            └─────┴─────┘
+        """
+        return self.__class__(lambda plx: self._call(plx).abs())
+
     def cum_sum(self) -> Self:
         """
         Return cumulative sum.
diff --git a/narwhals/series.py b/narwhals/series.py
index 44a7556e..ed8e146e 100644
--- a/narwhals/series.py
+++ b/narwhals/series.py
@@ -625,6 +625,42 @@ class Series:
         """
         return self._from_series(self._series.drop_nulls())
 
+    def abs(self) -> Self:
+        """
+        Calculate the absolute value of each element.
+
+        Examples:
+            >>> import pandas as pd
+            >>> import polars as pl
+            >>> import narwhals as nw
+            >>> s = [2, -4, 3]
+            >>> s_pd = pd.Series(s)
+            >>> s_pl = pl.Series(s)
+
+            We define a dataframe-agnostic function:
+
+            >>> @nw.narwhalify
+            ... def func(s_any):
+            ...     return s_any.abs()
+
+            We can then pass either pandas or Polars to `func`:
+
+            >>> func(s_pd)
+            0    2
+            1    4
+            2    3
+            dtype: int64
+            >>> func(s_pl)  # doctest: +NORMALIZE_WHITESPACE
+            shape: (3,)
+            Series: '' [i64]
+            [
+               2
+               4
+               3
+            ]
+        """
+        return self._from_series(self._series.abs())
+
     def cum_sum(self) -> Self:
         """
         Calculate the cumulative sum.


Test patch:
diff --git a/tests/expr/abs_test.py b/tests/expr/abs_test.py
new file mode 100644
index 00000000..eb558d13
--- /dev/null
+++ b/tests/expr/abs_test.py
@@ -0,0 +1,15 @@
+from typing import Any
+
+import narwhals as nw
+from tests.utils import compare_dicts
+
+
+def test_abs(constructor_with_pyarrow: Any) -> None:
+    df = nw.from_native(
+        constructor_with_pyarrow({"a": [1, 2, 3, -4, 5]}), eager_only=True
+    )
+    result = df.select(b=nw.col("a").abs())
+    expected = {"b": [1, 2, 3, 4, 5]}
+    compare_dicts(result, expected)
+    result = df.select(b=df["a"].abs())
+    compare_dicts(result, expected)