16
16
Iterable ,
17
17
Iterator ,
18
18
List ,
19
+ Literal ,
19
20
Sequence ,
20
21
cast ,
21
22
)
@@ -288,6 +289,11 @@ def agg_list_like(self) -> DataFrame | Series:
288
289
-------
289
290
Result of aggregation.
290
291
"""
292
+ return self .agg_or_apply_list_like (op_name = "agg" )
293
+
294
+ def agg_or_apply_list_like (
295
+ self , op_name : Literal ["agg" , "apply" ]
296
+ ) -> DataFrame | Series :
291
297
from pandas .core .groupby .generic import (
292
298
DataFrameGroupBy ,
293
299
SeriesGroupBy ,
@@ -296,6 +302,9 @@ def agg_list_like(self) -> DataFrame | Series:
296
302
297
303
obj = self .obj
298
304
func = cast (List [AggFuncTypeBase ], self .func )
305
+ kwargs = self .kwargs
306
+ if op_name == "apply" :
307
+ kwargs = {** kwargs , "by_row" : False }
299
308
300
309
if getattr (obj , "axis" , 0 ) == 1 :
301
310
raise NotImplementedError ("axis other than 0 is not supported" )
@@ -313,8 +322,6 @@ def agg_list_like(self) -> DataFrame | Series:
313
322
keys = []
314
323
315
324
is_groupby = isinstance (obj , (DataFrameGroupBy , SeriesGroupBy ))
316
- is_ser_or_df = isinstance (obj , (ABCDataFrame , ABCSeries ))
317
- this_args = [self .axis , * self .args ] if is_ser_or_df else self .args
318
325
319
326
context_manager : ContextManager
320
327
if is_groupby :
@@ -323,12 +330,19 @@ def agg_list_like(self) -> DataFrame | Series:
323
330
context_manager = com .temp_setattr (obj , "as_index" , True )
324
331
else :
325
332
context_manager = nullcontext ()
333
+
334
+ def include_axis (colg ) -> bool :
335
+ return isinstance (colg , ABCDataFrame ) or (
336
+ isinstance (colg , ABCSeries ) and op_name == "agg"
337
+ )
338
+
326
339
with context_manager :
327
340
# degenerate case
328
341
if selected_obj .ndim == 1 :
329
342
for a in func :
330
343
colg = obj ._gotitem (selected_obj .name , ndim = 1 , subset = selected_obj )
331
- new_res = colg .aggregate (a , * this_args , ** self .kwargs )
344
+ args = [self .axis , * self .args ] if include_axis (colg ) else self .args
345
+ new_res = getattr (colg , op_name )(a , * args , ** kwargs )
332
346
results .append (new_res )
333
347
334
348
# make sure we find a good name
@@ -339,7 +353,8 @@ def agg_list_like(self) -> DataFrame | Series:
339
353
indices = []
340
354
for index , col in enumerate (selected_obj ):
341
355
colg = obj ._gotitem (col , ndim = 1 , subset = selected_obj .iloc [:, index ])
342
- new_res = colg .aggregate (func , * this_args , ** self .kwargs )
356
+ args = [self .axis , * self .args ] if include_axis (colg ) else self .args
357
+ new_res = getattr (colg , op_name )(func , * args , ** kwargs )
343
358
results .append (new_res )
344
359
indices .append (index )
345
360
keys = selected_obj .columns .take (indices )
@@ -366,15 +381,23 @@ def agg_dict_like(self) -> DataFrame | Series:
366
381
-------
367
382
Result of aggregation.
368
383
"""
384
+ return self .agg_or_apply_dict_like (op_name = "agg" )
385
+
386
+ def agg_or_apply_dict_like (
387
+ self , op_name : Literal ["agg" , "apply" ]
388
+ ) -> DataFrame | Series :
369
389
from pandas import Index
370
390
from pandas .core .groupby .generic import (
371
391
DataFrameGroupBy ,
372
392
SeriesGroupBy ,
373
393
)
374
394
from pandas .core .reshape .concat import concat
375
395
396
+ assert op_name in ["agg" , "apply" ]
397
+
376
398
obj = self .obj
377
399
func = cast (AggFuncTypeDict , self .func )
400
+ kwargs = {"by_row" : False } if op_name == "apply" else {}
378
401
379
402
if getattr (obj , "axis" , 0 ) == 1 :
380
403
raise NotImplementedError ("axis other than 0 is not supported" )
@@ -387,7 +410,7 @@ def agg_dict_like(self) -> DataFrame | Series:
387
410
selected_obj = obj ._selected_obj
388
411
selection = obj ._selection
389
412
390
- func = self .normalize_dictlike_arg ("agg" , selected_obj , func )
413
+ func = self .normalize_dictlike_arg (op_name , selected_obj , func )
391
414
392
415
is_groupby = isinstance (obj , (DataFrameGroupBy , SeriesGroupBy ))
393
416
context_manager : ContextManager
@@ -404,17 +427,18 @@ def agg_dict_like(self) -> DataFrame | Series:
404
427
)
405
428
406
429
# Numba Groupby engine/engine-kwargs passthrough
407
- kwargs = {}
408
430
if is_groupby :
409
431
engine = self .kwargs .get ("engine" , None )
410
432
engine_kwargs = self .kwargs .get ("engine_kwargs" , None )
411
- kwargs = {"engine" : engine , "engine_kwargs" : engine_kwargs }
433
+ kwargs . update ( {"engine" : engine , "engine_kwargs" : engine_kwargs })
412
434
413
435
with context_manager :
414
436
if selected_obj .ndim == 1 :
415
437
# key only used for output
416
438
colg = obj ._gotitem (selection , ndim = 1 )
417
- result_data = [colg .agg (how , ** kwargs ) for _ , how in func .items ()]
439
+ result_data = [
440
+ getattr (colg , op_name )(how , ** kwargs ) for _ , how in func .items ()
441
+ ]
418
442
result_index = list (func .keys ())
419
443
elif is_non_unique_col :
420
444
# key used for column selection and output
@@ -429,7 +453,9 @@ def agg_dict_like(self) -> DataFrame | Series:
429
453
label_to_indices [label ].append (index )
430
454
431
455
key_data = [
432
- selected_obj ._ixs (indice , axis = 1 ).agg (how , ** kwargs )
456
+ getattr (selected_obj ._ixs (indice , axis = 1 ), op_name )(
457
+ how , ** kwargs
458
+ )
433
459
for label , indices in label_to_indices .items ()
434
460
for indice in indices
435
461
]
@@ -439,7 +465,7 @@ def agg_dict_like(self) -> DataFrame | Series:
439
465
else :
440
466
# key used for column selection and output
441
467
result_data = [
442
- obj ._gotitem (key , ndim = 1 ). agg (how , ** kwargs )
468
+ getattr ( obj ._gotitem (key , ndim = 1 ), op_name ) (how , ** kwargs )
443
469
for key , how in func .items ()
444
470
]
445
471
result_index = list (func .keys ())
@@ -535,7 +561,7 @@ def apply_str(self) -> DataFrame | Series:
535
561
self .kwargs ["axis" ] = self .axis
536
562
return self ._apply_str (obj , func , * self .args , ** self .kwargs )
537
563
538
- def apply_multiple (self ) -> DataFrame | Series :
564
+ def apply_list_or_dict_like (self ) -> DataFrame | Series :
539
565
"""
540
566
Compute apply in case of a list-like or dict-like.
541
567
@@ -551,9 +577,9 @@ def apply_multiple(self) -> DataFrame | Series:
551
577
kwargs = self .kwargs
552
578
553
579
if is_dict_like (func ):
554
- result = self .agg_dict_like ( )
580
+ result = self .agg_or_apply_dict_like ( op_name = "apply" )
555
581
else :
556
- result = self .agg_list_like ( )
582
+ result = self .agg_or_apply_list_like ( op_name = "apply" )
557
583
558
584
result = reconstruct_and_relabel_result (result , func , ** kwargs )
559
585
@@ -692,9 +718,9 @@ def values(self):
692
718
693
719
def apply (self ) -> DataFrame | Series :
694
720
"""compute the results"""
695
- # dispatch to agg
721
+ # dispatch to handle list-like or dict-like
696
722
if is_list_like (self .func ):
697
- return self .apply_multiple ()
723
+ return self .apply_list_or_dict_like ()
698
724
699
725
# all empty
700
726
if len (self .columns ) == 0 and len (self .index ) == 0 :
@@ -1041,13 +1067,15 @@ def infer_to_same_shape(self, results: ResType, res_index: Index) -> DataFrame:
1041
1067
class SeriesApply (NDFrameApply ):
1042
1068
obj : Series
1043
1069
axis : AxisInt = 0
1070
+ by_row : bool # only relevant for apply()
1044
1071
1045
1072
def __init__ (
1046
1073
self ,
1047
1074
obj : Series ,
1048
1075
func : AggFuncType ,
1049
1076
* ,
1050
1077
convert_dtype : bool | lib .NoDefault = lib .no_default ,
1078
+ by_row : bool = True ,
1051
1079
args ,
1052
1080
kwargs ,
1053
1081
) -> None :
@@ -1062,6 +1090,7 @@ def __init__(
1062
1090
stacklevel = find_stack_level (),
1063
1091
)
1064
1092
self .convert_dtype = convert_dtype
1093
+ self .by_row = by_row
1065
1094
1066
1095
super ().__init__ (
1067
1096
obj ,
@@ -1078,9 +1107,9 @@ def apply(self) -> DataFrame | Series:
1078
1107
if len (obj ) == 0 :
1079
1108
return self .apply_empty_result ()
1080
1109
1081
- # dispatch to agg
1110
+ # dispatch to handle list-like or dict-like
1082
1111
if is_list_like (self .func ):
1083
- return self .apply_multiple ()
1112
+ return self .apply_list_or_dict_like ()
1084
1113
1085
1114
if isinstance (self .func , str ):
1086
1115
# if we are a string, try to dispatch
@@ -1126,6 +1155,8 @@ def apply_standard(self) -> DataFrame | Series:
1126
1155
if isinstance (func , np .ufunc ):
1127
1156
with np .errstate (all = "ignore" ):
1128
1157
return func (obj , * self .args , ** self .kwargs )
1158
+ elif not self .by_row :
1159
+ return func (obj , * self .args , ** self .kwargs )
1129
1160
1130
1161
if self .args or self .kwargs :
1131
1162
# _map_values does not support args/kwargs
0 commit comments