diff --git a/pandas/tests/extension/base/accumulate.py b/pandas/tests/extension/base/accumulate.py index 6774fcc27f35c..d60b41ccbb46e 100644 --- a/pandas/tests/extension/base/accumulate.py +++ b/pandas/tests/extension/base/accumulate.py @@ -11,28 +11,32 @@ class BaseAccumulateTests(BaseExtensionTests): make sense for numeric/boolean operations. """ - def check_accumulate(self, s, op_name, skipna): - result = getattr(s, op_name)(skipna=skipna) + def _supports_accumulation(self, ser: pd.Series, op_name: str) -> bool: + # Do we expect this accumulation to be supported for this dtype? + # We default to assuming "no"; subclass authors should override here. + return False + + def check_accumulate(self, ser: pd.Series, op_name: str, skipna: bool): + alt = ser.astype("float64") + result = getattr(ser, op_name)(skipna=skipna) if result.dtype == pd.Float32Dtype() and op_name == "cumprod" and skipna: + # TODO: avoid special-casing here pytest.skip( f"Float32 precision lead to large differences with op {op_name} " f"and skipna={skipna}" ) - expected = getattr(s.astype("float64"), op_name)(skipna=skipna) + expected = getattr(alt, op_name)(skipna=skipna) tm.assert_series_equal(result, expected, check_dtype=False) - @pytest.mark.parametrize("skipna", [True, False]) - def test_accumulate_series_raises(self, data, all_numeric_accumulations, skipna): - op_name = all_numeric_accumulations - ser = pd.Series(data) - - with pytest.raises(NotImplementedError): - getattr(ser, op_name)(skipna=skipna) - @pytest.mark.parametrize("skipna", [True, False]) def test_accumulate_series(self, data, all_numeric_accumulations, skipna): op_name = all_numeric_accumulations ser = pd.Series(data) - self.check_accumulate(ser, op_name, skipna) + + if self._supports_accumulation(ser, op_name): + self.check_accumulate(ser, op_name, skipna) + else: + with pytest.raises(NotImplementedError): + getattr(ser, op_name)(skipna=skipna) diff --git a/pandas/tests/extension/test_arrow.py b/pandas/tests/extension/test_arrow.py index 0bb9e02ca732a..7c4ea2d4d7b88 100644 --- a/pandas/tests/extension/test_arrow.py +++ b/pandas/tests/extension/test_arrow.py @@ -354,47 +354,32 @@ def check_accumulate(self, ser, op_name, skipna): expected = getattr(ser.astype("Float64"), op_name)(skipna=skipna) tm.assert_series_equal(result, expected, check_dtype=False) - @pytest.mark.parametrize("skipna", [True, False]) - def test_accumulate_series_raises(self, data, all_numeric_accumulations, skipna): - pa_type = data.dtype.pyarrow_dtype - if ( - ( - pa.types.is_integer(pa_type) - or pa.types.is_floating(pa_type) - or pa.types.is_duration(pa_type) - ) - and all_numeric_accumulations == "cumsum" - and not pa_version_under9p0 - ): - pytest.skip("These work, are tested by test_accumulate_series.") + def _supports_accumulation(self, ser: pd.Series, op_name: str) -> bool: + # error: Item "dtype[Any]" of "dtype[Any] | ExtensionDtype" has no + # attribute "pyarrow_dtype" + pa_type = ser.dtype.pyarrow_dtype # type: ignore[union-attr] - op_name = all_numeric_accumulations - ser = pd.Series(data) - - with pytest.raises(NotImplementedError): - getattr(ser, op_name)(skipna=skipna) - - @pytest.mark.parametrize("skipna", [True, False]) - def test_accumulate_series(self, data, all_numeric_accumulations, skipna, request): - pa_type = data.dtype.pyarrow_dtype - op_name = all_numeric_accumulations - ser = pd.Series(data) - - do_skip = False if pa.types.is_string(pa_type) or pa.types.is_binary(pa_type): if op_name in ["cumsum", "cumprod"]: - do_skip = True + return False elif pa.types.is_temporal(pa_type) and not pa.types.is_duration(pa_type): if op_name in ["cumsum", "cumprod"]: - do_skip = True + return False elif pa.types.is_duration(pa_type): if op_name == "cumprod": - do_skip = True + return False + return True - if do_skip: - pytest.skip( - f"{op_name} should *not* work, we test in " - "test_accumulate_series_raises that these correctly raise." + @pytest.mark.parametrize("skipna", [True, False]) + def test_accumulate_series(self, data, all_numeric_accumulations, skipna, request): + pa_type = data.dtype.pyarrow_dtype + op_name = all_numeric_accumulations + ser = pd.Series(data) + + if not self._supports_accumulation(ser, op_name): + # The base class test will check that we raise + return super().test_accumulate_series( + data, all_numeric_accumulations, skipna ) if all_numeric_accumulations != "cumsum" or pa_version_under9p0: diff --git a/pandas/tests/extension/test_boolean.py b/pandas/tests/extension/test_boolean.py index a4910f29df9b4..3d9798169c736 100644 --- a/pandas/tests/extension/test_boolean.py +++ b/pandas/tests/extension/test_boolean.py @@ -274,6 +274,9 @@ class TestUnaryOps(base.BaseUnaryOpsTests): class TestAccumulation(base.BaseAccumulateTests): + def _supports_accumulation(self, ser: pd.Series, op_name: str) -> bool: + return True + def check_accumulate(self, s, op_name, skipna): length = 64 if not IS64 or is_platform_windows(): @@ -288,10 +291,6 @@ def check_accumulate(self, s, op_name, skipna): expected = expected.astype("boolean") tm.assert_series_equal(result, expected) - @pytest.mark.parametrize("skipna", [True, False]) - def test_accumulate_series_raises(self, data, all_numeric_accumulations, skipna): - pass - class TestParsing(base.BaseParsingTests): pass diff --git a/pandas/tests/extension/test_categorical.py b/pandas/tests/extension/test_categorical.py index a712e2992c120..fc4dfe3af3bca 100644 --- a/pandas/tests/extension/test_categorical.py +++ b/pandas/tests/extension/test_categorical.py @@ -157,9 +157,7 @@ class TestReduce(base.BaseNoReduceTests): class TestAccumulate(base.BaseAccumulateTests): - @pytest.mark.parametrize("skipna", [True, False]) - def test_accumulate_series(self, data, all_numeric_accumulations, skipna): - pass + pass class TestMethods(base.BaseMethodsTests): diff --git a/pandas/tests/extension/test_masked_numeric.py b/pandas/tests/extension/test_masked_numeric.py index 6262f9ceac561..fc22ccabd7104 100644 --- a/pandas/tests/extension/test_masked_numeric.py +++ b/pandas/tests/extension/test_masked_numeric.py @@ -290,9 +290,8 @@ class TestBooleanReduce(base.BaseBooleanReduceTests): class TestAccumulation(base.BaseAccumulateTests): - @pytest.mark.parametrize("skipna", [True, False]) - def test_accumulate_series_raises(self, data, all_numeric_accumulations, skipna): - pass + def _supports_accumulation(self, ser: pd.Series, op_name: str) -> bool: + return True def check_accumulate(self, ser: pd.Series, op_name: str, skipna: bool): # overwrite to ensure pd.NA is tested instead of np.nan diff --git a/pandas/tests/extension/test_sparse.py b/pandas/tests/extension/test_sparse.py index 851a630dbc1f2..77898abb70f4f 100644 --- a/pandas/tests/extension/test_sparse.py +++ b/pandas/tests/extension/test_sparse.py @@ -476,6 +476,4 @@ def test_EA_types(self, engine, data): class TestNoNumericAccumulations(base.BaseAccumulateTests): - @pytest.mark.parametrize("skipna", [True, False]) - def test_accumulate_series(self, data, all_numeric_accumulations, skipna): - pass + pass