@@ -1308,42 +1308,35 @@ def _transform_general(self, func, *args, **kwargs):
1308
1308
gen = self .grouper .get_iterator (obj , axis = self .axis )
1309
1309
fast_path , slow_path = self ._define_paths (func , * args , ** kwargs )
1310
1310
1311
- for name , group in gen :
1312
- if group .size == 0 :
1313
- continue
1311
+ # Determine whether to use slow or fast path by evaluating on the first group.
1312
+ # Need to handle the case of an empty generator and process the result so that
1313
+ # it does not need to be computed again.
1314
+ try :
1315
+ name , group = next (gen )
1316
+ except StopIteration :
1317
+ pass
1318
+ else :
1314
1319
object .__setattr__ (group , "name" , name )
1315
-
1316
- # Try slow path and fast path.
1317
1320
try :
1318
1321
path , res = self ._choose_path (fast_path , slow_path , group )
1319
1322
except TypeError :
1320
1323
return self ._transform_item_by_item (obj , fast_path )
1321
1324
except ValueError as err :
1322
1325
msg = "transform must return a scalar value for each group"
1323
1326
raise ValueError (msg ) from err
1324
-
1325
- if isinstance (res , Series ):
1326
-
1327
- # we need to broadcast across the
1328
- # other dimension; this will preserve dtypes
1329
- # GH14457
1330
- if res .index .is_ (obj .index ):
1331
- r = concat ([res ] * len (group .columns ), axis = 1 )
1332
- r .columns = group .columns
1333
- r .index = group .index
1334
- else :
1335
- r = self .obj ._constructor (
1336
- np .concatenate ([res .values ] * len (group .index )).reshape (
1337
- group .shape
1338
- ),
1339
- columns = group .columns ,
1340
- index = group .index ,
1341
- )
1342
-
1343
- applied .append (r )
1344
- else :
1327
+ if group .size > 0 :
1328
+ res = _wrap_transform_general_frame (self .obj , group , res )
1345
1329
applied .append (res )
1346
1330
1331
+ # Compute and process with the remaining groups
1332
+ for name , group in gen :
1333
+ if group .size == 0 :
1334
+ continue
1335
+ object .__setattr__ (group , "name" , name )
1336
+ res = path (group )
1337
+ res = _wrap_transform_general_frame (self .obj , group , res )
1338
+ applied .append (res )
1339
+
1347
1340
concat_index = obj .columns if self .axis == 0 else obj .index
1348
1341
other_axis = 1 if self .axis == 0 else 0 # switches between 0 & 1
1349
1342
concatenated = concat (applied , axis = self .axis , verify_integrity = False )
@@ -1853,3 +1846,28 @@ def func(df):
1853
1846
return self ._python_apply_general (func , self ._obj_with_exclusions )
1854
1847
1855
1848
boxplot = boxplot_frame_groupby
1849
+
1850
+
1851
+ def _wrap_transform_general_frame (
1852
+ obj : DataFrame , group : DataFrame , res : DataFrame | Series
1853
+ ) -> DataFrame :
1854
+ from pandas import concat
1855
+
1856
+ if isinstance (res , Series ):
1857
+ # we need to broadcast across the
1858
+ # other dimension; this will preserve dtypes
1859
+ # GH14457
1860
+ if res .index .is_ (obj .index ):
1861
+ res_frame = concat ([res ] * len (group .columns ), axis = 1 )
1862
+ res_frame .columns = group .columns
1863
+ res_frame .index = group .index
1864
+ else :
1865
+ res_frame = obj ._constructor (
1866
+ np .concatenate ([res .values ] * len (group .index )).reshape (group .shape ),
1867
+ columns = group .columns ,
1868
+ index = group .index ,
1869
+ )
1870
+ assert isinstance (res_frame , DataFrame )
1871
+ return res_frame
1872
+ else :
1873
+ return res
0 commit comments