Skip to content

Commit 68f9c05

Browse files
committed
Merge branch 'main' into perf/codspeed-ci
2 parents eb2c343 + 266be9c commit 68f9c05

30 files changed

+141
-141
lines changed

.github/release-drafter.yml

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,31 +9,31 @@ autolabeler:
99
- label: breaking
1010
title:
1111
# Example: feat!: ...
12-
- '/^(build|chore|ci|depr|docs|feat|fix|perf|refactor|release|test)(\(.*\))?\!\: /'
12+
- '/^([B|b]uild|[C|c]hore|[CI|ci]|[D|d]epr|[D|d]oc|DOC|[F|f]eat|[F|f]ix|[P|p]erf|[R|r]efactor|[R|r]elease|[T|t]est)\! /'
1313
- label: build
1414
title:
15-
- '/^build/'
15+
- '/^[B|b]uild/'
1616
- label: internal
1717
title:
18-
- '/^(chore|ci|refactor|test|template|bench)/'
18+
- '/^[C|c]hore|[CI|ci]|[R|r]efactor|[T|t]est|[T|t]emplate|[B|b]ench/'
1919
- label: deprecation
2020
title:
21-
- '/^depr/'
21+
- '/^[D|d]epr/'
2222
- label: documentation
2323
title:
24-
- '/(.*doc|.*docstring)/'
24+
- '/^[D|d]oc|DOC/'
2525
- label: enhancement
2626
title:
27-
- '/^(.*feat|.*enh)/'
27+
- '/^.*feat|.*enh|Feat|ENH|Enh/'
2828
- label: fix
2929
title:
30-
- '/^fix/'
30+
- '/^[F|f]ix/'
3131
- label: performance
3232
title:
33-
- '/^perf/'
33+
- '/^[P|p]erf/'
3434
- label: release
3535
title:
36-
- '/^release/'
36+
- '/^[R|r]elease/'
3737

3838
version-resolver:
3939
major:

narwhals/_dask/expr.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,40 @@ def func(_input: Any) -> Any:
545545
returns_scalar=False,
546546
)
547547

548+
def is_duplicated(self: Self) -> Self:
549+
def func(_input: Any) -> Any:
550+
_name = _input.name
551+
return (
552+
_input.to_frame().groupby(_name).transform("size", meta=(_name, int)) > 1
553+
)
554+
555+
return self._from_call(
556+
func,
557+
"is_duplicated",
558+
returns_scalar=False,
559+
)
560+
561+
def is_unique(self: Self) -> Self:
562+
def func(_input: Any) -> Any:
563+
_name = _input.name
564+
return (
565+
_input.to_frame().groupby(_name).transform("size", meta=(_name, int)) == 1
566+
)
567+
568+
return self._from_call(
569+
func,
570+
"is_unique",
571+
returns_scalar=False,
572+
)
573+
574+
def is_in(self: Self, other: Any) -> Self:
575+
return self._from_call(
576+
lambda _input, other: _input.isin(other),
577+
"is_in",
578+
other,
579+
returns_scalar=False,
580+
)
581+
548582
def null_count(self: Self) -> Self:
549583
return self._from_call(
550584
lambda _input: _input.isna().sum(),

narwhals/_pandas_like/series.py

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -221,18 +221,9 @@ def is_in(self, other: Any) -> PandasLikeSeries:
221221
return self._from_native_series(res)
222222

223223
def arg_true(self) -> PandasLikeSeries:
224-
import numpy as np # ignore-banned-import
225-
226224
ser = self._native_series
227-
res = np.flatnonzero(ser)
228-
return self._from_native_series(
229-
native_series_from_iterable(
230-
res,
231-
name=self.name,
232-
index=range(len(res)),
233-
implementation=self._implementation,
234-
)
235-
)
225+
result = ser.__class__(range(len(ser)), name=ser.name, index=ser.index).loc[ser]
226+
return self._from_native_series(result)
236227

237228
# Binary comparisons
238229

narwhals/_pandas_like/utils.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,10 @@ def set_axis(
197197
implementation: Implementation,
198198
backend_version: tuple[int, ...],
199199
) -> T:
200+
if implementation is Implementation.CUDF: # pragma: no cover
201+
obj = obj.copy(deep=False) # type: ignore[attr-defined]
202+
obj.index = index # type: ignore[attr-defined]
203+
return obj
200204
if implementation is Implementation.PANDAS and backend_version < (
201205
1,
202206
): # pragma: no cover
@@ -210,7 +214,7 @@ def set_axis(
210214
kwargs["copy"] = False
211215
else: # pragma: no cover
212216
pass
213-
return obj.set_axis(index, axis=0, **kwargs) # type: ignore[no-any-return, attr-defined]
217+
return obj.set_axis(index, axis=0, **kwargs) # type: ignore[attr-defined, no-any-return]
214218

215219

216220
def translate_dtype(column: Any) -> DType:

narwhals/expr.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1114,8 +1114,8 @@ def arg_true(self) -> Self:
11141114
11151115
>>> func(df_pd)
11161116
a
1117-
0 1
1118-
1 2
1117+
1 1
1118+
2 2
11191119
>>> func(df_pl)
11201120
shape: (2, 1)
11211121
┌─────┐

narwhals/functions.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ def new_series(
133133
raise NotImplementedError(msg)
134134
else: # pragma: no cover
135135
try:
136-
# implementation is UNKNOWN, Narhwals extension using this feature should
136+
# implementation is UNKNOWN, Narwhals extension using this feature should
137137
# implement `from_dict` function in the top-level namespace.
138138
native_series = native_namespace.new_series(name, values, dtype)
139139
except AttributeError as e:
@@ -256,7 +256,7 @@ def from_dict(
256256
native_frame = native_namespace.table(data, schema=schema)
257257
else: # pragma: no cover
258258
try:
259-
# implementation is UNKNOWN, Narhwals extension using this feature should
259+
# implementation is UNKNOWN, Narwhals extension using this feature should
260260
# implement `from_dict` function in the top-level namespace.
261261
native_frame = native_namespace.from_dict(data)
262262
except AttributeError as e:

narwhals/series.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -766,8 +766,8 @@ def arg_true(self) -> Self:
766766
We can then pass either pandas or Polars to `func`:
767767
768768
>>> func(s_pd)
769-
0 1
770-
1 2
769+
1 1
770+
2 2
771771
Name: a, dtype: int64
772772
>>> func(s_pl) # doctest: +NORMALIZE_WHITESPACE
773773
shape: (2,)

narwhals/stable/v1.py

Lines changed: 11 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from __future__ import annotations
22

3-
from functools import wraps
43
from typing import TYPE_CHECKING
54
from typing import Any
65
from typing import Callable
@@ -39,6 +38,7 @@
3938
from narwhals.schema import Schema as NwSchema
4039
from narwhals.series import Series as NwSeries
4140
from narwhals.translate import get_native_namespace as nw_get_native_namespace
41+
from narwhals.translate import narwhalify as nw_narwhalify
4242
from narwhals.translate import to_native
4343
from narwhals.typing import IntoDataFrameT
4444
from narwhals.typing import IntoFrameT
@@ -734,6 +734,7 @@ def narwhalify(
734734
*,
735735
strict: bool = False,
736736
eager_only: bool | None = False,
737+
eager_or_interchange_only: bool | None = False,
737738
series_only: bool | None = False,
738739
allow_series: bool | None = True,
739740
) -> Callable[..., Any]:
@@ -776,7 +777,7 @@ def func(df):
776777
You can also pass in extra arguments, e.g.
777778
778779
```python
779-
@nw.narhwalify(eager_only=True)
780+
@nw.narwhalify(eager_only=True)
780781
```
781782
782783
that will get passed down to `nw.from_native`.
@@ -792,53 +793,14 @@ def func(df):
792793
allow_series: Whether to allow series (default is only dataframe / lazyframe).
793794
"""
794795

795-
# TODO(Unassigned): do we have a way to de-dupe this a bit?
796-
def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
797-
@wraps(func)
798-
def wrapper(*args: Any, **kwargs: Any) -> Any:
799-
args = [
800-
from_native(
801-
arg,
802-
strict=strict,
803-
eager_only=eager_only,
804-
series_only=series_only,
805-
allow_series=allow_series,
806-
)
807-
for arg in args
808-
] # type: ignore[assignment]
809-
810-
kwargs = {
811-
name: from_native(
812-
value,
813-
strict=strict,
814-
eager_only=eager_only,
815-
series_only=series_only,
816-
allow_series=allow_series,
817-
)
818-
for name, value in kwargs.items()
819-
}
820-
821-
backends = {
822-
b()
823-
for v in [*args, *kwargs.values()]
824-
if (b := getattr(v, "__native_namespace__", None))
825-
}
826-
827-
if backends.__len__() > 1:
828-
msg = "Found multiple backends. Make sure that all dataframe/series inputs come from the same backend."
829-
raise ValueError(msg)
830-
831-
result = func(*args, **kwargs)
832-
833-
return to_native(result, strict=strict)
834-
835-
return wrapper
836-
837-
if func is None:
838-
return decorator
839-
else:
840-
# If func is not None, it means the decorator is used without arguments
841-
return decorator(func)
796+
return nw_narwhalify(
797+
func=func,
798+
strict=strict,
799+
eager_only=eager_only,
800+
eager_or_interchange_only=eager_or_interchange_only,
801+
series_only=series_only,
802+
allow_series=allow_series,
803+
)
842804

843805

844806
def all() -> Expr:

narwhals/translate.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -595,7 +595,7 @@ def func(df):
595595
You can also pass in extra arguments, e.g.
596596
597597
```python
598-
@nw.narhwalify(eager_only=True)
598+
@nw.narwhalify(eager_only=True)
599599
```
600600
601601
that will get passed down to `nw.from_native`.

requirements-dev.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
covdefaults
22
pandas
3-
polars[timezones]
3+
polars
44
pre-commit
55
pyarrow
66
pytest

tests/conftest.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import pyarrow as pa
88
import pytest
99

10+
from narwhals.dependencies import get_cudf
1011
from narwhals.dependencies import get_dask_dataframe
1112
from narwhals.dependencies import get_modin
1213
from narwhals.typing import IntoDataFrame
@@ -17,6 +18,8 @@
1718
import modin.pandas # noqa: F401
1819
with contextlib.suppress(ImportError):
1920
import dask.dataframe # noqa: F401
21+
with contextlib.suppress(ImportError):
22+
import cudf # noqa: F401
2023

2124

2225
def pytest_addoption(parser: Any) -> None:
@@ -56,6 +59,11 @@ def modin_constructor(obj: Any) -> IntoDataFrame: # pragma: no cover
5659
return mpd.DataFrame(pd.DataFrame(obj)).convert_dtypes(dtype_backend="pyarrow") # type: ignore[no-any-return]
5760

5861

62+
def cudf_constructor(obj: Any) -> IntoDataFrame: # pragma: no cover
63+
cudf = get_cudf()
64+
return cudf.DataFrame(obj) # type: ignore[no-any-return]
65+
66+
5967
def polars_eager_constructor(obj: Any) -> IntoDataFrame:
6068
return pl.DataFrame(obj)
6169

@@ -87,7 +95,8 @@ def pyarrow_table_constructor(obj: Any) -> IntoDataFrame:
8795

8896
if get_modin() is not None: # pragma: no cover
8997
eager_constructors.append(modin_constructor)
90-
# TODO(unassigned): when Dask gets better support, remove the "False and" part
98+
if get_cudf() is not None:
99+
eager_constructors.append(cudf_constructor) # pragma: no cover
91100
if get_dask_dataframe() is not None: # pragma: no cover
92101
lazy_constructors.append(dask_lazy_constructor) # type: ignore # noqa: PGH003
93102

tests/expr_and_series/arithmetic_test.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -143,10 +143,10 @@ def test_truediv_same_dims(constructor_eager: Any, request: Any) -> None:
143143
request.applymarker(pytest.mark.xfail)
144144
s_left = nw.from_native(constructor_eager({"a": [1, 2, 3]}), eager_only=True)["a"]
145145
s_right = nw.from_native(constructor_eager({"a": [2, 2, 1]}), eager_only=True)["a"]
146-
result = (s_left / s_right).to_list()
147-
assert result == [0.5, 1.0, 3.0]
148-
result = (s_left.__rtruediv__(s_right)).to_list()
149-
assert result == [2, 1, 1 / 3]
146+
result = s_left / s_right
147+
compare_dicts({"a": result}, {"a": [0.5, 1.0, 3.0]})
148+
result = s_left.__rtruediv__(s_right)
149+
compare_dicts({"a": result}, {"a": [2, 1, 1 / 3]})
150150

151151

152152
@pytest.mark.slow()

tests/expr_and_series/cat/get_categories_test.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ def test_get_categories(request: Any, constructor_eager: Any) -> None:
2525
result_expr = df.select(nw.col("a").cat.get_categories())
2626
compare_dicts(result_expr, expected)
2727

28-
result_series = df["a"].cat.get_categories().to_list()
29-
assert set(result_series) == set(expected["a"])
28+
result_series = df["a"].cat.get_categories()
29+
compare_dicts({"a": result_series}, expected)
3030

3131

3232
def test_get_categories_pyarrow() -> None:
@@ -41,5 +41,5 @@ def test_get_categories_pyarrow() -> None:
4141
result_expr = df.select(nw.col("a").cat.get_categories())
4242
compare_dicts(result_expr, expected)
4343

44-
result_series = df["a"].cat.get_categories().to_list()
45-
assert result_series == expected["a"]
44+
result_series = df["a"].cat.get_categories()
45+
compare_dicts({"a": result_series}, expected)

0 commit comments

Comments
 (0)