Repository: narwhals-dev/narwhals
Pull request #288 (2024-06-11 18:37:45)

feat: implement series arithmetic
<!--
# 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
- [x] ✨ Feature
- [ ] 🐛 Bug Fix
- [ ] 🔧 Optimization
- [ ] 📝 Documentation
- [ ] ✅ Test
- [ ] 🐳 Other

## Related issues 

- Related issue # 
- related # #286 

## 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/series.py b/narwhals/series.py
index eced4855..7bb33b4e 100644
--- a/narwhals/series.py
+++ b/narwhals/series.py
@@ -1102,6 +1102,45 @@ class Series:
         """
         return self._series.to_pandas()
 
+    def __add__(self, other: object) -> Series:
+        return self._from_series(self._series.__add__(self._extract_native(other)))
+
+    def __radd__(self, other: object) -> Series:
+        return self._from_series(self._series.__radd__(self._extract_native(other)))
+
+    def __sub__(self, other: object) -> Series:
+        return self._from_series(self._series.__sub__(self._extract_native(other)))
+
+    def __rsub__(self, other: object) -> Series:
+        return self._from_series(self._series.__rsub__(self._extract_native(other)))
+
+    def __mul__(self, other: object) -> Series:
+        return self._from_series(self._series.__mul__(self._extract_native(other)))
+
+    def __rmul__(self, other: object) -> Series:
+        return self._from_series(self._series.__rmul__(self._extract_native(other)))
+
+    def __truediv__(self, other: object) -> Series:
+        return self._from_series(self._series.__truediv__(self._extract_native(other)))
+
+    def __floordiv__(self, other: object) -> Series:
+        return self._from_series(self._series.__floordiv__(self._extract_native(other)))
+
+    def __rfloordiv__(self, other: object) -> Series:
+        return self._from_series(self._series.__rfloordiv__(self._extract_native(other)))
+
+    def __pow__(self, other: object) -> Series:
+        return self._from_series(self._series.__pow__(self._extract_native(other)))
+
+    def __rpow__(self, other: object) -> Series:
+        return self._from_series(self._series.__rpow__(self._extract_native(other)))
+
+    def __mod__(self, other: object) -> Series:
+        return self._from_series(self._series.__mod__(self._extract_native(other)))
+
+    def __rmod__(self, other: object) -> Series:
+        return self._from_series(self._series.__rmod__(self._extract_native(other)))
+
     def __eq__(self, other: object) -> Series:  # type: ignore[override]
         return self._from_series(self._series.__eq__(self._extract_native(other)))
 


Test patch:
diff --git a/tests/series/arithmetic_test.py b/tests/series/arithmetic_test.py
new file mode 100644
index 00000000..83199dc7
--- /dev/null
+++ b/tests/series/arithmetic_test.py
@@ -0,0 +1,48 @@
+from __future__ import annotations
+
+from typing import Any
+
+import pandas as pd
+import polars as pl
+import pytest
+
+import narwhals as nw
+
+data = [1, 2, 3]
+
+
+@pytest.mark.parametrize("constructor", [pd.Series, pl.Series])
+@pytest.mark.parametrize(
+    ("attr", "rhs", "expected"),
+    [
+        ("__add__", 1, [2, 3, 4]),
+        ("__sub__", 1, [0, 1, 2]),
+        ("__mul__", 2, [2, 4, 6]),
+        ("__truediv__", 2, [0.5, 1.0, 1.5]),
+        ("__floordiv__", 2, [0, 1, 1]),
+        ("__mod__", 2, [1, 0, 1]),
+        ("__pow__", 2, [1, 4, 9]),
+    ],
+)
+def test_arithmetic(attr: str, rhs: Any, expected: list[Any], constructor: Any) -> None:
+    s = nw.from_native(constructor(data), series_only=True)
+    result = getattr(s, attr)(rhs)
+    assert result.to_numpy().tolist() == expected
+
+
+@pytest.mark.parametrize("constructor", [pd.Series, pl.Series])
+@pytest.mark.parametrize(
+    ("attr", "rhs", "expected"),
+    [
+        ("__radd__", 1, [2, 3, 4]),
+        ("__rsub__", 1, [0, -1, -2]),
+        ("__rmul__", 2, [2, 4, 6]),
+        ("__rfloordiv__", 2, [2, 1, 0]),
+        ("__rmod__", 2, [0, 0, 2]),
+        ("__rpow__", 2, [2, 4, 8]),
+    ],
+)
+def test_rarithmetic(attr: str, rhs: Any, expected: list[Any], constructor: Any) -> None:
+    s = nw.from_native(constructor(data), series_only=True)
+    result = getattr(s, attr)(rhs)
+    assert result.to_numpy().tolist() == expected