diff --git a/pandas/core/ops/__init__.py b/pandas/core/ops/__init__.py index 1ee4c8e85be6b..d0adf2da04db3 100644 --- a/pandas/core/ops/__init__.py +++ b/pandas/core/ops/__init__.py @@ -510,6 +510,7 @@ def _comp_method_SERIES(cls, op, special): Wrapper function for Series arithmetic operations, to avoid code duplication. """ + str_rep = _get_opstr(op) op_name = _get_op_name(op, special) @unpack_zerodim_and_defer(op_name) @@ -523,7 +524,7 @@ def wrapper(self, other): lvalues = extract_array(self, extract_numpy=True) rvalues = extract_array(other, extract_numpy=True) - res_values = comparison_op(lvalues, rvalues, op) + res_values = comparison_op(lvalues, rvalues, op, str_rep) return _construct_result(self, res_values, index=self.index, name=res_name) diff --git a/pandas/core/ops/array_ops.py b/pandas/core/ops/array_ops.py index 10e3f32de3958..b216a927f65b3 100644 --- a/pandas/core/ops/array_ops.py +++ b/pandas/core/ops/array_ops.py @@ -126,7 +126,7 @@ def na_op(x, y): return na_op -def na_arithmetic_op(left, right, op, str_rep: str): +def na_arithmetic_op(left, right, op, str_rep: Optional[str], is_cmp: bool = False): """ Return the result of evaluating op on the passed in values. @@ -137,6 +137,8 @@ def na_arithmetic_op(left, right, op, str_rep: str): left : np.ndarray right : np.ndarray or scalar str_rep : str or None + is_cmp : bool, default False + If this a comparison operation. Returns ------- @@ -151,8 +153,18 @@ def na_arithmetic_op(left, right, op, str_rep: str): try: result = expressions.evaluate(op, str_rep, left, right) except TypeError: + if is_cmp: + # numexpr failed on comparison op, e.g. ndarray[float] > datetime + # In this case we do not fall back to the masked op, as that + # will handle complex numbers incorrectly, see GH#32047 + raise result = masked_arith_op(left, right, op) + if is_cmp and (is_scalar(result) or result is NotImplemented): + # numpy returned a scalar instead of operating element-wise + # e.g. numeric array vs str + return invalid_comparison(left, right, op) + return missing.dispatch_fill_zeros(op, left, right, result) @@ -199,7 +211,9 @@ def arithmetic_op(left: ArrayLike, right: Any, op, str_rep: str): return res_values -def comparison_op(left: ArrayLike, right: Any, op) -> ArrayLike: +def comparison_op( + left: ArrayLike, right: Any, op, str_rep: Optional[str] = None, +) -> ArrayLike: """ Evaluate a comparison operation `=`, `!=`, `>=`, `>`, `<=`, or `<`. @@ -244,16 +258,8 @@ def comparison_op(left: ArrayLike, right: Any, op) -> ArrayLike: res_values = comp_method_OBJECT_ARRAY(op, lvalues, rvalues) else: - op_name = f"__{op.__name__}__" - method = getattr(lvalues, op_name) with np.errstate(all="ignore"): - res_values = method(rvalues) - - if res_values is NotImplemented: - res_values = invalid_comparison(lvalues, rvalues, op) - if is_scalar(res_values): - typ = type(rvalues) - raise TypeError(f"Could not compare {typ} type with Series") + res_values = na_arithmetic_op(lvalues, rvalues, op, str_rep, is_cmp=True) return res_values @@ -380,7 +386,7 @@ def get_array_op(op, str_rep: Optional[str] = None): """ op_name = op.__name__.strip("_") if op_name in {"eq", "ne", "lt", "le", "gt", "ge"}: - return partial(comparison_op, op=op) + return partial(comparison_op, op=op, str_rep=str_rep) elif op_name in {"and", "or", "xor", "rand", "ror", "rxor"}: return partial(logical_op, op=op) else: diff --git a/pandas/tests/arithmetic/test_numeric.py b/pandas/tests/arithmetic/test_numeric.py index 51d09a92773b1..d4baf2f374cdf 100644 --- a/pandas/tests/arithmetic/test_numeric.py +++ b/pandas/tests/arithmetic/test_numeric.py @@ -66,8 +66,7 @@ def test_df_numeric_cmp_dt64_raises(self): ts = pd.Timestamp.now() df = pd.DataFrame({"x": range(5)}) - msg = "Invalid comparison between dtype=int64 and Timestamp" - + msg = "'[<>]' not supported between instances of 'Timestamp' and 'int'" with pytest.raises(TypeError, match=msg): df > ts with pytest.raises(TypeError, match=msg):