Skip to content

Commit e480752

Browse files
WillAydLaurent Mutricymroeschke
committed
remove ops div class to solve pandas-dev#21374 (pandas-dev#59144)
* remove core.computation.ops.Div resolves pandas-dev#21374 pandas-dev#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 acb9e97 commit e480752

File tree

6 files changed

+66
-64
lines changed

6 files changed

+66
-64
lines changed

pandas/_testing/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@
111111

112112
COMPLEX_DTYPES: list[Dtype] = [complex, "complex64", "complex128"]
113113
STRING_DTYPES: list[Dtype] = [str, "str", "U"]
114+
COMPLEX_FLOAT_DTYPES: list[Dtype] = [*COMPLEX_DTYPES, *FLOAT_NUMPY_DTYPES]
114115

115116
DATETIME64_DTYPES: list[Dtype] = ["datetime64[ns]", "M8[ns]"]
116117
TIMEDELTA64_DTYPES: list[Dtype] = ["timedelta64[ns]", "m8[ns]"]

pandas/conftest.py

+15
Original file line numberDiff line numberDiff line change
@@ -1403,6 +1403,21 @@ def complex_dtype(request):
14031403
return request.param
14041404

14051405

1406+
@pytest.fixture(params=tm.COMPLEX_FLOAT_DTYPES)
1407+
def complex_or_float_dtype(request):
1408+
"""
1409+
Parameterized fixture for complex and numpy float dtypes.
1410+
1411+
* complex
1412+
* 'complex64'
1413+
* 'complex128'
1414+
* float
1415+
* 'float32'
1416+
* 'float64'
1417+
"""
1418+
return request.param
1419+
1420+
14061421
@pytest.fixture(params=tm.SIGNED_INT_NUMPY_DTYPES)
14071422
def any_signed_int_numpy_dtype(request):
14081423
"""

pandas/core/computation/expr.py

+1-5
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
UNARY_OPS_SYMS,
3232
BinOp,
3333
Constant,
34-
Div,
3534
FuncNode,
3635
Op,
3736
Term,
@@ -370,7 +369,7 @@ class BaseExprVisitor(ast.NodeVisitor):
370369
"Add",
371370
"Sub",
372371
"Mult",
373-
None,
372+
"Div",
374373
"Pow",
375374
"FloorDiv",
376375
"Mod",
@@ -533,9 +532,6 @@ def visit_BinOp(self, node, **kwargs):
533532
left, right = self._maybe_downcast_constants(left, right)
534533
return self._maybe_evaluate_binop(op, op_class, left, right)
535534

536-
def visit_Div(self, node, **kwargs):
537-
return lambda lhs, rhs: Div(lhs, rhs)
538-
539535
def visit_UnaryOp(self, node, **kwargs):
540536
op = self.visit(node.op)
541537
operand = self.visit(node.operand)

pandas/core/computation/ops.py

-53
Original file line numberDiff line numberDiff line change
@@ -332,31 +332,6 @@ def _not_in(x, y):
332332
_binary_ops_dict.update(d)
333333

334334

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

@@ -513,34 +488,6 @@ def _disallow_scalar_only_bool_ops(self):
513488
raise NotImplementedError("cannot evaluate scalar only bool ops")
514489

515490

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

pandas/tests/computation/test_eval.py

+16-6
Original file line numberDiff line numberDiff line change
@@ -747,16 +747,26 @@ class TestTypeCasting:
747747
@pytest.mark.parametrize("op", ["+", "-", "*", "**", "/"])
748748
# maybe someday... numexpr has too many upcasting rules now
749749
# chain(*(np.core.sctypes[x] for x in ['uint', 'int', 'float']))
750-
@pytest.mark.parametrize("dt", [np.float32, np.float64])
751750
@pytest.mark.parametrize("left_right", [("df", "3"), ("3", "df")])
752-
def test_binop_typecasting(self, engine, parser, op, dt, left_right):
753-
df = DataFrame(np.random.default_rng(2).standard_normal((5, 3)), dtype=dt)
751+
def test_binop_typecasting(
752+
self, engine, parser, op, complex_or_float_dtype, left_right, request
753+
):
754+
# GH#21374
755+
dtype = complex_or_float_dtype
756+
df = DataFrame(np.random.default_rng(2).standard_normal((5, 3)), dtype=dtype)
754757
left, right = left_right
755758
s = f"{left} {op} {right}"
756759
res = pd.eval(s, engine=engine, parser=parser)
757-
assert df.values.dtype == dt
758-
assert res.values.dtype == dt
759-
tm.assert_frame_equal(res, eval(s))
760+
if dtype == "complex64" and engine == "numexpr":
761+
mark = pytest.mark.xfail(
762+
reason="numexpr issue with complex that are upcast "
763+
"to complex 128 "
764+
"https://github.com/pydata/numexpr/issues/492"
765+
)
766+
request.applymarker(mark)
767+
assert df.values.dtype == dtype
768+
assert res.values.dtype == dtype
769+
tm.assert_frame_equal(res, eval(s), check_exact=False)
760770

761771

762772
# -------------------------------------

pandas/tests/frame/test_query_eval.py

+33
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,39 @@ def test_eval_object_dtype_binop(self):
188188
expected = DataFrame({"a1": ["Y", "N"], "c": [True, False]})
189189
tm.assert_frame_equal(res, expected)
190190

191+
def test_using_numpy(self, engine, parser):
192+
# GH 58041
193+
skip_if_no_pandas_parser(parser)
194+
df = Series([0.2, 1.5, 2.8], name="a").to_frame()
195+
res = df.eval("@np.floor(a)", engine=engine, parser=parser)
196+
expected = np.floor(df["a"])
197+
tm.assert_series_equal(expected, res)
198+
199+
def test_eval_simple(self, engine, parser):
200+
df = Series([0.2, 1.5, 2.8], name="a").to_frame()
201+
res = df.eval("a", engine=engine, parser=parser)
202+
expected = df["a"]
203+
tm.assert_series_equal(expected, res)
204+
205+
def test_extension_array_eval(self, engine, parser, request):
206+
# 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)
212+
df = DataFrame({"a": pd.array([1, 2, 3]), "b": pd.array([4, 5, 6])})
213+
result = df.eval("a / b", engine=engine, parser=parser)
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])
222+
tm.assert_series_equal(result, expected)
223+
191224

192225
class TestDataFrameQueryWithMultiIndex:
193226
def test_query_with_named_multiindex(self, parser, engine):

0 commit comments

Comments
 (0)