1
0
forked from pool/python-pandas
python-pandas/pandas-pr59353-np2eval.patch

175 lines
5.9 KiB
Diff
Raw Permalink Normal View History

diff --git a/pandas/_testing/__init__.py b/pandas/_testing/__init__.py
index 361998db8e..87d419e2db 100644
--- a/pandas/_testing/__init__.py
+++ b/pandas/_testing/__init__.py
@@ -111,6 +111,7 @@ ALL_FLOAT_DTYPES: list[Dtype] = [*FLOAT_NUMPY_DTYPES, *FLOAT_EA_DTYPES]
COMPLEX_DTYPES: list[Dtype] = [complex, "complex64", "complex128"]
STRING_DTYPES: list[Dtype] = [str, "str", "U"]
+COMPLEX_FLOAT_DTYPES: list[Dtype] = [*COMPLEX_DTYPES, *FLOAT_NUMPY_DTYPES]
DATETIME64_DTYPES: list[Dtype] = ["datetime64[ns]", "M8[ns]"]
TIMEDELTA64_DTYPES: list[Dtype] = ["timedelta64[ns]", "m8[ns]"]
diff --git a/pandas/conftest.py b/pandas/conftest.py
index 7c35dfdde9..10134c90f8 100644
--- a/pandas/conftest.py
+++ b/pandas/conftest.py
@@ -1403,6 +1403,21 @@ def complex_dtype(request):
return request.param
+@pytest.fixture(params=tm.COMPLEX_FLOAT_DTYPES)
+def complex_or_float_dtype(request):
+ """
+ Parameterized fixture for complex and numpy float dtypes.
+
+ * complex
+ * 'complex64'
+ * 'complex128'
+ * float
+ * 'float32'
+ * 'float64'
+ """
+ return request.param
+
+
@pytest.fixture(params=tm.SIGNED_INT_NUMPY_DTYPES)
def any_signed_int_numpy_dtype(request):
"""
diff --git a/pandas/core/computation/expr.py b/pandas/core/computation/expr.py
index b5861fbaeb..d642c37cea 100644
--- a/pandas/core/computation/expr.py
+++ b/pandas/core/computation/expr.py
@@ -31,7 +31,6 @@ from pandas.core.computation.ops import (
UNARY_OPS_SYMS,
BinOp,
Constant,
- Div,
FuncNode,
Op,
Term,
@@ -370,7 +369,7 @@ class BaseExprVisitor(ast.NodeVisitor):
"Add",
"Sub",
"Mult",
- None,
+ "Div",
"Pow",
"FloorDiv",
"Mod",
@@ -533,9 +532,6 @@ class BaseExprVisitor(ast.NodeVisitor):
left, right = self._maybe_downcast_constants(left, right)
return self._maybe_evaluate_binop(op, op_class, left, right)
- def visit_Div(self, node, **kwargs):
- return lambda lhs, rhs: Div(lhs, rhs)
-
def visit_UnaryOp(self, node, **kwargs):
op = self.visit(node.op)
operand = self.visit(node.operand)
diff --git a/pandas/core/computation/ops.py b/pandas/core/computation/ops.py
index 95ac20ba39..ea8b1c0457 100644
--- a/pandas/core/computation/ops.py
+++ b/pandas/core/computation/ops.py
@@ -332,31 +332,6 @@ for d in (_cmp_ops_dict, _bool_ops_dict, _arith_ops_dict):
_binary_ops_dict.update(d)
-def _cast_inplace(terms, acceptable_dtypes, dtype) -> None:
- """
- Cast an expression inplace.
-
- Parameters
- ----------
- terms : Op
- The expression that should cast.
- acceptable_dtypes : list of acceptable numpy.dtype
- Will not cast if term's dtype in this list.
- dtype : str or numpy.dtype
- The dtype to cast to.
- """
- dt = np.dtype(dtype)
- for term in terms:
- if term.type in acceptable_dtypes:
- continue
-
- try:
- new_value = term.value.astype(dt)
- except AttributeError:
- new_value = dt.type(term.value)
- term.update(new_value)
-
-
def is_term(obj) -> bool:
return isinstance(obj, Term)
@@ -516,31 +491,6 @@ class BinOp(Op):
def isnumeric(dtype) -> bool:
return issubclass(np.dtype(dtype).type, np.number)
-
-class Div(BinOp):
- """
- Div operator to special case casting.
-
- Parameters
- ----------
- lhs, rhs : Term or Op
- The Terms or Ops in the ``/`` expression.
- """
-
- def __init__(self, lhs, rhs) -> None:
- super().__init__("/", lhs, rhs)
-
- if not isnumeric(lhs.return_type) or not isnumeric(rhs.return_type):
- raise TypeError(
- f"unsupported operand type(s) for {self.op}: "
- f"'{lhs.return_type}' and '{rhs.return_type}'"
- )
-
- # do not upcast float32s to float64 un-necessarily
- acceptable_dtypes = [np.float32, np.float64]
- _cast_inplace(com.flatten(self), acceptable_dtypes, np.float64)
-
-
UNARY_OPS_SYMS = ("+", "-", "~", "not")
_unary_ops_funcs = (operator.pos, operator.neg, operator.invert, operator.invert)
_unary_ops_dict = dict(zip(UNARY_OPS_SYMS, _unary_ops_funcs))
diff --git a/pandas/tests/computation/test_eval.py b/pandas/tests/computation/test_eval.py
index 17630f14b0..e8fad6b8cb 100644
--- a/pandas/tests/computation/test_eval.py
+++ b/pandas/tests/computation/test_eval.py
@@ -747,16 +747,26 @@ class TestTypeCasting:
@pytest.mark.parametrize("op", ["+", "-", "*", "**", "/"])
# maybe someday... numexpr has too many upcasting rules now
# chain(*(np.core.sctypes[x] for x in ['uint', 'int', 'float']))
- @pytest.mark.parametrize("dt", [np.float32, np.float64])
@pytest.mark.parametrize("left_right", [("df", "3"), ("3", "df")])
- def test_binop_typecasting(self, engine, parser, op, dt, left_right):
- df = DataFrame(np.random.default_rng(2).standard_normal((5, 3)), dtype=dt)
+ def test_binop_typecasting(
+ self, engine, parser, op, complex_or_float_dtype, left_right, request
+ ):
+ # GH#21374
+ dtype = complex_or_float_dtype
+ df = DataFrame(np.random.default_rng(2).standard_normal((5, 3)), dtype=dtype)
left, right = left_right
s = f"{left} {op} {right}"
res = pd.eval(s, engine=engine, parser=parser)
- assert df.values.dtype == dt
- assert res.values.dtype == dt
- tm.assert_frame_equal(res, eval(s))
+ if dtype == "complex64" and engine == "numexpr":
+ mark = pytest.mark.xfail(
+ reason="numexpr issue with complex that are upcast "
+ "to complex 128 "
+ "https://github.com/pydata/numexpr/issues/492"
+ )
+ request.applymarker(mark)
+ assert df.values.dtype == dtype
+ assert res.values.dtype == dtype
+ tm.assert_frame_equal(res, eval(s), check_exact=False)
# -------------------------------------