Skip to content

Commit 2391aa4

Browse files
authored
REF: get str_rep in numexpr code (#34325)
1 parent 29d8ce5 commit 2391aa4

File tree

4 files changed

+72
-103
lines changed

4 files changed

+72
-103
lines changed

pandas/core/computation/expressions.py

+40-7
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
Offer fast expression evaluation through numexpr
66
77
"""
8-
8+
import operator
99
import warnings
1010

1111
import numpy as np
@@ -15,6 +15,7 @@
1515
from pandas.core.dtypes.generic import ABCDataFrame
1616

1717
from pandas.core.computation.check import _NUMEXPR_INSTALLED
18+
from pandas.core.ops import roperator
1819

1920
if _NUMEXPR_INSTALLED:
2021
import numexpr as ne
@@ -120,6 +121,38 @@ def _evaluate_numexpr(op, op_str, a, b):
120121
return result
121122

122123

124+
_op_str_mapping = {
125+
operator.add: "+",
126+
roperator.radd: "+",
127+
operator.mul: "*",
128+
roperator.rmul: "*",
129+
operator.sub: "-",
130+
roperator.rsub: "-",
131+
operator.truediv: "/",
132+
roperator.rtruediv: "/",
133+
operator.floordiv: "//",
134+
roperator.rfloordiv: "//",
135+
operator.mod: "%",
136+
roperator.rmod: "%",
137+
operator.pow: "**",
138+
roperator.rpow: "**",
139+
operator.eq: "==",
140+
operator.ne: "!=",
141+
operator.le: "<=",
142+
operator.lt: "<",
143+
operator.ge: ">=",
144+
operator.gt: ">",
145+
operator.and_: "&",
146+
roperator.rand_: "&",
147+
operator.or_: "|",
148+
roperator.ror_: "|",
149+
operator.xor: "^",
150+
roperator.rxor: "^",
151+
divmod: None,
152+
roperator.rdivmod: None,
153+
}
154+
155+
123156
def _where_standard(cond, a, b):
124157
# Caller is responsible for extracting ndarray if necessary
125158
return np.where(cond, a, b)
@@ -178,23 +211,23 @@ def _bool_arith_check(
178211
return True
179212

180213

181-
def evaluate(op, op_str, a, b, use_numexpr=True):
214+
def evaluate(op, a, b, use_numexpr: bool = True):
182215
"""
183216
Evaluate and return the expression of the op on a and b.
184217
185218
Parameters
186219
----------
187220
op : the actual operand
188-
op_str : str
189-
The string version of the op.
190221
a : left operand
191222
b : right operand
192223
use_numexpr : bool, default True
193224
Whether to try to use numexpr.
194225
"""
195-
use_numexpr = use_numexpr and _bool_arith_check(op_str, a, b)
196-
if use_numexpr:
197-
return _evaluate(op, op_str, a, b)
226+
op_str = _op_str_mapping.get(op, None)
227+
if op_str is not None:
228+
use_numexpr = use_numexpr and _bool_arith_check(op_str, a, b)
229+
if use_numexpr:
230+
return _evaluate(op, op_str, a, b) # type: ignore
198231
return _evaluate_standard(op, op_str, a, b)
199232

200233

pandas/core/ops/__init__.py

+16-72
Original file line numberDiff line numberDiff line change
@@ -179,51 +179,6 @@ def _get_frame_op_default_axis(name):
179179
return "columns"
180180

181181

182-
def _get_opstr(op):
183-
"""
184-
Find the operation string, if any, to pass to numexpr for this
185-
operation.
186-
187-
Parameters
188-
----------
189-
op : binary operator
190-
191-
Returns
192-
-------
193-
op_str : string or None
194-
"""
195-
return {
196-
operator.add: "+",
197-
radd: "+",
198-
operator.mul: "*",
199-
rmul: "*",
200-
operator.sub: "-",
201-
rsub: "-",
202-
operator.truediv: "/",
203-
rtruediv: "/",
204-
operator.floordiv: "//",
205-
rfloordiv: "//",
206-
operator.mod: "%",
207-
rmod: "%",
208-
operator.pow: "**",
209-
rpow: "**",
210-
operator.eq: "==",
211-
operator.ne: "!=",
212-
operator.le: "<=",
213-
operator.lt: "<",
214-
operator.ge: ">=",
215-
operator.gt: ">",
216-
operator.and_: "&",
217-
rand_: "&",
218-
operator.or_: "|",
219-
ror_: "|",
220-
operator.xor: "^",
221-
rxor: "^",
222-
divmod: None,
223-
rdivmod: None,
224-
}[op]
225-
226-
227182
def _get_op_name(op, special: bool) -> str:
228183
"""
229184
Find the name to attach to this method according to conventions
@@ -293,7 +248,7 @@ def fill_binop(left, right, fill_value):
293248
# Dispatch logic
294249

295250

296-
def dispatch_to_series(left, right, func, str_rep=None, axis=None):
251+
def dispatch_to_series(left, right, func, axis=None):
297252
"""
298253
Evaluate the frame operation func(left, right) by evaluating
299254
column-by-column, dispatching to the Series implementation.
@@ -303,7 +258,6 @@ def dispatch_to_series(left, right, func, str_rep=None, axis=None):
303258
left : DataFrame
304259
right : scalar or DataFrame
305260
func : arithmetic or comparison operator
306-
str_rep : str or None, default None
307261
axis : {None, 0, 1, "index", "columns"}
308262
309263
Returns
@@ -318,14 +272,14 @@ def dispatch_to_series(left, right, func, str_rep=None, axis=None):
318272
if lib.is_scalar(right) or np.ndim(right) == 0:
319273

320274
# Get the appropriate array-op to apply to each block's values.
321-
array_op = get_array_op(func, str_rep=str_rep)
275+
array_op = get_array_op(func)
322276
bm = left._mgr.apply(array_op, right=right)
323277
return type(left)(bm)
324278

325279
elif isinstance(right, ABCDataFrame):
326280
assert right._indexed_same(left)
327281

328-
array_op = get_array_op(func, str_rep=str_rep)
282+
array_op = get_array_op(func)
329283
bm = left._mgr.operate_blockwise(right._mgr, array_op)
330284
return type(left)(bm)
331285

@@ -358,7 +312,7 @@ def column_op(a, b):
358312
# Remaining cases have less-obvious dispatch rules
359313
raise NotImplementedError(right)
360314

361-
new_data = expressions.evaluate(column_op, str_rep, left, right)
315+
new_data = expressions.evaluate(column_op, left, right)
362316
return new_data
363317

364318

@@ -391,7 +345,6 @@ def _arith_method_SERIES(cls, op, special):
391345
Wrapper function for Series arithmetic operations, to avoid
392346
code duplication.
393347
"""
394-
str_rep = _get_opstr(op)
395348
op_name = _get_op_name(op, special)
396349

397350
@unpack_zerodim_and_defer(op_name)
@@ -402,7 +355,7 @@ def wrapper(left, right):
402355

403356
lvalues = extract_array(left, extract_numpy=True)
404357
rvalues = extract_array(right, extract_numpy=True)
405-
result = arithmetic_op(lvalues, rvalues, op, str_rep)
358+
result = arithmetic_op(lvalues, rvalues, op)
406359

407360
return left._construct_result(result, name=res_name)
408361

@@ -415,7 +368,6 @@ def _comp_method_SERIES(cls, op, special):
415368
Wrapper function for Series arithmetic operations, to avoid
416369
code duplication.
417370
"""
418-
str_rep = _get_opstr(op)
419371
op_name = _get_op_name(op, special)
420372

421373
@unpack_zerodim_and_defer(op_name)
@@ -429,7 +381,7 @@ def wrapper(self, other):
429381
lvalues = extract_array(self, extract_numpy=True)
430382
rvalues = extract_array(other, extract_numpy=True)
431383

432-
res_values = comparison_op(lvalues, rvalues, op, str_rep)
384+
res_values = comparison_op(lvalues, rvalues, op)
433385

434386
return self._construct_result(res_values, name=res_name)
435387

@@ -490,7 +442,7 @@ def flex_wrapper(self, other, level=None, fill_value=None, axis=0):
490442
# DataFrame
491443

492444

493-
def _combine_series_frame(left, right, func, axis: int, str_rep: str):
445+
def _combine_series_frame(left, right, func, axis: int):
494446
"""
495447
Apply binary operator `func` to self, other using alignment and fill
496448
conventions determined by the axis argument.
@@ -501,7 +453,6 @@ def _combine_series_frame(left, right, func, axis: int, str_rep: str):
501453
right : Series
502454
func : binary operator
503455
axis : {0, 1}
504-
str_rep : str
505456
506457
Returns
507458
-------
@@ -520,7 +471,7 @@ def _combine_series_frame(left, right, func, axis: int, str_rep: str):
520471

521472
rvalues = np.broadcast_to(rvalues, left.shape)
522473

523-
array_op = get_array_op(func, str_rep=str_rep)
474+
array_op = get_array_op(func)
524475
bm = left._mgr.apply(array_op, right=rvalues.T, align_keys=["right"])
525476
return type(left)(bm)
526477

@@ -679,12 +630,11 @@ def _frame_arith_method_with_reindex(
679630

680631

681632
def _arith_method_FRAME(cls, op, special):
682-
str_rep = _get_opstr(op)
683633
op_name = _get_op_name(op, special)
684634
default_axis = _get_frame_op_default_axis(op_name)
685635

686-
na_op = define_na_arithmetic_op(op, str_rep)
687-
is_logical = str_rep in ["&", "|", "^"]
636+
na_op = define_na_arithmetic_op(op)
637+
is_logical = op.__name__.strip("_").lstrip("_") in ["and", "or", "xor"]
688638

689639
if op_name in _op_descriptions:
690640
# i.e. include "add" but not "__add__"
@@ -719,15 +669,13 @@ def f(self, other, axis=default_axis, level=None, fill_value=None):
719669
raise NotImplementedError(f"fill_value {fill_value} not supported.")
720670

721671
axis = self._get_axis_number(axis) if axis is not None else 1
722-
new_data = _combine_series_frame(
723-
self, other, pass_op, axis=axis, str_rep=str_rep
724-
)
672+
new_data = _combine_series_frame(self, other, pass_op, axis=axis)
725673
else:
726674
# in this case we always have `np.ndim(other) == 0`
727675
if fill_value is not None:
728676
self = self.fillna(fill_value)
729677

730-
new_data = dispatch_to_series(self, other, op, str_rep)
678+
new_data = dispatch_to_series(self, other, op)
731679

732680
return self._construct_result(new_data)
733681

@@ -737,7 +685,6 @@ def f(self, other, axis=default_axis, level=None, fill_value=None):
737685

738686

739687
def _flex_comp_method_FRAME(cls, op, special):
740-
str_rep = _get_opstr(op)
741688
op_name = _get_op_name(op, special)
742689
default_axis = _get_frame_op_default_axis(op_name)
743690

@@ -752,16 +699,14 @@ def f(self, other, axis=default_axis, level=None):
752699

753700
if isinstance(other, ABCDataFrame):
754701
# Another DataFrame
755-
new_data = dispatch_to_series(self, other, op, str_rep)
702+
new_data = dispatch_to_series(self, other, op)
756703

757704
elif isinstance(other, ABCSeries):
758705
axis = self._get_axis_number(axis) if axis is not None else 1
759-
new_data = _combine_series_frame(
760-
self, other, op, axis=axis, str_rep=str_rep
761-
)
706+
new_data = _combine_series_frame(self, other, op, axis=axis)
762707
else:
763708
# in this case we always have `np.ndim(other) == 0`
764-
new_data = dispatch_to_series(self, other, op, str_rep)
709+
new_data = dispatch_to_series(self, other, op)
765710

766711
return self._construct_result(new_data)
767712

@@ -771,7 +716,6 @@ def f(self, other, axis=default_axis, level=None):
771716

772717

773718
def _comp_method_FRAME(cls, op, special):
774-
str_rep = _get_opstr(op)
775719
op_name = _get_op_name(op, special)
776720

777721
@Appender(f"Wrapper for comparison method {op_name}")
@@ -783,7 +727,7 @@ def f(self, other):
783727

784728
axis = "columns" # only relevant for Series other case
785729
# See GH#4537 for discussion of scalar op behavior
786-
new_data = dispatch_to_series(self, other, op, str_rep, axis=axis)
730+
new_data = dispatch_to_series(self, other, op, axis=axis)
787731
return self._construct_result(new_data)
788732

789733
f.__name__ = op_name

0 commit comments

Comments
 (0)