diff --git a/pandas/_testing/__init__.py b/pandas/_testing/__init__.py index a28b119854bb7..0b2be53131af6 100644 --- a/pandas/_testing/__init__.py +++ b/pandas/_testing/__init__.py @@ -88,6 +88,7 @@ assert_timedelta_array_equal, raise_assert_detail, ) +from pandas._testing.compat import get_dtype # noqa:F401 from pandas._testing.contexts import ( # noqa:F401 RNGContext, decompress_file, diff --git a/pandas/_testing/compat.py b/pandas/_testing/compat.py new file mode 100644 index 0000000000000..1b7d038214949 --- /dev/null +++ b/pandas/_testing/compat.py @@ -0,0 +1,13 @@ +""" +Helpers for sharing tests between DataFrame/Series +""" + +from pandas import DataFrame + + +def get_dtype(obj): + if isinstance(obj, DataFrame): + # Note: we are assuming only one column + return obj.dtypes.iat[0] + else: + return obj.dtype diff --git a/pandas/tests/extension/base/ops.py b/pandas/tests/extension/base/ops.py index c93603398977e..3ba998477fb38 100644 --- a/pandas/tests/extension/base/ops.py +++ b/pandas/tests/extension/base/ops.py @@ -18,17 +18,21 @@ def check_opname(self, s, op_name, other, exc=Exception): self._check_op(s, op, other, op_name, exc) + def _combine(self, obj, other, op): + if isinstance(obj, pd.DataFrame): + if len(obj.columns) != 1: + raise NotImplementedError + expected = obj.iloc[:, 0].combine(other, op).to_frame() + else: + expected = obj.combine(other, op) + return expected + def _check_op(self, s, op, other, op_name, exc=NotImplementedError): if exc is None: result = op(s, other) - if isinstance(s, pd.DataFrame): - if len(s.columns) != 1: - raise NotImplementedError - expected = s.iloc[:, 0].combine(other, op).to_frame() - self.assert_frame_equal(result, expected) - else: - expected = s.combine(other, op) - self.assert_series_equal(result, expected) + expected = self._combine(s, other, op) + assert isinstance(result, type(s)) + self.assert_equal(result, expected) else: with pytest.raises(exc): op(s, other) @@ -72,7 +76,6 @@ def test_arith_series_with_scalar(self, data, all_arithmetic_operators): s = pd.Series(data) self.check_opname(s, op_name, s.iloc[0], exc=self.series_scalar_exc) - @pytest.mark.xfail(run=False, reason="_reduce needs implementation") def test_arith_frame_with_scalar(self, data, all_arithmetic_operators): # frame & scalar op_name = all_arithmetic_operators diff --git a/pandas/tests/extension/json/test_json.py b/pandas/tests/extension/json/test_json.py index 164a39498ec73..a63aaef52ccef 100644 --- a/pandas/tests/extension/json/test_json.py +++ b/pandas/tests/extension/json/test_json.py @@ -322,6 +322,12 @@ class TestArithmeticOps(BaseJSON, base.BaseArithmeticOpsTests): def test_error(self, data, all_arithmetic_operators): pass + def test_arith_frame_with_scalar(self, data, all_arithmetic_operators, request): + if len(data[0]) != 1: + mark = pytest.mark.xfail(reason="raises in coercing to Series") + request.node.add_marker(mark) + super().test_arith_frame_with_scalar(data, all_arithmetic_operators) + def test_add_series_with_extension_array(self, data): ser = pd.Series(data) with pytest.raises(TypeError, match="unsupported"): diff --git a/pandas/tests/extension/test_floating.py b/pandas/tests/extension/test_floating.py index 440d7391c558f..53a31ead73515 100644 --- a/pandas/tests/extension/test_floating.py +++ b/pandas/tests/extension/test_floating.py @@ -99,6 +99,7 @@ def check_opname(self, s, op_name, other, exc=None): def _check_op(self, s, op, other, op_name, exc=NotImplementedError): if exc is None: + sdtype = tm.get_dtype(s) if ( hasattr(other, "dtype") and not is_extension_array_dtype(other.dtype) @@ -106,15 +107,15 @@ def _check_op(self, s, op, other, op_name, exc=NotImplementedError): ): # other is np.float64 and would therefore always result in # upcasting, so keeping other as same numpy_dtype - other = other.astype(s.dtype.numpy_dtype) + other = other.astype(sdtype.numpy_dtype) result = op(s, other) - expected = s.combine(other, op) + expected = self._combine(s, other, op) # combine method result in 'biggest' (float64) dtype - expected = expected.astype(s.dtype) + expected = expected.astype(sdtype) - self.assert_series_equal(result, expected) + self.assert_equal(result, expected) else: with pytest.raises(exc): op(s, other) diff --git a/pandas/tests/extension/test_integer.py b/pandas/tests/extension/test_integer.py index 2a469bb388fbe..1f9a24d4242d1 100644 --- a/pandas/tests/extension/test_integer.py +++ b/pandas/tests/extension/test_integer.py @@ -111,7 +111,8 @@ def check_opname(self, s, op_name, other, exc=None): def _check_op(self, s, op, other, op_name, exc=NotImplementedError): if exc is None: - if s.dtype.is_unsigned_integer and (op_name == "__rsub__"): + sdtype = tm.get_dtype(s) + if sdtype.is_unsigned_integer and (op_name == "__rsub__"): # TODO see https://github.com/pandas-dev/pandas/issues/22023 pytest.skip("unsigned subtraction gives negative values") @@ -122,21 +123,21 @@ def _check_op(self, s, op, other, op_name, exc=NotImplementedError): ): # other is np.int64 and would therefore always result in # upcasting, so keeping other as same numpy_dtype - other = other.astype(s.dtype.numpy_dtype) + other = other.astype(sdtype.numpy_dtype) result = op(s, other) - expected = s.combine(other, op) + expected = self._combine(s, other, op) if op_name in ("__rtruediv__", "__truediv__", "__div__"): expected = expected.fillna(np.nan).astype("Float64") elif op_name.startswith("__r"): # TODO reverse operators result in object dtype # see https://github.com/pandas-dev/pandas/issues/22024 - expected = expected.astype(s.dtype) - result = result.astype(s.dtype) + expected = expected.astype(sdtype) + result = result.astype(sdtype) else: # combine method result in 'biggest' (int64) dtype - expected = expected.astype(s.dtype) + expected = expected.astype(sdtype) pass if (op_name == "__rpow__") and isinstance(other, pd.Series): @@ -144,7 +145,7 @@ def _check_op(self, s, op, other, op_name, exc=NotImplementedError): # see https://github.com/pandas-dev/pandas/issues/22022 result = result.fillna(1) - self.assert_series_equal(result, expected) + self.assert_equal(result, expected) else: with pytest.raises(exc): op(s, other) diff --git a/pandas/tests/extension/test_sparse.py b/pandas/tests/extension/test_sparse.py index 86f9080571459..b1027ea3f6881 100644 --- a/pandas/tests/extension/test_sparse.py +++ b/pandas/tests/extension/test_sparse.py @@ -16,6 +16,7 @@ import numpy as np import pytest +from pandas.compat import IS64, is_platform_windows from pandas.errors import PerformanceWarning from pandas.core.dtypes.common import is_object_dtype @@ -403,6 +404,25 @@ def test_arith_series_with_array(self, data, all_arithmetic_operators): self._skip_if_different_combine(data) super().test_arith_series_with_array(data, all_arithmetic_operators) + def test_arith_frame_with_scalar(self, data, all_arithmetic_operators, request): + if data.dtype.fill_value != 0: + pass + elif all_arithmetic_operators.strip("_") not in [ + "mul", + "rmul", + "floordiv", + "rfloordiv", + "pow", + "mod", + "rmod", + ]: + mark = pytest.mark.xfail(reason="result dtype.fill_value mismatch") + request.node.add_marker(mark) + elif is_platform_windows() or not IS64: + mark = pytest.mark.xfail(reason="results are int32, expected int64") + request.node.add_marker(mark) + super().test_arith_frame_with_scalar(data, all_arithmetic_operators) + class TestComparisonOps(BaseSparseTests, base.BaseComparisonOpsTests): def _compare_other(self, s, data, op_name, other):