Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
124 commits
Select commit Hold shift + click to select a range
c3389d5
WIP: pandas and (almost) polars
FBruzzesi Mar 31, 2025
cbc2275
arrow
FBruzzesi Mar 31, 2025
3c03f42
lazyframes
FBruzzesi Mar 31, 2025
3ba79c0
WIP typing
FBruzzesi Mar 31, 2025
4ce0c15
almost there
FBruzzesi Mar 31, 2025
1b2f38a
better parsing: include multi-output exprs
FBruzzesi Mar 31, 2025
105623e
test multi-output
FBruzzesi Mar 31, 2025
73f85ea
allow lit and agg's
FBruzzesi Apr 1, 2025
b940b2c
some more typing and coverage
FBruzzesi Apr 1, 2025
c6cf243
deduplicate
FBruzzesi Apr 2, 2025
f189b26
fix tests
FBruzzesi Apr 2, 2025
698091d
fix(typing): Narrow `EagerGroupBy._keys` to `list[str]`
dangotbanned Apr 2, 2025
922a02e
fix(typing): Mostly resolve `Polars*`
dangotbanned Apr 2, 2025
6407f52
fix(typing): Narrow from `Series | Expr` to `Expr`
dangotbanned Apr 2, 2025
1868a4d
fix(typing): Un break `extract_native` overloads
dangotbanned Apr 2, 2025
733d9f4
refactor: Generalize `_parse_keys` -> `_evaluate_aliases`
dangotbanned Apr 2, 2025
94e3211
fix(typing): Un-confuse `mypy`
dangotbanned Apr 2, 2025
d38e466
revert: Undo more renaming
dangotbanned Apr 2, 2025
a38fa41
refactor(typing): Remove implicit `Self`
dangotbanned Apr 2, 2025
1f17e52
refactor: Use `CompliantExpr._evaluate_aliases` more
dangotbanned Apr 2, 2025
08ba27f
test: Skip `over` for `duckdb<1.3`
dangotbanned Apr 2, 2025
25307f4
chore: Remove dead code
dangotbanned Apr 2, 2025
72f5dbb
refactor: Move `with_columns` out of `__init__`
dangotbanned Apr 2, 2025
eac99b8
special paths for pandas-like and dask
FBruzzesi Apr 3, 2025
4d5a396
move parsing to group_by
FBruzzesi Apr 3, 2025
0e6a983
Merge remote-tracking branch 'upstream/main' into feat/allow-expr-in-…
dangotbanned Apr 5, 2025
660c2ab
revert(typing): Remove `IncompletePolarsExpr`
dangotbanned Apr 5, 2025
befcc48
chore(typing): Ignore existing issues
dangotbanned Apr 5, 2025
aaf027d
fix(typing): Make everyone compliant again
dangotbanned Apr 5, 2025
8408b01
fix(typing): Use `pyright` ignores
dangotbanned Apr 5, 2025
3f93217
refactor: Move inner function to `_expression_passing`
dangotbanned Apr 5, 2025
890d741
parametrize test
FBruzzesi Apr 6, 2025
8f6632c
no cover
FBruzzesi Apr 6, 2025
f075315
fixup tests
FBruzzesi Apr 6, 2025
3bb4968
rm shapeerror for lazyframe, fix duckdb context condition in test
FBruzzesi Apr 6, 2025
9ea5d11
flatten first
FBruzzesi Apr 6, 2025
3473c56
Merge branch 'main' into feat/allow-expr-in-group-by
FBruzzesi Apr 8, 2025
346f492
Merge branch 'main' into feat/allow-expr-in-group-by
FBruzzesi Apr 8, 2025
ec449ec
Merge branch 'main' into feat/allow-expr-in-group-by
FBruzzesi Apr 9, 2025
8431cee
Merge branch 'feat/allow-expr-in-group-by' of https://github.com/narw…
FBruzzesi Apr 9, 2025
f050486
Merge remote-tracking branch 'upstream/main' into feat/allow-expr-in-…
dangotbanned Apr 9, 2025
02c7ac5
solve conflicts
FBruzzesi Apr 10, 2025
74da552
solve conflicts
FBruzzesi Apr 10, 2025
bb7bda7
Merge branch 'main' into feat/allow-expr-in-group-by
MarcoGorelli Apr 10, 2025
54a7178
WIP
FBruzzesi Apr 11, 2025
386e38c
just missing ordering
FBruzzesi Apr 11, 2025
3faba54
compliant frames
FBruzzesi Apr 11, 2025
8ab528e
ok make stable v1 work
FBruzzesi Apr 11, 2025
49a77b7
extract_native(arg) -> arg.native
FBruzzesi Apr 12, 2025
7e86ac1
fix polars drop_null_keys case
FBruzzesi Apr 12, 2025
f26f6ed
rename parsing method, fix sqlframe
FBruzzesi Apr 12, 2025
326222f
fix eager __iter__
FBruzzesi Apr 12, 2025
78970ef
fix __all__ namespaces, arrow over
FBruzzesi Apr 12, 2025
c43512c
add index name in expected output
FBruzzesi Apr 12, 2025
fff4f41
xfail polars
FBruzzesi Apr 12, 2025
36fe9e3
missing drop_null_keys condition to xfail
FBruzzesi Apr 12, 2025
b5f5888
Merge branch 'main' into feat/allow-expr-in-group-by
FBruzzesi Apr 12, 2025
99987aa
deal with unnamed and key in ops in agg
FBruzzesi Apr 12, 2025
7862d28
Merge branch 'main' into feat/allow-expr-in-group-by
FBruzzesi Apr 12, 2025
a452116
old pandas, make mypy happy?
FBruzzesi Apr 12, 2025
dad4035
pandas?
FBruzzesi Apr 12, 2025
a077261
resolve conflicts
FBruzzesi Apr 12, 2025
9ac7a24
fix duckdb dashed names
FBruzzesi Apr 12, 2025
1b02590
docstrings and raise test
FBruzzesi Apr 12, 2025
b6d5190
merge main
FBruzzesi Apr 12, 2025
0393cc7
forgot about main errors for different exprkind's
FBruzzesi Apr 12, 2025
5bf2179
avoid using internal functions
FBruzzesi Apr 13, 2025
0bfaabc
ok unnamed?
FBruzzesi Apr 13, 2025
0c1d427
xfail polars drop_null_keys and multi output exprs
FBruzzesi Apr 13, 2025
925a295
pin down polars xfail
FBruzzesi Apr 13, 2025
f6dba0b
polars conditions
FBruzzesi Apr 13, 2025
4dec624
fast_path for keys being all strings
FBruzzesi Apr 14, 2025
7c42fb2
do not use init in protocol
FBruzzesi Apr 14, 2025
5c43b49
Merge branch 'main' into feat/allow-expr-in-group-by
FBruzzesi Apr 14, 2025
99313bf
merge main
FBruzzesi Apr 15, 2025
fa77614
Merge branch 'main' into feat/allow-expr-in-group-by
FBruzzesi Apr 16, 2025
cfaea70
Merge branch 'main' into feat/allow-expr-in-group-by
FBruzzesi Apr 18, 2025
3754321
type annotate 'keys'
FBruzzesi Apr 18, 2025
69edc84
do not cast
FBruzzesi Apr 18, 2025
2794362
Merge branch 'main' into feat/allow-expr-in-group-by
FBruzzesi Apr 18, 2025
e7b4790
Marco's feedback
FBruzzesi Apr 18, 2025
614b69d
merge main
FBruzzesi Apr 18, 2025
c5c0bad
merge main
FBruzzesi Apr 19, 2025
bb5bae8
WIP: Dan's feedback
FBruzzesi Apr 19, 2025
caaa092
fix(typing): Use an invariant `TypeVar` for frame
dangotbanned Apr 19, 2025
797cf74
Dan's feedback, pt2: it does look promising indeed
FBruzzesi Apr 19, 2025
3e968b5
refactor: Reduce protocol footprint
dangotbanned Apr 19, 2025
b28da50
fix(typing): Use a constrained `TypeVar`
dangotbanned Apr 19, 2025
8e66073
perf: Avoid converting to `Expr`
dangotbanned Apr 19, 2025
66a24f2
fix: Don't explode on angery `_bool_`
dangotbanned Apr 19, 2025
5d6dd61
try without `tupleify`?
dangotbanned Apr 19, 2025
e2184ea
refactor: Simplify `PolarsGroupBy`
dangotbanned Apr 19, 2025
c5379d7
remove duplicate line oops
dangotbanned Apr 19, 2025
0df5c3f
Merge branch 'main' into feat/allow-expr-in-group-by
FBruzzesi Apr 19, 2025
f079244
refactor: Align both `Polars` impls
dangotbanned Apr 19, 2025
61be623
fix?
FBruzzesi Apr 19, 2025
ae38ed7
feedback
FBruzzesi Apr 19, 2025
5f593c8
rollback pandas change
FBruzzesi Apr 19, 2025
5aef757
ok, pragmatism wins?
FBruzzesi Apr 19, 2025
eae7f40
pragmatism wins: test fix
FBruzzesi Apr 19, 2025
2a574c0
similar trick for pyarrow
FBruzzesi Apr 19, 2025
169d749
feedback
FBruzzesi Apr 20, 2025
62f5749
GroupBy positional only, parametrize exception test
FBruzzesi Apr 20, 2025
a16ec2a
raise for drop_null_keys with expr/series in keys
FBruzzesi Apr 20, 2025
bb22b1b
feat(typing): Add overloads for `drop_null_keys` string only
dangotbanned Apr 20, 2025
cb4a28e
also ignore `[call-overload]`
dangotbanned Apr 20, 2025
db9175f
split test to raise on drop_null_keys True and exprs
FBruzzesi Apr 20, 2025
1533cde
rm ExpansionKind.is_multi_output
FBruzzesi Apr 20, 2025
2b62198
Merge branch 'main' into feat/allow-expr-in-group-by
FBruzzesi Apr 21, 2025
a943409
Merge branch 'main' into feat/allow-expr-in-group-by
FBruzzesi Apr 21, 2025
b8ac267
perf: Avoid creating `2*len(exprs)` lists in `agg`
dangotbanned Apr 22, 2025
b08aabe
revert(typing): Un-add `EagerDataFrameT_co`
dangotbanned Apr 22, 2025
e3b5a7e
perf: evaluate exclude outside of loop
dangotbanned Apr 22, 2025
5e77645
revert: Remove duplicate method
dangotbanned Apr 22, 2025
3f22a44
perf: More outside of loop
dangotbanned Apr 22, 2025
d8a47ad
Merge remote-tracking branch 'upstream/main' into feat/allow-expr-in-…
dangotbanned Apr 23, 2025
7adffd0
chore: Apply suggestions
dangotbanned Apr 23, 2025
5440c4f
Dan's feedback
FBruzzesi Apr 24, 2025
a605700
Merge branch 'main' into feat/allow-expr-in-group-by
dangotbanned Apr 24, 2025
e839ba6
Merge branch 'main' into feat/allow-expr-in-group-by
dangotbanned Apr 26, 2025
8a9b313
fix when grouping by selector
MarcoGorelli Apr 27, 2025
9565b9e
simplify pyarrow, remove double-drop_nulls
MarcoGorelli Apr 27, 2025
1623a71
restore existing test
MarcoGorelli Apr 27, 2025
4995bc8
remove unnecessary xfail
MarcoGorelli Apr 27, 2025
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
2 changes: 1 addition & 1 deletion narwhals/_arrow/dataframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,7 @@ def with_columns(self: ArrowDataFrame, *exprs: ArrowExpr) -> ArrowDataFrame:

return self._with_native(native_frame, validate_column_names=False)

def group_by(self: Self, *keys: str, drop_null_keys: bool) -> ArrowGroupBy:
def group_by(self: Self, *keys: ArrowExpr, drop_null_keys: bool) -> ArrowGroupBy:
from narwhals._arrow.group_by import ArrowGroupBy

return ArrowGroupBy(self, keys, drop_null_keys=drop_null_keys)
Expand Down
3 changes: 2 additions & 1 deletion narwhals/_arrow/expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,8 @@ def func(df: ArrowDataFrame) -> Sequence[ArrowSeries]:
)
raise NotImplementedError(msg)

tmp = df.group_by(*partition_by, drop_null_keys=False).agg(self)
ns = self.__narwhals_namespace__()
tmp = df.group_by(ns.col(*partition_by), drop_null_keys=False).agg(self)
tmp = df.simple_select(*partition_by).join(
tmp,
how="left",
Expand Down
23 changes: 16 additions & 7 deletions narwhals/_arrow/group_by.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import collections
from itertools import chain
from typing import TYPE_CHECKING
from typing import Any
from typing import ClassVar
Expand Down Expand Up @@ -41,18 +42,26 @@ class ArrowGroupBy(EagerGroupBy["ArrowDataFrame", "ArrowExpr"]):
}

def __init__(
self,
self: Self,
compliant_frame: ArrowDataFrame,
keys: Sequence[str],
keys: Sequence[ArrowExpr],
/,
*,
drop_null_keys: bool,
) -> None:
if drop_null_keys:
self._compliant_frame = compliant_frame.drop_nulls(keys)
else:
self._compliant_frame = compliant_frame
self._keys: list[str] = list(keys)
compliant_frame = compliant_frame.with_columns(*keys)

self._keys: list[str] = list(
chain.from_iterable(
evaluate_output_names_and_aliases(
expr=key, df=compliant_frame, exclude=[]
)[1]
for key in keys
)
)
self._compliant_frame = (
compliant_frame.drop_nulls(self._keys) if drop_null_keys else compliant_frame
)
Comment thread
dangotbanned marked this conversation as resolved.
Outdated
self._grouped = pa.TableGroupBy(self.compliant.native, self._keys)

def agg(self: Self, *exprs: ArrowExpr) -> ArrowDataFrame:
Expand Down
5 changes: 3 additions & 2 deletions narwhals/_compliant/dataframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from typing import TypeVar
from typing import overload

from narwhals._compliant.typing import CompliantExprAny
from narwhals._compliant.typing import CompliantExprT_contra
from narwhals._compliant.typing import CompliantSeriesT
from narwhals._compliant.typing import EagerExprT_contra
Expand Down Expand Up @@ -127,7 +128,7 @@ def filter(self, predicate: CompliantExprT_contra | Incomplete) -> Self: ...
def gather_every(self, n: int, offset: int) -> Self: ...
def get_column(self, name: str) -> CompliantSeriesT: ...
def group_by(
self, *keys: str, drop_null_keys: bool
self, *keys: CompliantExprAny, drop_null_keys: bool
) -> CompliantGroupBy[Self, Any]: ...
def head(self, n: int) -> Self: ...
def item(self, row: int | None, column: int | str | None) -> Any: ...
Expand Down Expand Up @@ -255,7 +256,7 @@ def filter(self, predicate: CompliantExprT_contra | Incomplete) -> Self: ...
)
def gather_every(self, n: int, offset: int) -> Self: ...
def group_by(
self, *keys: str, drop_null_keys: bool
self, *keys: CompliantExprAny, drop_null_keys: bool
) -> CompliantGroupBy[Self, Any]: ...
def head(self, n: int) -> Self: ...
def join(
Expand Down
3 changes: 2 additions & 1 deletion narwhals/_compliant/group_by.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from typing import TypeVar

from narwhals._compliant.typing import CompliantDataFrameT_co
from narwhals._compliant.typing import CompliantExprAny
from narwhals._compliant.typing import CompliantExprT_contra
from narwhals._compliant.typing import CompliantFrameT_co
from narwhals._compliant.typing import CompliantLazyFrameT_co
Expand Down Expand Up @@ -67,7 +68,7 @@ def compliant(self) -> CompliantFrameT_co:
def __init__(
self,
compliant_frame: CompliantFrameT_co,
keys: Sequence[str],
keys: Sequence[CompliantExprAny],
/,
*,
drop_null_keys: bool,
Expand Down
2 changes: 1 addition & 1 deletion narwhals/_compliant/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ def _with_native(self, series: Any, *, preserve_broadcast: bool = False) -> Self
"""Return a new `CompliantSeries`, wrapping the native `series`.

In cases when operations are known to not affect whether a result should
be broadcast, we can pass `preverse_broadcast=True`.
be broadcast, we can pass `preserve_broadcast=True`.
Comment thread
dangotbanned marked this conversation as resolved.
Set this with care - it should only be set for unary expressions which don't
change length or order, such as `.alias` or `.fill_null`. If in doubt, don't
set it, you probably don't need it.
Expand Down
2 changes: 1 addition & 1 deletion narwhals/_dask/dataframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ def join_asof(
),
)

def group_by(self: Self, *by: str, drop_null_keys: bool) -> DaskLazyGroupBy:
def group_by(self: Self, *by: DaskExpr, drop_null_keys: bool) -> DaskLazyGroupBy:
from narwhals._dask.group_by import DaskLazyGroupBy

return DaskLazyGroupBy(self, by, drop_null_keys=drop_null_keys)
Expand Down
21 changes: 17 additions & 4 deletions narwhals/_dask/group_by.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

from functools import partial
from itertools import chain
from typing import TYPE_CHECKING
from typing import Any
from typing import Callable
Expand Down Expand Up @@ -72,12 +73,24 @@ class DaskLazyGroupBy(DepthTrackingGroupBy["DaskLazyFrame", "DaskExpr", Aggregat
}

def __init__(
self: Self, df: DaskLazyFrame, keys: Sequence[str], /, *, drop_null_keys: bool
self: Self,
compliant_frame: DaskLazyFrame,
keys: Sequence[DaskExpr],
/,
*,
drop_null_keys: bool,
) -> None:
self._compliant_frame = df
self._keys: list[str] = list(keys)
self._compliant_frame = compliant_frame.with_columns(*keys)
self._keys: list[str] = list(
chain.from_iterable(
evaluate_output_names_and_aliases(
expr=key, df=compliant_frame, exclude=[]
)[1]
for key in keys
)
)
Comment thread
FBruzzesi marked this conversation as resolved.
Outdated
self._grouped = self.compliant.native.groupby(
list(self._keys), dropna=drop_null_keys, observed=True
self._keys, dropna=drop_null_keys, observed=True
)

def agg(self: Self, *exprs: DaskExpr) -> DaskLazyFrame:
Expand Down
2 changes: 1 addition & 1 deletion narwhals/_duckdb/dataframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ def _with_native(self: Self, df: duckdb.DuckDBPyRelation) -> Self:
df, backend_version=self._backend_version, version=self._version
)

def group_by(self: Self, *keys: str, drop_null_keys: bool) -> DuckDBGroupBy:
def group_by(self: Self, *keys: DuckDBExpr, drop_null_keys: bool) -> DuckDBGroupBy:
from narwhals._duckdb.group_by import DuckDBGroupBy

return DuckDBGroupBy(self, keys, drop_null_keys=drop_null_keys)
Expand Down
22 changes: 18 additions & 4 deletions narwhals/_duckdb/group_by.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from typing import Sequence

from narwhals._compliant import LazyGroupBy
from narwhals._expression_parsing import evaluate_output_names_and_aliases

if TYPE_CHECKING:
from duckdb import Expression # noqa: F401
Expand All @@ -17,14 +18,27 @@
class DuckDBGroupBy(LazyGroupBy["DuckDBLazyFrame", "DuckDBExpr", "Expression"]):
def __init__(
self: Self,
df: DuckDBLazyFrame,
keys: Sequence[str],
compliant_frame: DuckDBLazyFrame,
keys: Sequence[DuckDBExpr],
/,
*,
drop_null_keys: bool,
) -> None:
self._compliant_frame = df.drop_nulls(subset=None) if drop_null_keys else df
self._keys = list(keys)
compliant_frame = compliant_frame.with_columns(*keys)

self._keys: list[str] = list(
chain.from_iterable(
evaluate_output_names_and_aliases(
expr=key, df=compliant_frame, exclude=[]
)[1]
for key in keys
)
)
Comment thread
FBruzzesi marked this conversation as resolved.
Outdated
self._compliant_frame = (
compliant_frame.drop_nulls(subset=self._keys)
if drop_null_keys
else compliant_frame
)

def agg(self: Self, *exprs: DuckDBExpr) -> DuckDBLazyFrame:
agg_columns = list(chain(self._keys, self._evaluate_exprs(exprs)))
Expand Down
4 changes: 3 additions & 1 deletion narwhals/_pandas_like/dataframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -699,7 +699,9 @@ def collect(
raise ValueError(msg) # pragma: no cover

# --- actions ---
def group_by(self: Self, *keys: str, drop_null_keys: bool) -> PandasLikeGroupBy:
def group_by(
self: Self, *keys: PandasLikeExpr, drop_null_keys: bool
) -> PandasLikeGroupBy:
Comment thread
dangotbanned marked this conversation as resolved.
from narwhals._pandas_like.group_by import PandasLikeGroupBy

return PandasLikeGroupBy(self, keys, drop_null_keys=drop_null_keys)
Expand Down
25 changes: 18 additions & 7 deletions narwhals/_pandas_like/group_by.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import collections
import warnings
from itertools import chain
from typing import TYPE_CHECKING
from typing import Any
from typing import ClassVar
Expand Down Expand Up @@ -40,20 +41,30 @@ class PandasLikeGroupBy(EagerGroupBy["PandasLikeDataFrame", "PandasLikeExpr"]):

def __init__(
self: Self,
df: PandasLikeDataFrame,
keys: Sequence[str],
compliant_frame: PandasLikeDataFrame,
keys: Sequence[PandasLikeExpr],
/,
*,
drop_null_keys: bool,
) -> None:
self._compliant_frame = df
self._keys: list[str] = list(keys)
self._compliant_frame = compliant_frame.with_columns(*keys)
self._keys: list[str] = list(
chain.from_iterable(
evaluate_output_names_and_aliases(
expr=key, df=compliant_frame, exclude=[]
)[1]
for key in keys
)
)
Comment thread
FBruzzesi marked this conversation as resolved.
Outdated
# Drop index to avoid potential collisions:
# https://github.com/narwhals-dev/narwhals/issues/1907.
if set(df.native.index.names).intersection(df.columns):
native_frame = df.native.reset_index(drop=True)

if set(self._compliant_frame.native.index.names).intersection(
self._compliant_frame.columns
):
native_frame = self._compliant_frame.native.reset_index(drop=True)
else:
native_frame = df.native
native_frame = self._compliant_frame.native
if (
self.compliant._implementation.is_pandas()
and self.compliant._backend_version < (1, 1)
Expand Down
7 changes: 5 additions & 2 deletions narwhals/_polars/dataframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
from typing_extensions import Self
from typing_extensions import TypeAlias

from narwhals._polars.expr import PolarsExpr
from narwhals._polars.group_by import PolarsGroupBy
from narwhals._polars.group_by import PolarsLazyGroupBy
from narwhals._polars.series import PolarsSeries
Expand Down Expand Up @@ -396,7 +397,7 @@ def to_dict(
else:
return self.native.to_dict(as_series=False)

def group_by(self: Self, *keys: str, drop_null_keys: bool) -> PolarsGroupBy:
def group_by(self: Self, *keys: PolarsExpr, drop_null_keys: bool) -> PolarsGroupBy:
from narwhals._polars.group_by import PolarsGroupBy

return PolarsGroupBy(self, keys, drop_null_keys=drop_null_keys)
Expand Down Expand Up @@ -641,7 +642,9 @@ def collect(
msg = f"Unsupported `backend` value: {backend}" # pragma: no cover
raise ValueError(msg) # pragma: no cover

def group_by(self: Self, *keys: str, drop_null_keys: bool) -> PolarsLazyGroupBy:
def group_by(
self: Self, *keys: PolarsExpr, drop_null_keys: bool
) -> PolarsLazyGroupBy:
from narwhals._polars.group_by import PolarsLazyGroupBy

return PolarsLazyGroupBy(self, keys, drop_null_keys=drop_null_keys)
Expand Down
59 changes: 43 additions & 16 deletions narwhals/_polars/group_by.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from narwhals._polars.utils import extract_native

if TYPE_CHECKING:
from polars._typing import IntoExpr
from polars.dataframe.group_by import GroupBy as NativeGroupBy
from polars.lazyframe.group_by import LazyGroupBy as NativeLazyGroupBy
from typing_extensions import Self
Expand All @@ -19,23 +20,36 @@

class PolarsGroupBy:
_compliant_frame: PolarsDataFrame
_keys: Sequence[str]
_keys: Sequence[IntoExpr]

@property
def compliant(self) -> PolarsDataFrame:
return self._compliant_frame

def __init__(
self, df: PolarsDataFrame, keys: Sequence[str], /, *, drop_null_keys: bool
self,
compliant_frame: PolarsDataFrame,
keys: Sequence[PolarsExpr],
/,
*,
drop_null_keys: bool,
) -> None:
self._compliant_frame = df
self._keys = list(keys)
df = df.drop_nulls(keys) if drop_null_keys else df
self._grouped: NativeGroupBy = df._native_frame.group_by(keys)
if not drop_null_keys:
self._compliant_frame = compliant_frame
self._keys = [extract_native(arg) for arg in keys]
else:
compliant_frame = compliant_frame.with_columns(*keys)
self._keys = [extract_native(arg).meta.output_name() for arg in keys]
self._compliant_frame = compliant_frame.drop_nulls(self._keys)

self._grouped: NativeGroupBy = self._compliant_frame._native_frame.group_by(
self._keys
)
Comment thread
FBruzzesi marked this conversation as resolved.
Outdated

def agg(self: Self, *aggs: PolarsExpr) -> PolarsDataFrame:
from_native = self.compliant._with_native
return from_native(self._grouped.agg(extract_native(arg) for arg in aggs))
return self.compliant._with_native(
self._grouped.agg(extract_native(arg) for arg in aggs)
Comment thread
FBruzzesi marked this conversation as resolved.
Outdated
)

def __iter__(self: Self) -> Iterator[tuple[tuple[str, ...], PolarsDataFrame]]:
for key, df in self._grouped:
Expand All @@ -44,20 +58,33 @@ def __iter__(self: Self) -> Iterator[tuple[tuple[str, ...], PolarsDataFrame]]:

class PolarsLazyGroupBy:
_compliant_frame: PolarsLazyFrame
_keys: Sequence[str]
_keys: Sequence[IntoExpr]

@property
def compliant(self) -> PolarsLazyFrame:
return self._compliant_frame

def __init__(
self, df: PolarsLazyFrame, keys: Sequence[str], /, *, drop_null_keys: bool
self,
compliant_frame: PolarsLazyFrame,
keys: Sequence[PolarsExpr],
/,
*,
drop_null_keys: bool,
) -> None:
self._compliant_frame = df
self._keys = list(keys)
df = df.drop_nulls(keys) if drop_null_keys else df
self._grouped: NativeLazyGroupBy = df._native_frame.group_by(keys)
if not drop_null_keys:
self._compliant_frame = compliant_frame
self._keys = [extract_native(arg) for arg in keys]
else:
compliant_frame = compliant_frame.with_columns(*keys)
self._keys = [extract_native(arg).meta.output_name() for arg in keys]
self._compliant_frame = compliant_frame.drop_nulls(self._keys)

self._grouped: NativeLazyGroupBy = self._compliant_frame._native_frame.group_by(
self._keys
)

def agg(self: Self, *aggs: PolarsExpr) -> PolarsLazyFrame:
from_native = self.compliant._with_native
return from_native(self._grouped.agg(extract_native(arg) for arg in aggs))
return self.compliant._with_native(
self._grouped.agg(extract_native(arg) for arg in aggs)
Comment thread
FBruzzesi marked this conversation as resolved.
Outdated
)
4 changes: 3 additions & 1 deletion narwhals/_spark_like/dataframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,9 @@ def drop(self: Self, columns: Sequence[str], *, strict: bool) -> Self:
def head(self: Self, n: int) -> Self:
return self._with_native(self.native.limit(num=n))

def group_by(self: Self, *keys: str, drop_null_keys: bool) -> SparkLikeLazyGroupBy:
def group_by(
self: Self, *keys: SparkLikeExpr, drop_null_keys: bool
) -> SparkLikeLazyGroupBy:
from narwhals._spark_like.group_by import SparkLikeLazyGroupBy

return SparkLikeLazyGroupBy(self, keys, drop_null_keys=drop_null_keys)
Expand Down
Loading