Skip to content

Commit 3782dd1

Browse files
mutricylLaurent Mutricymroeschke
authored
remove ops div class to solve #21374 (#59144)
* remove core.computation.ops.Div resolves #21374 #58748 * need to preserve order * updating tests * update whatsnew * solve mypy issue * fixing pytests * better than cast * adding specific test * Update pandas/tests/frame/test_query_eval.py Co-authored-by: Matthew Roeschke <[email protected]> * Update pandas/tests/computation/test_eval.py Co-authored-by: Matthew Roeschke <[email protected]> --------- Co-authored-by: Laurent Mutricy <[email protected]> Co-authored-by: Matthew Roeschke <[email protected]>
1 parent 8bca186 commit 3782dd1

File tree

7 files changed

+48
-66
lines changed

7 files changed

+48
-66
lines changed

doc/source/whatsnew/v3.0.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,7 @@ Other
616616
^^^^^
617617
- Bug in :class:`DataFrame` when passing a ``dict`` with a NA scalar and ``columns`` that would always return ``np.nan`` (:issue:`57205`)
618618
- Bug in :func:`eval` on :class:`ExtensionArray` on including division ``/`` failed with a ``TypeError``. (:issue:`58748`)
619+
- Bug in :func:`eval` on :class:`complex` including division ``/`` discards imaginary part. (:issue:`21374`)
619620
- Bug in :func:`eval` where the names of the :class:`Series` were not preserved when using ``engine="numexpr"``. (:issue:`10239`)
620621
- Bug in :func:`unique` on :class:`Index` not always returning :class:`Index` (:issue:`57043`)
621622
- Bug in :meth:`DataFrame.apply` where passing ``engine="numba"`` ignored ``args`` passed to the applied function (:issue:`58712`)

pandas/_testing/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@
107107

108108
COMPLEX_DTYPES: list[Dtype] = [complex, "complex64", "complex128"]
109109
STRING_DTYPES: list[Dtype] = [str, "str", "U"]
110+
COMPLEX_FLOAT_DTYPES: list[Dtype] = [*COMPLEX_DTYPES, *FLOAT_NUMPY_DTYPES]
110111

111112
DATETIME64_DTYPES: list[Dtype] = ["datetime64[ns]", "M8[ns]"]
112113
TIMEDELTA64_DTYPES: list[Dtype] = ["timedelta64[ns]", "m8[ns]"]

pandas/conftest.py

+15
Original file line numberDiff line numberDiff line change
@@ -1448,6 +1448,21 @@ def complex_dtype(request):
14481448
return request.param
14491449

14501450

1451+
@pytest.fixture(params=tm.COMPLEX_FLOAT_DTYPES)
1452+
def complex_or_float_dtype(request):
1453+
"""
1454+
Parameterized fixture for complex and numpy float dtypes.
1455+
1456+
* complex
1457+
* 'complex64'
1458+
* 'complex128'
1459+
* float
1460+
* 'float32'
1461+
* 'float64'
1462+
"""
1463+
return request.param
1464+
1465+
14511466
@pytest.fixture(params=tm.SIGNED_INT_NUMPY_DTYPES)
14521467
def any_signed_int_numpy_dtype(request):
14531468
"""

pandas/core/computation/expr.py

+1-5
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
UNARY_OPS_SYMS,
3333
BinOp,
3434
Constant,
35-
Div,
3635
FuncNode,
3736
Op,
3837
Term,
@@ -374,7 +373,7 @@ class BaseExprVisitor(ast.NodeVisitor):
374373
"Add",
375374
"Sub",
376375
"Mult",
377-
None,
376+
"Div",
378377
"Pow",
379378
"FloorDiv",
380379
"Mod",
@@ -537,9 +536,6 @@ def visit_BinOp(self, node, **kwargs):
537536
left, right = self._maybe_downcast_constants(left, right)
538537
return self._maybe_evaluate_binop(op, op_class, left, right)
539538

540-
def visit_Div(self, node, **kwargs):
541-
return lambda lhs, rhs: Div(lhs, rhs)
542-
543539
def visit_UnaryOp(self, node, **kwargs):
544540
op = self.visit(node.op)
545541
operand = self.visit(node.operand)

pandas/core/computation/ops.py

-52
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
from pandas.core.dtypes.common import (
2020
is_list_like,
21-
is_numeric_dtype,
2221
is_scalar,
2322
)
2423

@@ -328,31 +327,6 @@ def _not_in(x, y):
328327
_binary_ops_dict.update(d)
329328

330329

331-
def _cast_inplace(terms, acceptable_dtypes, dtype) -> None:
332-
"""
333-
Cast an expression inplace.
334-
335-
Parameters
336-
----------
337-
terms : Op
338-
The expression that should cast.
339-
acceptable_dtypes : list of acceptable numpy.dtype
340-
Will not cast if term's dtype in this list.
341-
dtype : str or numpy.dtype
342-
The dtype to cast to.
343-
"""
344-
dt = np.dtype(dtype)
345-
for term in terms:
346-
if term.type in acceptable_dtypes:
347-
continue
348-
349-
try:
350-
new_value = term.value.astype(dt)
351-
except AttributeError:
352-
new_value = dt.type(term.value)
353-
term.update(new_value)
354-
355-
356330
def is_term(obj) -> bool:
357331
return isinstance(obj, Term)
358332

@@ -509,32 +483,6 @@ def _disallow_scalar_only_bool_ops(self) -> None:
509483
raise NotImplementedError("cannot evaluate scalar only bool ops")
510484

511485

512-
class Div(BinOp):
513-
"""
514-
Div operator to special case casting.
515-
516-
Parameters
517-
----------
518-
lhs, rhs : Term or Op
519-
The Terms or Ops in the ``/`` expression.
520-
"""
521-
522-
def __init__(self, lhs, rhs) -> None:
523-
super().__init__("/", lhs, rhs)
524-
525-
if not is_numeric_dtype(lhs.return_type) or not is_numeric_dtype(
526-
rhs.return_type
527-
):
528-
raise TypeError(
529-
f"unsupported operand type(s) for {self.op}: "
530-
f"'{lhs.return_type}' and '{rhs.return_type}'"
531-
)
532-
533-
# do not upcast float32s to float64 un-necessarily
534-
acceptable_dtypes = [np.float32, np.float64]
535-
_cast_inplace(com.flatten(self), acceptable_dtypes, np.float64)
536-
537-
538486
UNARY_OPS_SYMS = ("+", "-", "~", "not")
539487
_unary_ops_funcs = (operator.pos, operator.neg, operator.invert, operator.invert)
540488
_unary_ops_dict = dict(zip(UNARY_OPS_SYMS, _unary_ops_funcs))

pandas/tests/computation/test_eval.py

+16-7
Original file line numberDiff line numberDiff line change
@@ -758,16 +758,25 @@ class TestTypeCasting:
758758
# maybe someday... numexpr has too many upcasting rules now
759759
# chain(*(np.core.sctypes[x] for x in ['uint', 'int', 'float']))
760760
@pytest.mark.parametrize("left_right", [("df", "3"), ("3", "df")])
761-
def test_binop_typecasting(self, engine, parser, op, float_numpy_dtype, left_right):
762-
df = DataFrame(
763-
np.random.default_rng(2).standard_normal((5, 3)), dtype=float_numpy_dtype
764-
)
761+
def test_binop_typecasting(
762+
self, engine, parser, op, complex_or_float_dtype, left_right, request
763+
):
764+
# GH#21374
765+
dtype = complex_or_float_dtype
766+
df = DataFrame(np.random.default_rng(2).standard_normal((5, 3)), dtype=dtype)
765767
left, right = left_right
766768
s = f"{left} {op} {right}"
767769
res = pd.eval(s, engine=engine, parser=parser)
768-
assert df.values.dtype == float_numpy_dtype
769-
assert res.values.dtype == float_numpy_dtype
770-
tm.assert_frame_equal(res, eval(s))
770+
if dtype == "complex64" and engine == "numexpr":
771+
mark = pytest.mark.xfail(
772+
reason="numexpr issue with complex that are upcast "
773+
"to complex 128 "
774+
"https://github.com/pydata/numexpr/issues/492"
775+
)
776+
request.applymarker(mark)
777+
assert df.values.dtype == dtype
778+
assert res.values.dtype == dtype
779+
tm.assert_frame_equal(res, eval(s), check_exact=False)
771780

772781

773782
# -------------------------------------

pandas/tests/frame/test_query_eval.py

+14-2
Original file line numberDiff line numberDiff line change
@@ -202,11 +202,23 @@ def test_eval_simple(self, engine, parser):
202202
expected = df["a"]
203203
tm.assert_series_equal(expected, res)
204204

205-
def test_extension_array_eval(self, engine, parser):
205+
def test_extension_array_eval(self, engine, parser, request):
206206
# GH#58748
207+
if engine == "numexpr":
208+
mark = pytest.mark.xfail(
209+
reason="numexpr does not support extension array dtypes"
210+
)
211+
request.applymarker(mark)
207212
df = DataFrame({"a": pd.array([1, 2, 3]), "b": pd.array([4, 5, 6])})
208213
result = df.eval("a / b", engine=engine, parser=parser)
209-
expected = Series([0.25, 0.40, 0.50])
214+
expected = Series(pd.array([0.25, 0.40, 0.50]))
215+
tm.assert_series_equal(result, expected)
216+
217+
def test_complex_eval(self, engine, parser):
218+
# GH#21374
219+
df = DataFrame({"a": [1 + 2j], "b": [1 + 1j]})
220+
result = df.eval("a/b", engine=engine, parser=parser)
221+
expected = Series([1.5 + 0.5j])
210222
tm.assert_series_equal(result, expected)
211223

212224

0 commit comments

Comments
 (0)