Skip to content

Commit 00ae553

Browse files
Backport PR #36552: REGR: Series.__mod__ behaves different with numexpr (#36750)
Co-authored-by: Simon Hawkins <[email protected]>
1 parent 637bdc3 commit 00ae553

File tree

4 files changed

+44
-4
lines changed

4 files changed

+44
-4
lines changed

doc/source/whatsnew/v1.1.3.rst

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ Fixed regressions
3434
- Fixed regression when adding a :meth:`timedelta_range` to a :class:`Timestamp` raised a ``ValueError`` (:issue:`35897`)
3535
- Fixed regression in :meth:`Series.__getitem__` incorrectly raising when the input was a tuple (:issue:`35534`)
3636
- Fixed regression in :meth:`Series.__getitem__` incorrectly raising when the input was a frozenset (:issue:`35747`)
37+
- Fixed regression in modulo of :class:`Index`, :class:`Series` and :class:`DataFrame` using ``numexpr`` using C not Python semantics (:issue:`36047`, :issue:`36526`)
3738
- Fixed regression in :meth:`read_excel` with ``engine="odf"`` caused ``UnboundLocalError`` in some cases where cells had nested child nodes (:issue:`36122`, :issue:`35802`)
3839
- Fixed regression in :meth:`DataFrame.replace` inconsistent replace when using a float in the replace method (:issue:`35376`)
3940
- Fixed regression in :class:`DataFrame` and :class:`Series` comparisons between numeric arrays and strings (:issue:`35700`, :issue:`36377`)

pandas/core/computation/expressions.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,10 @@ def _evaluate_numexpr(op, op_str, a, b):
132132
roperator.rtruediv: "/",
133133
operator.floordiv: "//",
134134
roperator.rfloordiv: "//",
135-
operator.mod: "%",
135+
# we require Python semantics for mod of negative for backwards compatibility
136+
# see https://github.com/pydata/numexpr/issues/365
137+
# so sticking with unaccelerated for now
138+
operator.mod: None,
136139
roperator.rmod: "%",
137140
operator.pow: "**",
138141
roperator.rpow: "**",

pandas/core/ops/methods.py

-2
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,6 @@ def _create_methods(cls, arith_method, comp_method, bool_method, special):
171171
mul=arith_method(cls, operator.mul, special),
172172
truediv=arith_method(cls, operator.truediv, special),
173173
floordiv=arith_method(cls, operator.floordiv, special),
174-
# Causes a floating point exception in the tests when numexpr enabled,
175-
# so for now no speedup
176174
mod=arith_method(cls, operator.mod, special),
177175
pow=arith_method(cls, operator.pow, special),
178176
# not entirely sure why this is necessary, but previously was included

pandas/tests/test_expressions.py

+39-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import pytest
77

88
import pandas._testing as tm
9-
from pandas.core.api import DataFrame
9+
from pandas.core.api import DataFrame, Index, Series
1010
from pandas.core.computation import expressions as expr
1111

1212
_frame = DataFrame(randn(10000, 4), columns=list("ABCD"), dtype="float64")
@@ -380,3 +380,41 @@ def test_frame_series_axis(self, axis, arith):
380380

381381
result = op_func(other, axis=axis)
382382
tm.assert_frame_equal(expected, result)
383+
384+
@pytest.mark.parametrize(
385+
"op",
386+
[
387+
"__mod__",
388+
pytest.param("__rmod__", marks=pytest.mark.xfail(reason="GH-36552")),
389+
"__floordiv__",
390+
"__rfloordiv__",
391+
],
392+
)
393+
@pytest.mark.parametrize("box", [DataFrame, Series, Index])
394+
@pytest.mark.parametrize("scalar", [-5, 5])
395+
def test_python_semantics_with_numexpr_installed(self, op, box, scalar):
396+
# https://github.com/pandas-dev/pandas/issues/36047
397+
expr._MIN_ELEMENTS = 0
398+
data = np.arange(-50, 50)
399+
obj = box(data)
400+
method = getattr(obj, op)
401+
result = method(scalar)
402+
403+
# compare result with numpy
404+
expr.set_use_numexpr(False)
405+
expected = method(scalar)
406+
expr.set_use_numexpr(True)
407+
tm.assert_equal(result, expected)
408+
409+
# compare result element-wise with Python
410+
for i, elem in enumerate(data):
411+
if box == DataFrame:
412+
scalar_result = result.iloc[i, 0]
413+
else:
414+
scalar_result = result[i]
415+
try:
416+
expected = getattr(int(elem), op)(scalar)
417+
except ZeroDivisionError:
418+
pass
419+
else:
420+
assert scalar_result == expected

0 commit comments

Comments
 (0)