3939 "mean" ,
4040 "median" ,
4141 "min" ,
42+ "mode" ,
4243 "nunique" ,
4344 "prod" ,
4445 "quantile" ,
@@ -115,6 +116,35 @@ def _getitem_aggs(
115116 result = ns ._concat_horizontal (
116117 [ns .from_native (result_single ).alias (name ).native for name in names ]
117118 )
119+ elif self .is_mode ():
120+ compliant = group_by .compliant
121+ if (keep := self .kwargs .get ("keep" )) != "any" : # pragma: no cover
122+ msg = (
123+ f"`Expr.mode(keep='{ keep } ')` is not implemented in group by context for "
124+ f"backend { compliant ._implementation } \n \n "
125+ "Hint: Use `nw.col(...).mode(keep='any')` instead."
126+ )
127+ raise NotImplementedError (msg )
128+
129+ cols = list (names )
130+ native = compliant .native
131+ keys , kwargs = group_by ._keys , group_by ._kwargs
132+
133+ # Implementation based on the following suggestion:
134+ # https://github.com/pandas-dev/pandas/issues/19254#issuecomment-778661578
135+ ns = compliant .__narwhals_namespace__ ()
136+ result = ns ._concat_horizontal (
137+ [
138+ native .groupby ([* keys , col ], ** kwargs )
139+ .size ()
140+ .sort_values (ascending = False )
141+ .reset_index (col )
142+ .groupby (keys , ** kwargs )[col ]
143+ .head (1 )
144+ .sort_index ()
145+ for col in cols
146+ ]
147+ )
118148 else :
119149 select = names [0 ] if len (names ) == 1 else list (names )
120150 result = self .native_agg ()(group_by ._grouped [select ])
@@ -127,6 +157,9 @@ def _getitem_aggs(
127157 def is_len (self ) -> bool :
128158 return self .leaf_name == "len"
129159
160+ def is_mode (self ) -> bool :
161+ return self .leaf_name == "mode"
162+
130163 def is_top_level_function (self ) -> bool :
131164 # e.g. `nw.len()`.
132165 return self .expr ._depth == 0
@@ -158,6 +191,7 @@ class PandasLikeGroupBy(
158191 "median" : "median" ,
159192 "max" : "max" ,
160193 "min" : "min" ,
194+ "mode" : "mode" ,
161195 "std" : "std" ,
162196 "var" : "var" ,
163197 "len" : "size" ,
@@ -176,6 +210,9 @@ class PandasLikeGroupBy(
176210 _output_key_names : list [str ]
177211 """Stores the **original** version of group keys."""
178212
213+ _kwargs : Mapping [str , bool ]
214+ """Stores keyword arguments for `DataFrame.groupby` other than `by`."""
215+
179216 @property
180217 def exclude (self ) -> tuple [str , ...]:
181218 """Group keys to ignore when expanding multi-output aggregations."""
@@ -200,13 +237,14 @@ def __init__(
200237 native = self .compliant .native
201238 if set (native .index .names ).intersection (self .compliant .columns ):
202239 native = native .reset_index (drop = True )
203- self ._grouped : NativeGroupBy = native .groupby (
204- self ._keys .copy (),
205- sort = False ,
206- as_index = True ,
207- dropna = drop_null_keys ,
208- observed = True ,
209- )
240+
241+ self ._kwargs = {
242+ "sort" : False ,
243+ "as_index" : True ,
244+ "dropna" : drop_null_keys ,
245+ "observed" : True ,
246+ }
247+ self ._grouped : NativeGroupBy = native .groupby (self ._keys .copy (), ** self ._kwargs )
210248
211249 def agg (self , * exprs : PandasLikeExpr ) -> PandasLikeDataFrame :
212250 all_aggs_are_simple = True
0 commit comments