diff --git a/doc/source/whatsnew/v0.25.0.rst b/doc/source/whatsnew/v0.25.0.rst index 008f6f0b8643e..888ba48c3142b 100644 --- a/doc/source/whatsnew/v0.25.0.rst +++ b/doc/source/whatsnew/v0.25.0.rst @@ -718,7 +718,7 @@ Numeric - Raises a helpful exception when a non-numeric index is sent to :meth:`interpolate` with methods which require numeric index. (:issue:`21662`) - Bug in :meth:`~pandas.eval` when comparing floats with scalar operators, for example: ``x < -0.1`` (:issue:`25928`) - Fixed bug where casting all-boolean array to integer extension array failed (:issue:`25211`) -- +- Bug in ``divmod`` with a :class:`Series` object containing zeros incorrectly raising ``AttributeError`` (:issue:`26987`) - Conversion diff --git a/pandas/core/missing.py b/pandas/core/missing.py index 4230b212f567a..4d33f9eec16fd 100644 --- a/pandas/core/missing.py +++ b/pandas/core/missing.py @@ -520,6 +520,26 @@ def fill_zeros(result, x, y, name, fill): Mask the nan's from x. """ + if name == '__divmod__' and isinstance(result, tuple): + # GH#26987; sometimes we don't have a tuple when called + # from dispatch_missing + res1 = fill_zeros(result[0], x, y, '__floordiv__', fill) + res2 = fill_zeros(result[1], x, y, '__mod__', fill) + mask = np.isinf(res1) & np.isinf(res2) + if mask.any(): + res2[mask] = np.nan + return res1, res2 + + elif name == '__rdivmod__': + # GH#26987; sometimes we don't have a tuple when called + # from dispatch_missing + res1 = fill_zeros(result[0], x, y, '__rfloordiv__', fill) + res2 = fill_zeros(result[1], x, y, '__rmod__', fill) + mask = np.isinf(res1) & np.isinf(res2) + if mask.any(): + res2[mask] = np.nan + return res1, res2 + if fill is None or is_float_dtype(result): return result diff --git a/pandas/core/ops.py b/pandas/core/ops.py index 0b9e56fd19556..7d7f2d6fffeee 100644 --- a/pandas/core/ops.py +++ b/pandas/core/ops.py @@ -1709,6 +1709,11 @@ def wrapper(left, right): rvalues = right if isinstance(rvalues, ABCSeries): rvalues = rvalues.values + elif (isinstance(rvalues, ABCIndexClass) + and rvalues.dtype.kind in 'fciu'): + # TOOD: We should do this unpacking unconditionally, but ATM doing + # so breaks on DatetimeArray because it lacks ravel() + rvalues = rvalues._values with np.errstate(all='ignore'): result = na_op(lvalues, rvalues) diff --git a/pandas/tests/arithmetic/test_numeric.py b/pandas/tests/arithmetic/test_numeric.py index f58f8981317df..63414443340c4 100644 --- a/pandas/tests/arithmetic/test_numeric.py +++ b/pandas/tests/arithmetic/test_numeric.py @@ -250,6 +250,42 @@ def test_ser_div_ser(self, dtype1, dtype2): tm.assert_series_equal(result, expected) assert not result.equals(second / first) + @pytest.mark.parametrize('dtype2', [ + np.int64, np.int32, np.int16, np.int8, + np.float64, np.float32, np.float16, + np.uint64, np.uint32, np.uint16, np.uint8]) + @pytest.mark.parametrize('dtype1', [np.int64, np.float64, np.uint64]) + def test_ser_divmod_zero(self, dtype1, dtype2): + # GH#26987 + left = pd.Series([1, 1]).astype(dtype1) + right = pd.Series([0, 2]).astype(dtype2) + + expected = left // right, left % right + result = divmod(left, right) + + tm.assert_series_equal(result[0], expected[0]) + tm.assert_series_equal(result[1], expected[1]) + + # rdivmod case + result = divmod(left.values, right) + tm.assert_series_equal(result[0], expected[0]) + tm.assert_series_equal(result[1], expected[1]) + + def test_ser_divmod_inf(self): + left = pd.Series([np.inf, 1.]) + right = pd.Series([np.inf, 2.]) + + expected = left // right, left % right + result = divmod(left, right) + + tm.assert_series_equal(result[0], expected[0]) + tm.assert_series_equal(result[1], expected[1]) + + # rdivmod case + result = divmod(left.values, right) + tm.assert_series_equal(result[0], expected[0]) + tm.assert_series_equal(result[1], expected[1]) + def test_rdiv_zero_compat(self): # GH#8674 zero_array = np.array([0] * 5)