Skip to content

BUG: Fix Series divmod #26987 #27130

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/source/whatsnew/v0.25.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 20 additions & 0 deletions pandas/core/missing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you do thi brlow line 546 you may not need to duplicate code

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think it ends up being a wash because we have to do these checks in multiple places

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the style of this change is not consistent with the rest of the function, it could be better integrated, for example a helper function would go a long way here.

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

Expand Down
5 changes: 5 additions & 0 deletions pandas/core/ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
36 changes: 36 additions & 0 deletions pandas/tests/arithmetic/test_numeric.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down