Skip to content

Commit 20a85c3

Browse files
jbrockmendeljreback
authored andcommitted
BUG: Fix divmod fill value, closes #26987 (#27239)
1 parent f4752fc commit 20a85c3

File tree

6 files changed

+67
-31
lines changed

6 files changed

+67
-31
lines changed

doc/source/whatsnew/v0.25.0.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -1007,7 +1007,7 @@ Numeric
10071007
- Raises a helpful exception when a non-numeric index is sent to :meth:`interpolate` with methods which require numeric index. (:issue:`21662`)
10081008
- Bug in :meth:`~pandas.eval` when comparing floats with scalar operators, for example: ``x < -0.1`` (:issue:`25928`)
10091009
- Fixed bug where casting all-boolean array to integer extension array failed (:issue:`25211`)
1010-
-
1010+
- Bug in ``divmod`` with a :class:`Series` object containing zeros incorrectly raising ``AttributeError`` (:issue:`26987`)
10111011
-
10121012
10131013
Conversion

doc/source/whatsnew/v0.25.1.rst

-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ Timezones
5656

5757
Numeric
5858
^^^^^^^
59-
6059
-
6160
-
6261
-

pandas/core/ops/__init__.py

+3-11
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ def _gen_fill_zeros(name):
249249
"""
250250
name = name.strip("__")
251251
if "div" in name:
252-
# truediv, floordiv, div, and reversed variants
252+
# truediv, floordiv, and reversed variants
253253
fill_value = np.inf
254254
elif "mod" in name:
255255
# mod, rmod
@@ -1668,14 +1668,7 @@ def na_op(x, y):
16681668
except TypeError:
16691669
result = masked_arith_op(x, y, op)
16701670

1671-
if isinstance(result, tuple):
1672-
# e.g. divmod
1673-
result = tuple(
1674-
missing.fill_zeros(r, x, y, op_name, fill_zeros) for r in result
1675-
)
1676-
else:
1677-
result = missing.fill_zeros(result, x, y, op_name, fill_zeros)
1678-
return result
1671+
return missing.dispatch_fill_zeros(op, x, y, result, fill_zeros)
16791672

16801673
def wrapper(left, right):
16811674
if isinstance(right, ABCDataFrame):
@@ -2157,8 +2150,7 @@ def na_op(x, y):
21572150
except TypeError:
21582151
result = masked_arith_op(x, y, op)
21592152

2160-
result = missing.fill_zeros(result, x, y, op_name, fill_zeros)
2161-
return result
2153+
return missing.dispatch_fill_zeros(op, x, y, result, fill_zeros)
21622154

21632155
if op_name in _op_descriptions:
21642156
# i.e. include "add" but not "__add__"

pandas/core/ops/missing.py

+23
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727

2828
from pandas.core.dtypes.common import is_float_dtype, is_integer_dtype, is_scalar
2929

30+
from .roperator import rdivmod
31+
3032

3133
def fill_zeros(result, x, y, name, fill):
3234
"""
@@ -163,3 +165,24 @@ def dispatch_missing(op, left, right, result):
163165
res1 = fill_zeros(result[1], left, right, opstr, np.nan)
164166
result = (res0, res1)
165167
return result
168+
169+
170+
# FIXME: de-duplicate with dispatch_missing
171+
def dispatch_fill_zeros(op, left, right, result, fill_value):
172+
"""
173+
Call fill_zeros with the appropriate fill value depending on the operation,
174+
with special logic for divmod and rdivmod.
175+
"""
176+
if op is divmod:
177+
result = (
178+
fill_zeros(result[0], left, right, "__floordiv__", np.inf),
179+
fill_zeros(result[1], left, right, "__mod__", np.nan),
180+
)
181+
elif op is rdivmod:
182+
result = (
183+
fill_zeros(result[0], left, right, "__rfloordiv__", np.inf),
184+
fill_zeros(result[1], left, right, "__rmod__", np.nan),
185+
)
186+
else:
187+
result = fill_zeros(result, left, right, op.__name__, fill_value)
188+
return result

pandas/tests/arithmetic/test_numeric.py

+39-18
Original file line numberDiff line numberDiff line change
@@ -265,25 +265,11 @@ def test_divmod_zero(self, zero, numeric_idx):
265265

266266
# ------------------------------------------------------------------
267267

268-
@pytest.mark.parametrize(
269-
"dtype2",
270-
[
271-
np.int64,
272-
np.int32,
273-
np.int16,
274-
np.int8,
275-
np.float64,
276-
np.float32,
277-
np.float16,
278-
np.uint64,
279-
np.uint32,
280-
np.uint16,
281-
np.uint8,
282-
],
283-
)
284268
@pytest.mark.parametrize("dtype1", [np.int64, np.float64, np.uint64])
285-
def test_ser_div_ser(self, dtype1, dtype2):
269+
def test_ser_div_ser(self, dtype1, any_real_dtype):
286270
# no longer do integer div for any ops, but deal with the 0's
271+
dtype2 = any_real_dtype
272+
287273
first = Series([3, 4, 5, 8], name="first").astype(dtype1)
288274
second = Series([0, 0, 0, 3], name="second").astype(dtype2)
289275

@@ -299,6 +285,39 @@ def test_ser_div_ser(self, dtype1, dtype2):
299285
tm.assert_series_equal(result, expected)
300286
assert not result.equals(second / first)
301287

288+
@pytest.mark.parametrize("dtype1", [np.int64, np.float64, np.uint64])
289+
def test_ser_divmod_zero(self, dtype1, any_real_dtype):
290+
# GH#26987
291+
dtype2 = any_real_dtype
292+
left = pd.Series([1, 1]).astype(dtype1)
293+
right = pd.Series([0, 2]).astype(dtype2)
294+
295+
expected = left // right, left % right
296+
result = divmod(left, right)
297+
298+
tm.assert_series_equal(result[0], expected[0])
299+
tm.assert_series_equal(result[1], expected[1])
300+
301+
# rdivmod case
302+
result = divmod(left.values, right)
303+
tm.assert_series_equal(result[0], expected[0])
304+
tm.assert_series_equal(result[1], expected[1])
305+
306+
def test_ser_divmod_inf(self):
307+
left = pd.Series([np.inf, 1.0])
308+
right = pd.Series([np.inf, 2.0])
309+
310+
expected = left // right, left % right
311+
result = divmod(left, right)
312+
313+
tm.assert_series_equal(result[0], expected[0])
314+
tm.assert_series_equal(result[1], expected[1])
315+
316+
# rdivmod case
317+
result = divmod(left.values, right)
318+
tm.assert_series_equal(result[0], expected[0])
319+
tm.assert_series_equal(result[1], expected[1])
320+
302321
def test_rdiv_zero_compat(self):
303322
# GH#8674
304323
zero_array = np.array([0] * 5)
@@ -662,7 +681,9 @@ def test_modulo2(self):
662681
result2 = p["second"] % p["first"]
663682
assert not result.equals(result2)
664683

665-
# GH#9144
684+
def test_modulo_zero_int(self):
685+
# GH#9144
686+
with np.errstate(all="ignore"):
666687
s = Series([0, 1])
667688

668689
result = s % 0

pandas/tests/sparse/series/test_series.py

+1
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,7 @@ def check(a, b):
578578
_check_op(a, b, lambda x, y: operator.floordiv(y, x))
579579
_check_op(a, b, lambda x, y: operator.mul(y, x))
580580

581+
# FIXME: don't leave commented-out
581582
# NaN ** 0 = 1 in C?
582583
# _check_op(a, b, operator.pow)
583584
# _check_op(a, b, lambda x, y: operator.pow(y, x))

0 commit comments

Comments
 (0)