Skip to content

Commit 7bc2000

Browse files
authored
REF: simplify extension reduction tests (pandas-dev#54394)
1 parent 23b3ac8 commit 7bc2000

11 files changed

+121
-104
lines changed

pandas/tests/extension/base/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ class TestMyDtype(BaseDtypeTests):
6060
BaseBooleanReduceTests,
6161
BaseNoReduceTests,
6262
BaseNumericReduceTests,
63+
BaseReduceTests,
6364
)
6465
from pandas.tests.extension.base.reshaping import BaseReshapingTests # noqa: F401
6566
from pandas.tests.extension.base.setitem import BaseSetitemTests # noqa: F401

pandas/tests/extension/base/reduce.py

+49-34
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ class BaseReduceTests(BaseExtensionTests):
1515
make sense for numeric/boolean operations.
1616
"""
1717

18+
def _supports_reduction(self, obj, op_name: str) -> bool:
19+
# Specify if we expect this reduction to succeed.
20+
return False
21+
1822
def check_reduce(self, s, op_name, skipna):
1923
# We perform the same operation on the np.float64 data and check
2024
# that the results match. Override if you need to cast to something
@@ -66,47 +70,42 @@ def check_reduce_frame(self, ser: pd.Series, op_name: str, skipna: bool):
6670

6771
tm.assert_extension_array_equal(result1, expected)
6872

69-
70-
class BaseNoReduceTests(BaseReduceTests):
71-
"""we don't define any reductions"""
72-
73-
@pytest.mark.parametrize("skipna", [True, False])
74-
def test_reduce_series_numeric(self, data, all_numeric_reductions, skipna):
75-
op_name = all_numeric_reductions
76-
s = pd.Series(data)
77-
78-
msg = (
79-
"[Cc]annot perform|Categorical is not ordered for operation|"
80-
"does not support reduction|"
81-
)
82-
83-
with pytest.raises(TypeError, match=msg):
84-
getattr(s, op_name)(skipna=skipna)
85-
8673
@pytest.mark.parametrize("skipna", [True, False])
8774
def test_reduce_series_boolean(self, data, all_boolean_reductions, skipna):
8875
op_name = all_boolean_reductions
8976
s = pd.Series(data)
9077

91-
msg = (
92-
"[Cc]annot perform|Categorical is not ordered for operation|"
93-
"does not support reduction|"
94-
)
78+
if not self._supports_reduction(s, op_name):
79+
msg = (
80+
"[Cc]annot perform|Categorical is not ordered for operation|"
81+
"does not support reduction|"
82+
)
9583

96-
with pytest.raises(TypeError, match=msg):
97-
getattr(s, op_name)(skipna=skipna)
84+
with pytest.raises(TypeError, match=msg):
85+
getattr(s, op_name)(skipna=skipna)
9886

87+
else:
88+
self.check_reduce(s, op_name, skipna)
9989

100-
class BaseNumericReduceTests(BaseReduceTests):
10190
@pytest.mark.parametrize("skipna", [True, False])
102-
def test_reduce_series(self, data, all_numeric_reductions, skipna):
91+
def test_reduce_series_numeric(self, data, all_numeric_reductions, skipna):
10392
op_name = all_numeric_reductions
10493
s = pd.Series(data)
10594

106-
# min/max with empty produce numpy warnings
107-
with warnings.catch_warnings():
108-
warnings.simplefilter("ignore", RuntimeWarning)
109-
self.check_reduce(s, op_name, skipna)
95+
if not self._supports_reduction(s, op_name):
96+
msg = (
97+
"[Cc]annot perform|Categorical is not ordered for operation|"
98+
"does not support reduction|"
99+
)
100+
101+
with pytest.raises(TypeError, match=msg):
102+
getattr(s, op_name)(skipna=skipna)
103+
104+
else:
105+
# min/max with empty produce numpy warnings
106+
with warnings.catch_warnings():
107+
warnings.simplefilter("ignore", RuntimeWarning)
108+
self.check_reduce(s, op_name, skipna)
110109

111110
@pytest.mark.parametrize("skipna", [True, False])
112111
def test_reduce_frame(self, data, all_numeric_reductions, skipna):
@@ -118,12 +117,28 @@ def test_reduce_frame(self, data, all_numeric_reductions, skipna):
118117
if op_name in ["count", "kurt", "sem"]:
119118
pytest.skip(f"{op_name} not an array method")
120119

120+
if not self._supports_reduction(s, op_name):
121+
pytest.skip(f"Reduction {op_name} not supported for this dtype")
122+
121123
self.check_reduce_frame(s, op_name, skipna)
122124

123125

126+
# TODO: deprecate BaseNoReduceTests, BaseNumericReduceTests, BaseBooleanReduceTests
127+
class BaseNoReduceTests(BaseReduceTests):
128+
"""we don't define any reductions"""
129+
130+
131+
class BaseNumericReduceTests(BaseReduceTests):
132+
# For backward compatibility only, this only runs the numeric reductions
133+
def _supports_reduction(self, obj, op_name: str) -> bool:
134+
if op_name in ["any", "all"]:
135+
pytest.skip("These are tested in BaseBooleanReduceTests")
136+
return True
137+
138+
124139
class BaseBooleanReduceTests(BaseReduceTests):
125-
@pytest.mark.parametrize("skipna", [True, False])
126-
def test_reduce_series(self, data, all_boolean_reductions, skipna):
127-
op_name = all_boolean_reductions
128-
s = pd.Series(data)
129-
self.check_reduce(s, op_name, skipna)
140+
# For backward compatibility only, this only runs the numeric reductions
141+
def _supports_reduction(self, obj, op_name: str) -> bool:
142+
if op_name not in ["any", "all"]:
143+
pytest.skip("These are tested in BaseNumericReduceTests")
144+
return True

pandas/tests/extension/decimal/test_decimal.py

+4-5
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,9 @@ def test_fillna_series_method(self, data_missing, fillna_method):
148148

149149

150150
class Reduce:
151+
def _supports_reduction(self, obj, op_name: str) -> bool:
152+
return True
153+
151154
def check_reduce(self, s, op_name, skipna):
152155
if op_name in ["median", "skew", "kurt", "sem"]:
153156
msg = r"decimal does not support the .* operation"
@@ -185,7 +188,7 @@ def _reduce(self, name: str, *, skipna: bool = True, **kwargs):
185188
tm.assert_series_equal(result, expected)
186189

187190

188-
class TestNumericReduce(Reduce, base.BaseNumericReduceTests):
191+
class TestReduce(Reduce, base.BaseReduceTests):
189192
@pytest.mark.parametrize("skipna", [True, False])
190193
def test_reduce_frame(self, data, all_numeric_reductions, skipna):
191194
op_name = all_numeric_reductions
@@ -196,10 +199,6 @@ def test_reduce_frame(self, data, all_numeric_reductions, skipna):
196199
return super().test_reduce_frame(data, all_numeric_reductions, skipna)
197200

198201

199-
class TestBooleanReduce(Reduce, base.BaseBooleanReduceTests):
200-
pass
201-
202-
203202
class TestMethods(base.BaseMethodsTests):
204203
def test_fillna_copy_frame(self, data_missing, using_copy_on_write):
205204
warn = FutureWarning if not using_copy_on_write else None

pandas/tests/extension/json/test_json.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ def test_fillna_frame(self):
180180
unhashable = pytest.mark.xfail(reason="Unhashable")
181181

182182

183-
class TestReduce(base.BaseNoReduceTests):
183+
class TestReduce(base.BaseReduceTests):
184184
pass
185185

186186

pandas/tests/extension/test_arrow.py

+39-38
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,10 @@ def test_accumulate_series(self, data, all_numeric_accumulations, skipna, reques
416416
self.check_accumulate(ser, op_name, skipna)
417417

418418

419-
class TestBaseNumericReduce(base.BaseNumericReduceTests):
419+
class TestReduce(base.BaseReduceTests):
420+
def _supports_reduction(self, obj, op_name: str) -> bool:
421+
return True
422+
420423
def check_reduce(self, ser, op_name, skipna):
421424
pa_dtype = ser.dtype.pyarrow_dtype
422425
if op_name == "count":
@@ -437,7 +440,7 @@ def check_reduce(self, ser, op_name, skipna):
437440
tm.assert_almost_equal(result, expected)
438441

439442
@pytest.mark.parametrize("skipna", [True, False])
440-
def test_reduce_series(self, data, all_numeric_reductions, skipna, request):
443+
def test_reduce_series_numeric(self, data, all_numeric_reductions, skipna, request):
441444
pa_dtype = data.dtype.pyarrow_dtype
442445
opname = all_numeric_reductions
443446

@@ -505,44 +508,10 @@ def test_reduce_series(self, data, all_numeric_reductions, skipna, request):
505508
"median",
506509
}:
507510
request.node.add_marker(xfail_mark)
508-
super().test_reduce_series(data, all_numeric_reductions, skipna)
509-
510-
def _get_expected_reduction_dtype(self, arr, op_name: str):
511-
if op_name in ["max", "min"]:
512-
cmp_dtype = arr.dtype
513-
elif arr.dtype.name == "decimal128(7, 3)[pyarrow]":
514-
if op_name not in ["median", "var", "std"]:
515-
cmp_dtype = arr.dtype
516-
else:
517-
cmp_dtype = "float64[pyarrow]"
518-
elif op_name in ["median", "var", "std", "mean", "skew"]:
519-
cmp_dtype = "float64[pyarrow]"
520-
else:
521-
cmp_dtype = {
522-
"i": "int64[pyarrow]",
523-
"u": "uint64[pyarrow]",
524-
"f": "float64[pyarrow]",
525-
}[arr.dtype.kind]
526-
return cmp_dtype
511+
super().test_reduce_series_numeric(data, all_numeric_reductions, skipna)
527512

528513
@pytest.mark.parametrize("skipna", [True, False])
529-
def test_reduce_frame(self, data, all_numeric_reductions, skipna):
530-
op_name = all_numeric_reductions
531-
if op_name == "skew":
532-
assert not hasattr(data, op_name)
533-
return
534-
return super().test_reduce_frame(data, all_numeric_reductions, skipna)
535-
536-
@pytest.mark.parametrize("typ", ["int64", "uint64", "float64"])
537-
def test_median_not_approximate(self, typ):
538-
# GH 52679
539-
result = pd.Series([1, 2], dtype=f"{typ}[pyarrow]").median()
540-
assert result == 1.5
541-
542-
543-
class TestBaseBooleanReduce(base.BaseBooleanReduceTests):
544-
@pytest.mark.parametrize("skipna", [True, False])
545-
def test_reduce_series(
514+
def test_reduce_series_boolean(
546515
self, data, all_boolean_reductions, skipna, na_value, request
547516
):
548517
pa_dtype = data.dtype.pyarrow_dtype
@@ -574,6 +543,38 @@ def test_reduce_series(
574543
result = getattr(ser, op_name)(skipna=skipna)
575544
assert result is (op_name == "any")
576545

546+
def _get_expected_reduction_dtype(self, arr, op_name: str):
547+
if op_name in ["max", "min"]:
548+
cmp_dtype = arr.dtype
549+
elif arr.dtype.name == "decimal128(7, 3)[pyarrow]":
550+
if op_name not in ["median", "var", "std"]:
551+
cmp_dtype = arr.dtype
552+
else:
553+
cmp_dtype = "float64[pyarrow]"
554+
elif op_name in ["median", "var", "std", "mean", "skew"]:
555+
cmp_dtype = "float64[pyarrow]"
556+
else:
557+
cmp_dtype = {
558+
"i": "int64[pyarrow]",
559+
"u": "uint64[pyarrow]",
560+
"f": "float64[pyarrow]",
561+
}[arr.dtype.kind]
562+
return cmp_dtype
563+
564+
@pytest.mark.parametrize("skipna", [True, False])
565+
def test_reduce_frame(self, data, all_numeric_reductions, skipna):
566+
op_name = all_numeric_reductions
567+
if op_name == "skew":
568+
assert not hasattr(data, op_name)
569+
return
570+
return super().test_reduce_frame(data, all_numeric_reductions, skipna)
571+
572+
@pytest.mark.parametrize("typ", ["int64", "uint64", "float64"])
573+
def test_median_not_approximate(self, typ):
574+
# GH 52679
575+
result = pd.Series([1, 2], dtype=f"{typ}[pyarrow]").median()
576+
assert result == 1.5
577+
577578

578579
class TestBaseGroupby(base.BaseGroupbyTests):
579580
def test_in_numeric_groupby(self, data_for_grouping):

pandas/tests/extension/test_boolean.py

+4-5
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,10 @@ def test_groupby_sum_mincount(self, data_for_grouping, min_count):
208208
tm.assert_frame_equal(result, expected)
209209

210210

211-
class TestNumericReduce(base.BaseNumericReduceTests):
211+
class TestReduce(base.BaseReduceTests):
212+
def _supports_reduction(self, obj, op_name: str) -> bool:
213+
return True
214+
212215
def check_reduce(self, s, op_name, skipna):
213216
if op_name == "count":
214217
result = getattr(s, op_name)()
@@ -236,10 +239,6 @@ def _get_expected_reduction_dtype(self, arr, op_name: str):
236239
return cmp_dtype
237240

238241

239-
class TestBooleanReduce(base.BaseBooleanReduceTests):
240-
pass
241-
242-
243242
class TestPrinting(base.BasePrintingTests):
244243
pass
245244

pandas/tests/extension/test_categorical.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ class TestMissing(base.BaseMissingTests):
157157
pass
158158

159159

160-
class TestReduce(base.BaseNoReduceTests):
160+
class TestReduce(base.BaseReduceTests):
161161
pass
162162

163163

pandas/tests/extension/test_interval.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ class TestInterface(BaseInterval, base.BaseInterfaceTests):
105105
pass
106106

107107

108-
class TestReduce(base.BaseNoReduceTests):
108+
class TestReduce(base.BaseReduceTests):
109109
@pytest.mark.parametrize("skipna", [True, False])
110110
def test_reduce_series_numeric(self, data, all_numeric_reductions, skipna):
111111
op_name = all_numeric_reductions

pandas/tests/extension/test_masked_numeric.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,12 @@ class TestGroupby(base.BaseGroupbyTests):
226226
pass
227227

228228

229-
class TestNumericReduce(base.BaseNumericReduceTests):
229+
class TestReduce(base.BaseReduceTests):
230+
def _supports_reduction(self, obj, op_name: str) -> bool:
231+
if op_name in ["any", "all"]:
232+
pytest.skip(reason="Tested in tests/reductions/test_reductions.py")
233+
return True
234+
230235
def check_reduce(self, ser: pd.Series, op_name: str, skipna: bool):
231236
# overwrite to ensure pd.NA is tested instead of np.nan
232237
# https://github.com/pandas-dev/pandas/issues/30958
@@ -265,11 +270,6 @@ def _get_expected_reduction_dtype(self, arr, op_name: str):
265270
return cmp_dtype
266271

267272

268-
@pytest.mark.skip(reason="Tested in tests/reductions/test_reductions.py")
269-
class TestBooleanReduce(base.BaseBooleanReduceTests):
270-
pass
271-
272-
273273
class TestAccumulation(base.BaseAccumulateTests):
274274
def _supports_accumulation(self, ser: pd.Series, op_name: str) -> bool:
275275
return True

pandas/tests/extension/test_numpy.py

+14-12
Original file line numberDiff line numberDiff line change
@@ -306,27 +306,29 @@ class TestPrinting(BaseNumPyTests, base.BasePrintingTests):
306306
pass
307307

308308

309-
class TestNumericReduce(BaseNumPyTests, base.BaseNumericReduceTests):
309+
class TestReduce(BaseNumPyTests, base.BaseReduceTests):
310+
def _supports_reduction(self, obj, op_name: str) -> bool:
311+
if tm.get_dtype(obj).kind == "O":
312+
return op_name in ["sum", "min", "max", "any", "all"]
313+
return True
314+
310315
def check_reduce(self, s, op_name, skipna):
311-
result = getattr(s, op_name)(skipna=skipna)
316+
res_op = getattr(s, op_name)
312317
# avoid coercing int -> float. Just cast to the actual numpy type.
313-
expected = getattr(s.astype(s.dtype._dtype), op_name)(skipna=skipna)
318+
exp_op = getattr(s.astype(s.dtype._dtype), op_name)
319+
if op_name == "count":
320+
result = res_op()
321+
expected = exp_op()
322+
else:
323+
result = res_op(skipna=skipna)
324+
expected = exp_op(skipna=skipna)
314325
tm.assert_almost_equal(result, expected)
315326

316327
@pytest.mark.skip("tests not written yet")
317328
@pytest.mark.parametrize("skipna", [True, False])
318329
def test_reduce_frame(self, data, all_numeric_reductions, skipna):
319330
pass
320331

321-
@pytest.mark.parametrize("skipna", [True, False])
322-
def test_reduce_series(self, data, all_boolean_reductions, skipna):
323-
super().test_reduce_series(data, all_boolean_reductions, skipna)
324-
325-
326-
@skip_nested
327-
class TestBooleanReduce(BaseNumPyTests, base.BaseBooleanReduceTests):
328-
pass
329-
330332

331333
class TestMissing(BaseNumPyTests, base.BaseMissingTests):
332334
@skip_nested

pandas/tests/extension/test_string.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ def test_fillna_no_op_returns_copy(self, data):
168168
tm.assert_extension_array_equal(result, data)
169169

170170

171-
class TestNoReduce(base.BaseNoReduceTests):
171+
class TestReduce(base.BaseReduceTests):
172172
@pytest.mark.parametrize("skipna", [True, False])
173173
def test_reduce_series_numeric(self, data, all_numeric_reductions, skipna):
174174
op_name = all_numeric_reductions

0 commit comments

Comments
 (0)