Skip to content

Commit 9f23f3f

Browse files
authored
TST: de-duplicate test_eval (#44661)
1 parent 243b8a6 commit 9f23f3f

File tree

1 file changed

+40
-75
lines changed

1 file changed

+40
-75
lines changed

pandas/tests/computation/test_eval.py

+40-75
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,6 @@ def lhs(request):
147147

148148
@td.skip_if_no_ne
149149
class TestEvalNumexprPandas:
150-
exclude_cmp: list[str] = []
151-
exclude_bool: list[str] = []
152-
153150
engine = "numexpr"
154151
parser = "pandas"
155152

@@ -171,8 +168,7 @@ def current_engines(self):
171168
@pytest.mark.parametrize("cmp2", [">", "<"], ids=["gt", "lt"])
172169
@pytest.mark.parametrize("binop", expr.BOOL_OPS_SYMS)
173170
def test_complex_cmp_ops(self, cmp1, cmp2, binop, lhs, rhs):
174-
if binop in self.exclude_bool:
175-
# i.e. "&" and "|"
171+
if self.parser == "python" and binop in ["and", "or"]:
176172
msg = "'BoolOp' nodes are not implemented"
177173
with pytest.raises(NotImplementedError, match=msg):
178174
ex = f"(lhs {cmp1} rhs) {binop} (lhs {cmp2} rhs)"
@@ -200,7 +196,7 @@ def test_simple_cmp_ops(self, cmp_op):
200196
tm.randbool(),
201197
)
202198

203-
if cmp_op in self.exclude_cmp:
199+
if self.parser == "python" and cmp_op in ["in", "not in"]:
204200
msg = "'(In|NotIn)' nodes are not implemented"
205201
for lhs, rhs in product(bool_lhses, bool_rhses):
206202

@@ -232,7 +228,7 @@ def test_single_invert_op(self, op, lhs):
232228

233229
@pytest.mark.parametrize("op", expr.CMP_OPS_SYMS)
234230
def test_compound_invert_op(self, op, lhs, rhs, request):
235-
if op in self.exclude_cmp:
231+
if self.parser == "python" and op in ["in", "not in"]:
236232

237233
msg = "'(In|NotIn)' nodes are not implemented"
238234
with pytest.raises(NotImplementedError, match=msg):
@@ -289,11 +285,13 @@ def check_operands(left, right, cmp_op):
289285

290286
def check_simple_cmp_op(self, lhs, cmp1, rhs):
291287
ex = f"lhs {cmp1} rhs"
292-
msg = (
293-
r"only list-like( or dict-like)? objects are allowed to be "
294-
r"passed to (DataFrame\.)?isin\(\), you passed a "
295-
r"(\[|')bool(\]|')|"
296-
"argument of type 'bool' is not iterable"
288+
msg = "|".join(
289+
[
290+
r"only list-like( or dict-like)? objects are allowed to be "
291+
r"passed to (DataFrame\.)?isin\(\), you passed a "
292+
r"(\[|')bool(\]|')",
293+
"argument of type 'bool' is not iterable",
294+
]
297295
)
298296
if cmp1 in ("in", "not in") and not is_list_like(rhs):
299297
with pytest.raises(TypeError, match=msg):
@@ -327,13 +325,17 @@ def check_alignment(self, result, nlhs, ghs, op):
327325
# TypeError, AttributeError: series or frame with scalar align
328326
pass
329327
else:
330-
# direct numpy comparison
331-
expected = self.ne.evaluate(f"nlhs {op} ghs")
332-
# Update assert statement due to unreliable numerical
333-
# precision component (GH37328)
334-
# TODO: update testing code so that assert_almost_equal statement
335-
# can be replaced again by the assert_numpy_array_equal statement
336-
tm.assert_almost_equal(result.values, expected)
328+
if self.engine == "numexpr":
329+
# direct numpy comparison
330+
expected = self.ne.evaluate(f"nlhs {op} ghs")
331+
# Update assert statement due to unreliable numerical
332+
# precision component (GH37328)
333+
# TODO: update testing code so that assert_almost_equal statement
334+
# can be replaced again by the assert_numpy_array_equal statement
335+
tm.assert_almost_equal(result.values, expected)
336+
else:
337+
expected = eval(f"nlhs {op} ghs")
338+
tm.assert_almost_equal(result, expected)
337339

338340
# modulus, pow, and floor division require special casing
339341

@@ -369,24 +371,9 @@ def check_floor_division(self, lhs, arith1, rhs):
369371
parser=self.parser,
370372
)
371373

372-
def get_expected_pow_result(self, lhs, rhs):
373-
try:
374-
expected = _eval_single_bin(lhs, "**", rhs, self.engine)
375-
except ValueError as e:
376-
if str(e).startswith(
377-
"negative number cannot be raised to a fractional power"
378-
):
379-
if self.engine == "python":
380-
pytest.skip(str(e))
381-
else:
382-
expected = np.nan
383-
else:
384-
raise
385-
return expected
386-
387374
def check_pow(self, lhs, arith1, rhs):
388375
ex = f"lhs {arith1} rhs"
389-
expected = self.get_expected_pow_result(lhs, rhs)
376+
expected = _eval_single_bin(lhs, "**", rhs, self.engine)
390377
result = pd.eval(ex, engine=self.engine, parser=self.parser)
391378

392379
if (
@@ -402,9 +389,9 @@ def check_pow(self, lhs, arith1, rhs):
402389

403390
ex = f"(lhs {arith1} rhs) {arith1} rhs"
404391
result = pd.eval(ex, engine=self.engine, parser=self.parser)
405-
expected = self.get_expected_pow_result(
406-
self.get_expected_pow_result(lhs, rhs), rhs
407-
)
392+
393+
middle = _eval_single_bin(lhs, "**", rhs, self.engine)
394+
expected = _eval_single_bin(middle, "**", rhs, self.engine)
408395
tm.assert_almost_equal(result, expected)
409396

410397
def check_single_invert_op(self, elem, cmp1):
@@ -426,11 +413,13 @@ def check_compound_invert_op(self, lhs, cmp1, rhs):
426413
skip_these = ["in", "not in"]
427414
ex = f"~(lhs {cmp1} rhs)"
428415

429-
msg = (
430-
r"only list-like( or dict-like)? objects are allowed to be "
431-
r"passed to (DataFrame\.)?isin\(\), you passed a "
432-
r"(\[|')float(\]|')|"
433-
"argument of type 'float' is not iterable"
416+
msg = "|".join(
417+
[
418+
r"only list-like( or dict-like)? objects are allowed to be "
419+
r"passed to (DataFrame\.)?isin\(\), you passed a "
420+
r"(\[|')float(\]|')",
421+
"argument of type 'float' is not iterable",
422+
]
434423
)
435424
if is_scalar(rhs) and cmp1 in skip_these:
436425
with pytest.raises(TypeError, match=msg):
@@ -457,11 +446,8 @@ def check_compound_invert_op(self, lhs, cmp1, rhs):
457446
ev = pd.eval(ex, engine=self.engine, parser=self.parser)
458447
tm.assert_almost_equal(ev, result)
459448

460-
def ex(self, op, var_name="lhs"):
461-
return f"{op}{var_name}"
462-
463449
def test_frame_invert(self):
464-
expr = self.ex("~")
450+
expr = "~lhs"
465451

466452
# ~ ##
467453
# frame
@@ -505,7 +491,7 @@ def test_frame_invert(self):
505491

506492
def test_series_invert(self):
507493
# ~ ####
508-
expr = self.ex("~")
494+
expr = "~lhs"
509495

510496
# series
511497
# float raises
@@ -551,7 +537,7 @@ def test_series_invert(self):
551537
result = pd.eval(expr, engine=self.engine, parser=self.parser)
552538

553539
def test_frame_negate(self):
554-
expr = self.ex("-")
540+
expr = "-lhs"
555541

556542
# float
557543
lhs = DataFrame(np.random.randn(5, 2))
@@ -577,7 +563,7 @@ def test_frame_negate(self):
577563
tm.assert_frame_equal(expect, result)
578564

579565
def test_series_negate(self):
580-
expr = self.ex("-")
566+
expr = "-lhs"
581567

582568
# float
583569
lhs = Series(np.random.randn(5))
@@ -614,7 +600,7 @@ def test_series_negate(self):
614600
],
615601
)
616602
def test_frame_pos(self, lhs):
617-
expr = self.ex("+")
603+
expr = "+lhs"
618604
expect = lhs
619605

620606
result = pd.eval(expr, engine=self.engine, parser=self.parser)
@@ -632,7 +618,7 @@ def test_frame_pos(self, lhs):
632618
],
633619
)
634620
def test_series_pos(self, lhs):
635-
expr = self.ex("+")
621+
expr = "+lhs"
636622
expect = lhs
637623

638624
result = pd.eval(expr, engine=self.engine, parser=self.parser)
@@ -782,19 +768,9 @@ def test_disallow_python_keywords(self):
782768

783769
@td.skip_if_no_ne
784770
class TestEvalNumexprPython(TestEvalNumexprPandas):
785-
exclude_cmp: list[str] = ["in", "not in"]
786-
exclude_bool: list[str] = ["and", "or"]
787-
788771
engine = "numexpr"
789772
parser = "python"
790773

791-
@classmethod
792-
def setup_class(cls):
793-
super().setup_class()
794-
import numexpr as ne
795-
796-
cls.ne = ne
797-
798774
def check_chained_cmp_op(self, lhs, cmp1, mid, cmp2, rhs):
799775
ex1 = f"lhs {cmp1} mid {cmp2} rhs"
800776
msg = "'BoolOp' nodes are not implemented"
@@ -816,25 +792,14 @@ def check_modulus(self, lhs, arith1, rhs):
816792
expected = _eval_single_bin(expected, arith1, rhs, self.engine)
817793
tm.assert_almost_equal(result, expected)
818794

819-
def check_alignment(self, result, nlhs, ghs, op):
820-
try:
821-
nlhs, ghs = nlhs.align(ghs)
822-
except (ValueError, TypeError, AttributeError):
823-
# ValueError: series frame or frame series align
824-
# TypeError, AttributeError: series or frame with scalar align
825-
pass
826-
else:
827-
expected = eval(f"nlhs {op} ghs")
828-
tm.assert_almost_equal(result, expected)
829-
830795

831796
class TestEvalPythonPandas(TestEvalPythonPython):
832797
engine = "python"
833798
parser = "pandas"
834-
exclude_bool: list[str] = []
835-
exclude_cmp: list[str] = []
836799

837800
def check_chained_cmp_op(self, lhs, cmp1, mid, cmp2, rhs):
801+
# FIXME: by calling this parent class method, we are using the parent
802+
# class's "engine" and "parser", which I don't think is what we want.
838803
TestEvalNumexprPandas.check_chained_cmp_op(self, lhs, cmp1, mid, cmp2, rhs)
839804

840805

0 commit comments

Comments
 (0)