1+ from typing import final
12import warnings
23
34import pytest
@@ -15,6 +16,9 @@ class BaseReduceTests(BaseExtensionTests):
1516 """
1617
1718 def check_reduce (self , s , op_name , skipna ):
19+ # We perform the same operation on the np.float64 data and check
20+ # that the results match. Override if you need to cast to something
21+ # other than float64.
1822 res_op = getattr (s , op_name )
1923 exp_op = getattr (s .astype ("float64" ), op_name )
2024 if op_name == "count" :
@@ -25,6 +29,43 @@ def check_reduce(self, s, op_name, skipna):
2529 expected = exp_op (skipna = skipna )
2630 tm .assert_almost_equal (result , expected )
2731
32+ def _get_expected_reduction_dtype (self , arr , op_name : str ):
33+ # Find the expected dtype when the given reduction is done on a DataFrame
34+ # column with this array. The default assumes float64-like behavior,
35+ # i.e. retains the dtype.
36+ return arr .dtype
37+
38+ # We anticipate that authors should not need to override check_reduce_frame,
39+ # but should be able to do any necessary overriding in
40+ # _get_expected_reduction_dtype. If you have a use case where this
41+ # does not hold, please let us know at github.com/pandas-dev/pandas/issues.
42+ @final
43+ def check_reduce_frame (self , ser : pd .Series , op_name : str , skipna : bool ):
44+ # Check that the 2D reduction done in a DataFrame reduction "looks like"
45+ # a wrapped version of the 1D reduction done by Series.
46+ arr = ser .array
47+ df = pd .DataFrame ({"a" : arr })
48+
49+ kwargs = {"ddof" : 1 } if op_name in ["var" , "std" ] else {}
50+
51+ cmp_dtype = self ._get_expected_reduction_dtype (arr , op_name )
52+
53+ # The DataFrame method just calls arr._reduce with keepdims=True,
54+ # so this first check is perfunctory.
55+ result1 = arr ._reduce (op_name , skipna = skipna , keepdims = True , ** kwargs )
56+ result2 = getattr (df , op_name )(skipna = skipna , ** kwargs ).array
57+ tm .assert_extension_array_equal (result1 , result2 )
58+
59+ # Check that the 2D reduction looks like a wrapped version of the
60+ # 1D reduction
61+ if not skipna and ser .isna ().any ():
62+ expected = pd .array ([pd .NA ], dtype = cmp_dtype )
63+ else :
64+ exp_value = getattr (ser .dropna (), op_name )()
65+ expected = pd .array ([exp_value ], dtype = cmp_dtype )
66+
67+ tm .assert_extension_array_equal (result1 , expected )
68+
2869
2970class BaseNoReduceTests (BaseReduceTests ):
3071 """we don't define any reductions"""
@@ -71,9 +112,12 @@ def test_reduce_series(self, data, all_numeric_reductions, skipna):
71112 def test_reduce_frame (self , data , all_numeric_reductions , skipna ):
72113 op_name = all_numeric_reductions
73114 s = pd .Series (data )
74- if not is_numeric_dtype (s ):
115+ if not is_numeric_dtype (s . dtype ):
75116 pytest .skip ("not numeric dtype" )
76117
118+ if op_name in ["count" , "kurt" , "sem" ]:
119+ pytest .skip (f"{ op_name } not an array method" )
120+
77121 self .check_reduce_frame (s , op_name , skipna )
78122
79123
0 commit comments