Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions leanframe/core/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,107 @@ def __add__(self, other) -> Series:
return Series(self._data + getattr(other, "_data", other))

def __radd__(self, other) -> Series:
# See https://docs.python.org/3/reference/datamodel.html#object.__radd__
return Series(getattr(other, "_data", other) + self._data)

def __sub__(self, other) -> Series:
return Series(self._data - getattr(other, "_data", other))

def __rsub__(self, other) -> Series:
# See https://docs.python.org/3/reference/datamodel.html#object.__rsub__
return Series(getattr(other, "_data", other) - self._data)

def __mul__(self, other) -> Series:
return Series(self._data * getattr(other, "_data", other))

def __rmul__(self, other) -> Series:
# See https://docs.python.org/3/reference/datamodel.html#object.__rmul__
return Series(getattr(other, "_data", other) * self._data)

def __truediv__(self, other) -> Series:
return Series(self._data / getattr(other, "_data", other))

def __rtruediv__(self, other) -> Series:
# See https://docs.python.org/3/reference/datamodel.html#object.__rtruediv__
return Series(getattr(other, "_data", other) / self._data)

def __floordiv__(self, other) -> Series:
return Series(self._data // getattr(other, "_data", other))

def __rfloordiv__(self, other) -> Series:
# See https://docs.python.org/3/reference/datamodel.html#object.__rfloordiv__
return Series(getattr(other, "_data", other) // self._data)

def __mod__(self, other) -> Series:
return Series(self._data % getattr(other, "_data", other))

def __rmod__(self, other) -> Series:
# See https://docs.python.org/3/reference/datamodel.html#object.__rmod__
return Series(getattr(other, "_data", other) % self._data)

def __pow__(self, other):
other_data = getattr(other, "_data", other)
result = self._data**other_data

is_self_int = self._data.type().is_integer()
is_other_int = False
if isinstance(other_data, ibis_types.Column):
is_other_int = other_data.type().is_integer()
elif isinstance(other_data, int):
is_other_int = True

if is_self_int and is_other_int:
return Series(result.cast("int64"))
return Series(result)

def __rpow__(self, other):
# See https://docs.python.org/3/reference/datamodel.html#object.__rpow__
other_data = getattr(other, "_data", other)
result = other_data**self._data

is_self_int = self._data.type().is_integer()
is_other_int = False
if isinstance(other_data, ibis_types.Column):
is_other_int = other_data.type().is_integer()
elif isinstance(other_data, int):
is_other_int = True

if is_self_int and is_other_int:
return Series(result.cast("int64"))
return Series(result)

def add(self, other) -> "Series":
"""Return the addition of the Series and the other."""
return self + other

def sub(self, other) -> "Series":
"""Return the subtraction of the Series and the other."""
return self - other

def mul(self, other) -> "Series":
"""Return the multiplication of the Series and the other."""
return self * other

def div(self, other) -> "Series":
"""Return the true division of the Series and the other."""
return self / other

def truediv(self, other) -> "Series":
"""Return the true division of the Series and the other."""
return self / other

def floordiv(self, other) -> "Series":
"""Return the floor division of the Series and the other."""
return self // other

def mod(self, other) -> "Series":
"""Return the modulo of the Series and the other."""
return self % other

def pow(self, other) -> "Series":
"""Return the power of the Series and the other."""
return self**other

def __lt__(self, other) -> Series:
return Series(self._data < getattr(other, "_data", other))

Expand Down
32 changes: 16 additions & 16 deletions specs/2025-09-16-series-methods.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,22 +54,22 @@ and check off the item with an x.
- [ ] pandas.Series.pop -- not feasible, requires index
- [ ] pandas.Series.item
- [ ] pandas.Series.xs
- [ ] pandas.Series.add
- [ ] pandas.Series.sub
- [ ] pandas.Series.mul
- [ ] pandas.Series.div
- [ ] pandas.Series.truediv
- [ ] pandas.Series.floordiv
- [ ] pandas.Series.mod
- [ ] pandas.Series.pow
- [ ] pandas.Series.radd
- [ ] pandas.Series.rsub
- [ ] pandas.Series.rmul
- [ ] pandas.Series.rdiv
- [ ] pandas.Series.rtruediv
- [ ] pandas.Series.rfloordiv
- [ ] pandas.Series.rmod
- [ ] pandas.Series.rpow
- [x] pandas.Series.add
- [x] pandas.Series.sub
- [x] pandas.Series.mul
- [x] pandas.Series.div
- [x] pandas.Series.truediv
- [x] pandas.Series.floordiv
- [x] pandas.Series.mod
- [x] pandas.Series.pow
- [x] pandas.Series.radd
- [x] pandas.Series.rsub
- [x] pandas.Series.rmul
- [x] pandas.Series.rdiv
- [x] pandas.Series.rtruediv
- [x] pandas.Series.rfloordiv
- [x] pandas.Series.rmod
- [x] pandas.Series.rpow
- [ ] pandas.Series.combine
- [ ] pandas.Series.combine_first
- [x] pandas.Series.round
Expand Down
56 changes: 44 additions & 12 deletions tests/unit/test_series.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,15 +184,33 @@ def test_to_pandas(session: leanframe.Session, series_pd: pd.Series):


@pytest.mark.parametrize(
("op", "other", "expected_data"),
("op", "other", "expected_data", "expected_dtype"),
[
pytest.param(lambda s, o: s + o, 1, [2, 3, 4], id="add_scalar"),
pytest.param(lambda s, o: o + s, 1, [2, 3, 4], id="radd_scalar"),
pytest.param(lambda s, o: s * o, 2, [2, 4, 6], id="mul_scalar"),
pytest.param(lambda s, o: o * s, 2, [2, 4, 6], id="rmul_scalar"),
pytest.param(lambda s, o: s + o, 1, [2, 3, 4], pd.ArrowDtype(pa.int64()), id="add_scalar"),
pytest.param(lambda s, o: s.add(o), 1, [2, 3, 4], pd.ArrowDtype(pa.int64()), id="add_method_scalar"),
pytest.param(lambda s, o: o + s, 1, [2, 3, 4], pd.ArrowDtype(pa.int64()), id="radd_scalar"),
pytest.param(lambda s, o: s - o, 1, [0, 1, 2], pd.ArrowDtype(pa.int64()), id="sub_scalar"),
pytest.param(lambda s, o: s.sub(o), 1, [0, 1, 2], pd.ArrowDtype(pa.int64()), id="sub_method_scalar"),
pytest.param(lambda s, o: o - s, 1, [0, -1, -2], pd.ArrowDtype(pa.int64()), id="rsub_scalar"),
pytest.param(lambda s, o: s * o, 2, [2, 4, 6], pd.ArrowDtype(pa.int64()), id="mul_scalar"),
pytest.param(lambda s, o: s.mul(o), 2, [2, 4, 6], pd.ArrowDtype(pa.int64()), id="mul_method_scalar"),
pytest.param(lambda s, o: o * s, 2, [2, 4, 6], pd.ArrowDtype(pa.int64()), id="rmul_scalar"),
pytest.param(lambda s, o: s / o, 2, [0.5, 1.0, 1.5], pd.ArrowDtype(pa.float64()), id="truediv_scalar"),
pytest.param(lambda s, o: s.div(o), 2, [0.5, 1.0, 1.5], pd.ArrowDtype(pa.float64()), id="div_method_scalar"),
pytest.param(lambda s, o: s.truediv(o), 2, [0.5, 1.0, 1.5], pd.ArrowDtype(pa.float64()), id="truediv_method_scalar"),
pytest.param(lambda s, o: o / s, 2, [2.0, 1.0, 2/3], pd.ArrowDtype(pa.float64()), id="rtruediv_scalar"),
pytest.param(lambda s, o: s // o, 2, [0, 1, 1], pd.ArrowDtype(pa.int64()), id="floordiv_scalar"),
pytest.param(lambda s, o: s.floordiv(o), 2, [0, 1, 1], pd.ArrowDtype(pa.int64()), id="floordiv_method_scalar"),
pytest.param(lambda s, o: o // s, 3, [3, 1, 1], pd.ArrowDtype(pa.int64()), id="rfloordiv_scalar"),
pytest.param(lambda s, o: s % o, 2, [1, 0, 1], pd.ArrowDtype(pa.int64()), id="mod_scalar"),
pytest.param(lambda s, o: s.mod(o), 2, [1, 0, 1], pd.ArrowDtype(pa.int64()), id="mod_method_scalar"),
pytest.param(lambda s, o: o % s, 2, [0, 0, 2], pd.ArrowDtype(pa.int64()), id="rmod_scalar"),
pytest.param(lambda s, o: s ** o, 2, [1, 4, 9], pd.ArrowDtype(pa.int64()), id="pow_scalar"),
pytest.param(lambda s, o: s.pow(o), 2, [1, 4, 9], pd.ArrowDtype(pa.int64()), id="pow_method_scalar"),
pytest.param(lambda s, o: o ** s, 2, [2, 4, 8], pd.ArrowDtype(pa.int64()), id="rpow_scalar"),
],
)
def test_series_arithmetic_scalar(session, op, other, expected_data):
def test_series_arithmetic_scalar(session, op, other, expected_data, expected_dtype):
pandas_df = pd.DataFrame(
{"a": [1, 2, 3]},
dtype=pd.ArrowDtype(pa.int64()),
Expand All @@ -204,12 +222,13 @@ def test_series_arithmetic_scalar(session, op, other, expected_data):

expected_series = pd.Series(
expected_data,
dtype=pd.ArrowDtype(pa.int64()),
dtype=expected_dtype,
)
pd.testing.assert_series_equal(
result_series.to_pandas(),
expected_series,
check_names=False,
rtol=0.001,
)


Expand Down Expand Up @@ -556,13 +575,26 @@ def test_series_iter(series_for_properties):


@pytest.mark.parametrize(
("op", "expected_data"),
("op", "expected_data", "expected_dtype"),
[
pytest.param(lambda s1, s2: s1 + s2, [5, 7, 9], id="add_series"),
pytest.param(lambda s1, s2: s1 * s2, [4, 10, 18], id="mul_series"),
pytest.param(lambda s1, s2: s1 + s2, [5, 7, 9], pd.ArrowDtype(pa.int64()), id="add_series"),
pytest.param(lambda s1, s2: s1.add(s2), [5, 7, 9], pd.ArrowDtype(pa.int64()), id="add_method_series"),
pytest.param(lambda s1, s2: s1 - s2, [-3, -3, -3], pd.ArrowDtype(pa.int64()), id="sub_series"),
pytest.param(lambda s1, s2: s1.sub(s2), [-3, -3, -3], pd.ArrowDtype(pa.int64()), id="sub_method_series"),
pytest.param(lambda s1, s2: s1 * s2, [4, 10, 18], pd.ArrowDtype(pa.int64()), id="mul_series"),
pytest.param(lambda s1, s2: s1.mul(s2), [4, 10, 18], pd.ArrowDtype(pa.int64()), id="mul_method_series"),
pytest.param(lambda s1, s2: s1 / s2, [0.25, 0.4, 0.5], pd.ArrowDtype(pa.float64()), id="truediv_series"),
pytest.param(lambda s1, s2: s1.div(s2), [0.25, 0.4, 0.5], pd.ArrowDtype(pa.float64()), id="div_method_series"),
pytest.param(lambda s1, s2: s1.truediv(s2), [0.25, 0.4, 0.5], pd.ArrowDtype(pa.float64()), id="truediv_method_series"),
pytest.param(lambda s1, s2: s1 // s2, [0, 0, 0], pd.ArrowDtype(pa.int64()), id="floordiv_series"),
pytest.param(lambda s1, s2: s1.floordiv(s2), [0, 0, 0], pd.ArrowDtype(pa.int64()), id="floordiv_method_series"),
pytest.param(lambda s1, s2: s1 % s2, [1, 2, 3], pd.ArrowDtype(pa.int64()), id="mod_series"),
pytest.param(lambda s1, s2: s1.mod(s2), [1, 2, 3], pd.ArrowDtype(pa.int64()), id="mod_method_series"),
pytest.param(lambda s1, s2: s1 ** s2, [1, 32, 729], pd.ArrowDtype(pa.int64()), id="pow_series"),
pytest.param(lambda s1, s2: s1.pow(s2), [1, 32, 729], pd.ArrowDtype(pa.int64()), id="pow_method_series"),
],
)
def test_series_arithmetic_series(session, op, expected_data):
def test_series_arithmetic_series(session, op, expected_data, expected_dtype):
pandas_df = pd.DataFrame(
{"a": [1, 2, 3], "b": [4, 5, 6]},
dtype=pd.ArrowDtype(pa.int64()),
Expand All @@ -575,7 +607,7 @@ def test_series_arithmetic_series(session, op, expected_data):

expected_series = pd.Series(
expected_data,
dtype=pd.ArrowDtype(pa.int64()),
dtype=expected_dtype,
)
pd.testing.assert_series_equal(
result_series.to_pandas(),
Expand Down