@@ -528,6 +528,26 @@ def transform(self, func, *args, engine=None, engine_kwargs=None, **kwargs):
528
528
func , * args , engine = engine , engine_kwargs = engine_kwargs , ** kwargs
529
529
)
530
530
531
+ def _cython_transform (
532
+ self , how : str , numeric_only : bool = True , axis : int = 0 , ** kwargs
533
+ ):
534
+ assert axis == 0 # handled by caller
535
+
536
+ obj = self ._selected_obj
537
+
538
+ is_numeric = is_numeric_dtype (obj .dtype )
539
+ if numeric_only and not is_numeric :
540
+ raise DataError ("No numeric types to aggregate" )
541
+
542
+ try :
543
+ result = self .grouper ._cython_operation (
544
+ "transform" , obj ._values , how , axis , ** kwargs
545
+ )
546
+ except (NotImplementedError , TypeError ):
547
+ raise DataError ("No numeric types to aggregate" )
548
+
549
+ return obj ._constructor (result , index = self .obj .index , name = obj .name )
550
+
531
551
def _transform_general (self , func : Callable , * args , ** kwargs ) -> Series :
532
552
"""
533
553
Transform with a callable func`.
@@ -1247,6 +1267,36 @@ def _wrap_applied_output_series(
1247
1267
1248
1268
return self ._reindex_output (result )
1249
1269
1270
+ def _cython_transform (
1271
+ self , how : str , numeric_only : bool = True , axis : int = 0 , ** kwargs
1272
+ ) -> DataFrame :
1273
+ assert axis == 0 # handled by caller
1274
+ # TODO: no tests with self.ndim == 1 for DataFrameGroupBy
1275
+
1276
+ # With self.axis == 0, we have multi-block tests
1277
+ # e.g. test_rank_min_int, test_cython_transform_frame
1278
+ # test_transform_numeric_ret
1279
+ # With self.axis == 1, _get_data_to_aggregate does a transpose
1280
+ # so we always have a single block.
1281
+ mgr : Manager2D = self ._get_data_to_aggregate ()
1282
+ if numeric_only :
1283
+ mgr = mgr .get_numeric_data (copy = False )
1284
+
1285
+ def arr_func (bvalues : ArrayLike ) -> ArrayLike :
1286
+ return self .grouper ._cython_operation (
1287
+ "transform" , bvalues , how , 1 , ** kwargs
1288
+ )
1289
+
1290
+ # We could use `mgr.apply` here and not have to set_axis, but
1291
+ # we would have to do shape gymnastics for ArrayManager compat
1292
+ res_mgr = mgr .grouped_reduce (arr_func , ignore_failures = True )
1293
+ res_mgr .set_axis (1 , mgr .axes [1 ])
1294
+
1295
+ res_df = self .obj ._constructor (res_mgr )
1296
+ if self .axis == 1 :
1297
+ res_df = res_df .T
1298
+ return res_df
1299
+
1250
1300
def _transform_general (self , func , * args , ** kwargs ):
1251
1301
from pandas .core .reshape .concat import concat
1252
1302
0 commit comments