Skip to content

BUG: Fix divmod fill value, closes #26987 #27239

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

Merged
merged 8 commits into from
Jul 6, 2019
Merged
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 @@ -1007,7 +1007,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
1 change: 0 additions & 1 deletion doc/source/whatsnew/v0.25.1.rst
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ Timezones

Numeric
^^^^^^^

-
-
-
Expand Down
14 changes: 3 additions & 11 deletions pandas/core/ops/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ def _gen_fill_zeros(name):
"""
name = name.strip("__")
if "div" in name:
# truediv, floordiv, div, and reversed variants
# truediv, floordiv, and reversed variants
fill_value = np.inf
elif "mod" in name:
# mod, rmod
Expand Down Expand Up @@ -1668,14 +1668,7 @@ def na_op(x, y):
except TypeError:
result = masked_arith_op(x, y, op)

if isinstance(result, tuple):
# e.g. divmod
result = tuple(
missing.fill_zeros(r, x, y, op_name, fill_zeros) for r in result
)
else:
result = missing.fill_zeros(result, x, y, op_name, fill_zeros)
return result
return missing.dispatch_fill_zeros(op, x, y, result, fill_zeros)

def wrapper(left, right):
if isinstance(right, ABCDataFrame):
Expand Down Expand Up @@ -2157,8 +2150,7 @@ def na_op(x, y):
except TypeError:
result = masked_arith_op(x, y, op)

result = missing.fill_zeros(result, x, y, op_name, fill_zeros)
return result
return missing.dispatch_fill_zeros(op, x, y, result, fill_zeros)

if op_name in _op_descriptions:
# i.e. include "add" but not "__add__"
Expand Down
23 changes: 23 additions & 0 deletions pandas/core/ops/missing.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@

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

from .roperator import rdivmod


def fill_zeros(result, x, y, name, fill):
"""
Expand Down Expand Up @@ -163,3 +165,24 @@ def dispatch_missing(op, left, right, result):
res1 = fill_zeros(result[1], left, right, opstr, np.nan)
result = (res0, res1)
return result


# FIXME: de-duplicate with dispatch_missing
def dispatch_fill_zeros(op, left, right, result, fill_value):
"""
Call fill_zeros with the appropriate fill value depending on the operation,
with special logic for divmod and rdivmod.
"""
if op is divmod:
result = (
fill_zeros(result[0], left, right, "__floordiv__", np.inf),
fill_zeros(result[1], left, right, "__mod__", np.nan),
)
elif op is rdivmod:
result = (
fill_zeros(result[0], left, right, "__rfloordiv__", np.inf),
fill_zeros(result[1], left, right, "__rmod__", np.nan),
)
else:
result = fill_zeros(result, left, right, op.__name__, fill_value)
return result
57 changes: 39 additions & 18 deletions pandas/tests/arithmetic/test_numeric.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,25 +265,11 @@ def test_divmod_zero(self, zero, numeric_idx):

# ------------------------------------------------------------------

@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_div_ser(self, dtype1, dtype2):
def test_ser_div_ser(self, dtype1, any_real_dtype):
# no longer do integer div for any ops, but deal with the 0's
dtype2 = any_real_dtype

first = Series([3, 4, 5, 8], name="first").astype(dtype1)
second = Series([0, 0, 0, 3], name="second").astype(dtype2)

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

@pytest.mark.parametrize("dtype1", [np.int64, np.float64, np.uint64])
def test_ser_divmod_zero(self, dtype1, any_real_dtype):
# GH#26987
dtype2 = any_real_dtype
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.0])
right = pd.Series([np.inf, 2.0])

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 Expand Up @@ -662,7 +681,9 @@ def test_modulo2(self):
result2 = p["second"] % p["first"]
assert not result.equals(result2)

# GH#9144
def test_modulo_zero_int(self):
# GH#9144
with np.errstate(all="ignore"):
s = Series([0, 1])

result = s % 0
Expand Down
1 change: 1 addition & 0 deletions pandas/tests/sparse/series/test_series.py
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,7 @@ def check(a, b):
_check_op(a, b, lambda x, y: operator.floordiv(y, x))
_check_op(a, b, lambda x, y: operator.mul(y, x))

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