@@ -1277,6 +1277,7 @@ def result_to_bool(result: np.ndarray, inference: Type) -> np.ndarray:
12771277 return self ._get_cythonized_result (
12781278 "group_any_all" ,
12791279 aggregate = True ,
1280+ numeric_only = False ,
12801281 cython_dtype = np .dtype (np .uint8 ),
12811282 needs_values = True ,
12821283 needs_mask = True ,
@@ -1433,18 +1434,16 @@ def std(self, ddof: int = 1):
14331434 Series or DataFrame
14341435 Standard deviation of values within each group.
14351436 """
1436- result = self .var (ddof = ddof )
1437- if result .ndim == 1 :
1438- result = np .sqrt (result )
1439- else :
1440- cols = result .columns .get_indexer_for (
1441- result .columns .difference (self .exclusions ).unique ()
1442- )
1443- # TODO(GH-22046) - setting with iloc broken if labels are not unique
1444- # .values to remove labels
1445- result .iloc [:, cols ] = np .sqrt (result .iloc [:, cols ]).values
1446-
1447- return result
1437+ return self ._get_cythonized_result (
1438+ "group_var_float64" ,
1439+ aggregate = True ,
1440+ needs_counts = True ,
1441+ needs_values = True ,
1442+ needs_2d = True ,
1443+ cython_dtype = np .dtype (np .float64 ),
1444+ post_processing = lambda vals , inference : np .sqrt (vals ),
1445+ ddof = ddof ,
1446+ )
14481447
14491448 @Substitution (name = "groupby" )
14501449 @Appender (_common_see_also )
@@ -1778,6 +1777,7 @@ def _fill(self, direction, limit=None):
17781777
17791778 return self ._get_cythonized_result (
17801779 "group_fillna_indexer" ,
1780+ numeric_only = False ,
17811781 needs_mask = True ,
17821782 cython_dtype = np .dtype (np .int64 ),
17831783 result_is_index = True ,
@@ -2078,6 +2078,7 @@ def post_processor(vals: np.ndarray, inference: Optional[Type]) -> np.ndarray:
20782078 return self ._get_cythonized_result (
20792079 "group_quantile" ,
20802080 aggregate = True ,
2081+ numeric_only = False ,
20812082 needs_values = True ,
20822083 needs_mask = True ,
20832084 cython_dtype = np .dtype (np .float64 ),
@@ -2367,7 +2368,11 @@ def _get_cythonized_result(
23672368 how : str ,
23682369 cython_dtype : np .dtype ,
23692370 aggregate : bool = False ,
2371+ numeric_only : bool = True ,
2372+ needs_counts : bool = False ,
23702373 needs_values : bool = False ,
2374+ needs_2d : bool = False ,
2375+ min_count : Optional [int ] = None ,
23712376 needs_mask : bool = False ,
23722377 needs_ngroups : bool = False ,
23732378 result_is_index : bool = False ,
@@ -2386,9 +2391,18 @@ def _get_cythonized_result(
23862391 aggregate : bool, default False
23872392 Whether the result should be aggregated to match the number of
23882393 groups
2394+ numeric_only : bool, default True
2395+ Whether only numeric datatypes should be computed
2396+ needs_counts : bool, default False
2397+ Whether the counts should be a part of the Cython call
23892398 needs_values : bool, default False
23902399 Whether the values should be a part of the Cython call
23912400 signature
2401+ needs_2d : bool, default False
2402+ Whether the values and result of the Cython call signature
2403+ are at least 2-dimensional.
2404+ min_count : int, default None
2405+ When not None, min_count for the Cython call
23922406 needs_mask : bool, default False
23932407 Whether boolean mask needs to be part of the Cython call
23942408 signature
@@ -2418,7 +2432,7 @@ def _get_cythonized_result(
24182432 if result_is_index and aggregate :
24192433 raise ValueError ("'result_is_index' and 'aggregate' cannot both be True!" )
24202434 if post_processing :
2421- if not callable (pre_processing ):
2435+ if not callable (post_processing ):
24222436 raise ValueError ("'post_processing' must be a callable!" )
24232437 if pre_processing :
24242438 if not callable (pre_processing ):
@@ -2438,21 +2452,39 @@ def _get_cythonized_result(
24382452 name = obj .name
24392453 values = obj ._values
24402454
2455+ if numeric_only and not is_numeric_dtype (values ):
2456+ continue
2457+
24412458 if aggregate :
24422459 result_sz = ngroups
24432460 else :
24442461 result_sz = len (values )
24452462
24462463 result = np .zeros (result_sz , dtype = cython_dtype )
2447- func = partial (base_func , result , labels )
2464+ if needs_2d :
2465+ result = result .reshape ((- 1 , 1 ))
2466+ func = partial (base_func , result )
2467+
24482468 inferences = None
24492469
2470+ if needs_counts :
2471+ counts = np .zeros (self .ngroups , dtype = np .int64 )
2472+ func = partial (func , counts )
2473+
24502474 if needs_values :
24512475 vals = values
24522476 if pre_processing :
24532477 vals , inferences = pre_processing (vals )
2478+ if needs_2d :
2479+ vals = vals .reshape ((- 1 , 1 ))
2480+ vals = vals .astype (cython_dtype , copy = False )
24542481 func = partial (func , vals )
24552482
2483+ func = partial (func , labels )
2484+
2485+ if min_count is not None :
2486+ func = partial (func , min_count )
2487+
24562488 if needs_mask :
24572489 mask = isna (values ).view (np .uint8 )
24582490 func = partial (func , mask )
@@ -2462,6 +2494,9 @@ def _get_cythonized_result(
24622494
24632495 func (** kwargs ) # Call func to modify indexer values in place
24642496
2497+ if needs_2d :
2498+ result = result .reshape (- 1 )
2499+
24652500 if result_is_index :
24662501 result = algorithms .take_nd (values , result )
24672502
@@ -2512,6 +2547,7 @@ def shift(self, periods=1, freq=None, axis=0, fill_value=None):
25122547
25132548 return self ._get_cythonized_result (
25142549 "group_shift_indexer" ,
2550+ numeric_only = False ,
25152551 cython_dtype = np .dtype (np .int64 ),
25162552 needs_ngroups = True ,
25172553 result_is_index = True ,
0 commit comments