Skip to content

Commit

Permalink
chore: simplify definitions of rhs expressions, bump dask minimum to …
Browse files Browse the repository at this point in the history
…2024.10 (#1720)
  • Loading branch information
MarcoGorelli authored Jan 5, 2025
1 parent 2dcfd26 commit 1f3cc41
Show file tree
Hide file tree
Showing 16 changed files with 110 additions and 233 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/extremes.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ jobs:
not_so_old_versions:
strategy:
matrix:
python-version: ["3.9"]
python-version: ["3.10"]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
Expand All @@ -99,7 +99,7 @@ jobs:
cache-suffix: ${{ matrix.python-version }}
cache-dependency-glob: "pyproject.toml"
- name: install-not-so-old-versions
run: uv pip install tox virtualenv setuptools pandas==2.0.3 polars==0.20.8 numpy==1.24.4 pyarrow==14.0.0 "pyarrow-stubs<17" pyspark==3.4.0 scipy==1.8.0 scikit-learn==1.3.0 dask[dataframe]==2024.7 tzdata --system
run: uv pip install tox virtualenv setuptools pandas==2.0.3 polars==0.20.8 numpy==1.24.4 pyarrow==15.0.0 "pyarrow-stubs<17" pyspark==3.4.0 scipy==1.8.0 scikit-learn==1.3.0 dask[dataframe]==2024.10 tzdata --system
- name: install-reqs
run: uv pip install -e ".[dev]" --system
- name: show-deps
Expand All @@ -110,11 +110,11 @@ jobs:
echo "$DEPS" | grep 'pandas==2.0.3'
echo "$DEPS" | grep 'polars==0.20.8'
echo "$DEPS" | grep 'numpy==1.24.4'
echo "$DEPS" | grep 'pyarrow==14.0.0'
echo "$DEPS" | grep 'pyarrow==15.0.0'
echo "$DEPS" | grep 'pyspark==3.4.0'
echo "$DEPS" | grep 'scipy==1.8.0'
echo "$DEPS" | grep 'scikit-learn==1.3.0'
echo "$DEPS" | grep 'dask==2024.7'
echo "$DEPS" | grep 'dask==2024.10'
- name: Run pytest
run: pytest tests --cov=narwhals --cov=tests --cov-fail-under=50 --runslow --constructors=pandas,pyarrow,polars[eager],polars[lazy],dask

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/pytest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ jobs:
pytest-full-coverage:
strategy:
matrix:
python-version: ["3.9", "3.11", "3.13"]
python-version: ["3.11", "3.13"]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
Expand All @@ -78,7 +78,7 @@ jobs:
- name: install pyspark
run: uv pip install -e ".[pyspark]" --system
# PySpark is not yet available on Python3.12+
if: matrix.python-version == '3.9' || matrix.python-version == '3.11'
if: matrix.python-version != '3.13'
- name: install ibis
run: uv pip install -e ".[ibis]" --system
# Ibis puts upper bounds on dependencies, and requires Python3.10+,
Expand Down
36 changes: 0 additions & 36 deletions narwhals/_arrow/expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,66 +163,30 @@ def __lt__(self: Self, other: ArrowExpr | Any) -> Self:
def __and__(self: Self, other: ArrowExpr | bool | Any) -> Self:
return reuse_series_implementation(self, "__and__", other=other)

def __rand__(self: Self, other: ArrowExpr | bool | Any) -> Self:
other = self.__narwhals_namespace__().lit(other, dtype=None)
return other.__and__(self) # type: ignore[return-value]

def __or__(self: Self, other: ArrowExpr | bool | Any) -> Self:
return reuse_series_implementation(self, "__or__", other=other)

def __ror__(self: Self, other: ArrowExpr | bool | Any) -> Self:
other = self.__narwhals_namespace__().lit(other, dtype=None)
return other.__or__(self) # type: ignore[return-value]

def __add__(self: Self, other: ArrowExpr | Any) -> Self:
return reuse_series_implementation(self, "__add__", other=other)

def __radd__(self: Self, other: ArrowExpr | Any) -> Self:
other = self.__narwhals_namespace__().lit(other, dtype=None)
return other.__add__(self) # type: ignore[return-value]

def __sub__(self: Self, other: ArrowExpr | Any) -> Self:
return reuse_series_implementation(self, "__sub__", other=other)

def __rsub__(self: Self, other: ArrowExpr | Any) -> Self:
other = self.__narwhals_namespace__().lit(other, dtype=None)
return other.__sub__(self) # type: ignore[return-value]

def __mul__(self: Self, other: ArrowExpr | Any) -> Self:
return reuse_series_implementation(self, "__mul__", other=other)

def __rmul__(self: Self, other: ArrowExpr | Any) -> Self:
other = self.__narwhals_namespace__().lit(other, dtype=None)
return other.__mul__(self) # type: ignore[return-value]

def __pow__(self: Self, other: ArrowExpr | Any) -> Self:
return reuse_series_implementation(self, "__pow__", other=other)

def __rpow__(self: Self, other: ArrowExpr | Any) -> Self:
other = self.__narwhals_namespace__().lit(other, dtype=None)
return other.__pow__(self) # type: ignore[return-value]

def __floordiv__(self: Self, other: ArrowExpr | Any) -> Self:
return reuse_series_implementation(self, "__floordiv__", other=other)

def __rfloordiv__(self: Self, other: ArrowExpr | Any) -> Self:
other = self.__narwhals_namespace__().lit(other, dtype=None)
return other.__floordiv__(self) # type: ignore[return-value]

def __truediv__(self: Self, other: ArrowExpr | Any) -> Self:
return reuse_series_implementation(self, "__truediv__", other=other)

def __rtruediv__(self: Self, other: ArrowExpr | Any) -> Self:
other = self.__narwhals_namespace__().lit(other, dtype=None)
return other.__truediv__(self) # type: ignore[return-value]

def __mod__(self: Self, other: ArrowExpr | Any) -> Self:
return reuse_series_implementation(self, "__mod__", other=other)

def __rmod__(self: Self, other: ArrowExpr | Any) -> Self:
other = self.__narwhals_namespace__().lit(other, dtype=None)
return other.__mod__(self) # type: ignore[return-value]

def __invert__(self: Self) -> Self:
return reuse_series_implementation(self, "__invert__")

Expand Down
72 changes: 0 additions & 72 deletions narwhals/_dask/expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,14 +210,6 @@ def __add__(self, other: Any) -> Self:
returns_scalar=binary_operation_returns_scalar(self, other),
)

def __radd__(self, other: Any) -> Self:
return self._from_call(
lambda _input, other: _input.__radd__(other),
"__radd__",
other=other,
returns_scalar=binary_operation_returns_scalar(self, other),
).alias("literal")

def __sub__(self, other: Any) -> Self:
return self._from_call(
lambda _input, other: _input.__sub__(other),
Expand All @@ -226,14 +218,6 @@ def __sub__(self, other: Any) -> Self:
returns_scalar=binary_operation_returns_scalar(self, other),
)

def __rsub__(self, other: Any) -> Self:
return self._from_call(
lambda _input, other: _input.__rsub__(other),
"__rsub__",
other=other,
returns_scalar=binary_operation_returns_scalar(self, other),
).alias("literal")

def __mul__(self, other: Any) -> Self:
return self._from_call(
lambda _input, other: _input.__mul__(other),
Expand All @@ -242,14 +226,6 @@ def __mul__(self, other: Any) -> Self:
returns_scalar=binary_operation_returns_scalar(self, other),
)

def __rmul__(self, other: Any) -> Self:
return self._from_call(
lambda _input, other: _input.__rmul__(other),
"__rmul__",
other=other,
returns_scalar=binary_operation_returns_scalar(self, other),
).alias("literal")

def __truediv__(self, other: Any) -> Self:
return self._from_call(
lambda _input, other: _input.__truediv__(other),
Expand All @@ -258,14 +234,6 @@ def __truediv__(self, other: Any) -> Self:
returns_scalar=binary_operation_returns_scalar(self, other),
)

def __rtruediv__(self, other: Any) -> Self:
return self._from_call(
lambda _input, other: _input.__rtruediv__(other),
"__rtruediv__",
other=other,
returns_scalar=binary_operation_returns_scalar(self, other),
).alias("literal")

def __floordiv__(self, other: Any) -> Self:
return self._from_call(
lambda _input, other: _input.__floordiv__(other),
Expand All @@ -274,14 +242,6 @@ def __floordiv__(self, other: Any) -> Self:
returns_scalar=binary_operation_returns_scalar(self, other),
)

def __rfloordiv__(self, other: Any) -> Self:
return self._from_call(
lambda _input, other: _input.__rfloordiv__(other),
"__rfloordiv__",
other=other,
returns_scalar=binary_operation_returns_scalar(self, other),
).alias("literal")

def __pow__(self, other: Any) -> Self:
return self._from_call(
lambda _input, other: _input.__pow__(other),
Expand All @@ -290,14 +250,6 @@ def __pow__(self, other: Any) -> Self:
returns_scalar=binary_operation_returns_scalar(self, other),
)

def __rpow__(self, other: Any) -> Self:
return self._from_call(
lambda _input, other: _input.__rpow__(other),
"__rpow__",
other=other,
returns_scalar=binary_operation_returns_scalar(self, other),
).alias("literal")

def __mod__(self, other: Any) -> Self:
return self._from_call(
lambda _input, other: _input.__mod__(other),
Expand All @@ -306,14 +258,6 @@ def __mod__(self, other: Any) -> Self:
returns_scalar=binary_operation_returns_scalar(self, other),
)

def __rmod__(self, other: Any) -> Self:
return self._from_call(
lambda _input, other: _input.__rmod__(other),
"__rmod__",
other=other,
returns_scalar=binary_operation_returns_scalar(self, other),
).alias("literal")

def __eq__(self, other: DaskExpr) -> Self: # type: ignore[override]
return self._from_call(
lambda _input, other: _input.__eq__(other),
Expand Down Expand Up @@ -370,14 +314,6 @@ def __and__(self, other: DaskExpr) -> Self:
returns_scalar=binary_operation_returns_scalar(self, other),
)

def __rand__(self, other: DaskExpr) -> Self:
return self._from_call(
lambda _input, other: _input.__rand__(other),
"__rand__",
other=other,
returns_scalar=binary_operation_returns_scalar(self, other),
).alias("literal")

def __or__(self, other: DaskExpr) -> Self:
return self._from_call(
lambda _input, other: _input.__or__(other),
Expand All @@ -386,14 +322,6 @@ def __or__(self, other: DaskExpr) -> Self:
returns_scalar=binary_operation_returns_scalar(self, other),
)

def __ror__(self, other: DaskExpr) -> Self:
return self._from_call(
lambda _input, other: _input.__ror__(other),
"__ror__",
other=other,
returns_scalar=binary_operation_returns_scalar(self, other),
).alias("literal")

def __invert__(self: Self) -> Self:
return self._from_call(
lambda _input: _input.__invert__(),
Expand Down
35 changes: 21 additions & 14 deletions narwhals/_dask/namespace.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,26 +66,30 @@ def nth(self, *column_indices: int) -> DaskExpr:
)

def lit(self, value: Any, dtype: DType | None) -> DaskExpr:
def convert_if_dtype(
series: dask_expr.Series, dtype: DType | type[DType]
) -> dask_expr.Series:
return (
series.astype(narwhals_to_native_dtype(dtype, self._version))
if dtype
else series
)
import dask.dataframe as dd
import pandas as pd

return DaskExpr(
lambda df: [
df._native_frame.assign(literal=value)["literal"].pipe(
convert_if_dtype, dtype
def func(df: DaskLazyFrame) -> list[dask_expr.Series]:
return [
dd.from_pandas(
pd.Series(
[value],
dtype=narwhals_to_native_dtype(dtype, self._version)
if dtype is not None
else None,
name="literal",
),
npartitions=df._native_frame.npartitions,
)
],
]

return DaskExpr(
func,
depth=0,
function_name="lit",
root_names=None,
output_names=["literal"],
returns_scalar=False,
returns_scalar=True,
backend_version=self._backend_version,
version=self._version,
kwargs={},
Expand Down Expand Up @@ -414,6 +418,9 @@ def __call__(self, df: DaskLazyFrame) -> Sequence[dask_expr.Series]:
# `self._otherwise_value` is a scalar and can't be converted to an expression
return [value_series.where(condition, self._otherwise_value)]
otherwise_series = otherwise_expr(df)[0]

if otherwise_expr._returns_scalar: # type: ignore[attr-defined]
return [value_series.where(condition, otherwise_series[0])]
validate_comparand(condition, otherwise_series)
return [value_series.where(condition, otherwise_series)]

Expand Down
3 changes: 2 additions & 1 deletion narwhals/_dask/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ def maybe_evaluate(df: DaskLazyFrame, obj: Any) -> Any:
msg = "Multi-output expressions (e.g. `nw.all()` or `nw.col('a', 'b')`) not supported in this context"
raise NotImplementedError(msg)
result = results[0]
validate_comparand(df._native_frame, result)
if not obj._returns_scalar:
validate_comparand(df._native_frame, result)
if obj._returns_scalar:
# Return scalar, let Dask do its broadcasting
return result[0]
Expand Down
37 changes: 0 additions & 37 deletions narwhals/_pandas_like/expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,68 +179,31 @@ def __lt__(self, other: PandasLikeExpr | Any) -> Self:
def __and__(self, other: PandasLikeExpr | bool | Any) -> Self:
return reuse_series_implementation(self, "__and__", other=other)

def __rand__(self, other: Any) -> Self:
other = self.__narwhals_namespace__().lit(other, dtype=None)
return other.__and__(self) # type: ignore[no-any-return]

def __or__(self, other: PandasLikeExpr | bool | Any) -> Self:
return reuse_series_implementation(self, "__or__", other=other)

def __ror__(self, other: Any) -> Self:
other = self.__narwhals_namespace__().lit(other, dtype=None)
return other.__or__(self) # type: ignore[no-any-return]

def __add__(self, other: PandasLikeExpr | Any) -> Self:
return reuse_series_implementation(self, "__add__", other=other)

def __radd__(self, other: Any) -> Self:
other = self.__narwhals_namespace__().lit(other, dtype=None)
return other.__add__(self) # type: ignore[no-any-return]

def __sub__(self, other: PandasLikeExpr | Any) -> Self:
return reuse_series_implementation(self, "__sub__", other=other)

def __rsub__(self, other: Any) -> Self:
other = self.__narwhals_namespace__().lit(other, dtype=None)
return other.__sub__(self) # type: ignore[no-any-return]

def __mul__(self, other: PandasLikeExpr | Any) -> Self:
return reuse_series_implementation(self, "__mul__", other=other)

def __rmul__(self, other: Any) -> Self:
other = self.__narwhals_namespace__().lit(other, dtype=None)
return other.__mul__(self) # type: ignore[no-any-return]

def __truediv__(self, other: PandasLikeExpr | Any) -> Self:
return reuse_series_implementation(self, "__truediv__", other=other)

def __rtruediv__(self, other: Any) -> Self:
other = self.__narwhals_namespace__().lit(other, dtype=None)
return other.__truediv__(self) # type: ignore[no-any-return]

def __floordiv__(self, other: PandasLikeExpr | Any) -> Self:
return reuse_series_implementation(self, "__floordiv__", other=other)

def __rfloordiv__(self, other: Any) -> Self:
other = self.__narwhals_namespace__().lit(other, dtype=None)
return other.__floordiv__(self) # type: ignore[no-any-return]

def __pow__(self, other: PandasLikeExpr | Any) -> Self:
return reuse_series_implementation(self, "__pow__", other=other)

def __rpow__(self, other: Any) -> Self:
other = self.__narwhals_namespace__().lit(other, dtype=None)
return other.__pow__(self) # type: ignore[no-any-return]

def __mod__(self, other: PandasLikeExpr | Any) -> Self:
return reuse_series_implementation(self, "__mod__", other=other)

def __rmod__(self, other: Any) -> Self:
other = self.__narwhals_namespace__().lit(other, dtype=None)
return other.__mod__(self) # type: ignore[no-any-return]

# Unary

def __invert__(self) -> Self:
return reuse_series_implementation(self, "__invert__")

Expand Down
Loading

0 comments on commit 1f3cc41

Please sign in to comment.