Skip to content

TST/REF: fixturize TestEval #44663

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 29, 2021
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 44 additions & 96 deletions pandas/tests/computation/test_eval.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,12 @@ def lhs(request):
midhs = lhs


@td.skip_if_no_ne
class TestEvalNumexprPandas:
engine = "numexpr"
parser = "pandas"
class TestEval:
@pytest.fixture(autouse=True)
def set_engine_parser_attrs(self, engine, parser):
# Older tests look for these as attributes, so we set them here.
self.engine = engine
self.parser = parser

@classmethod
def setup_class(cls):
Expand Down Expand Up @@ -184,47 +186,18 @@ def test_complex_cmp_ops(self, cmp1, cmp2, binop, lhs, rhs):
self.check_equal(result, expected)

@pytest.mark.parametrize("cmp_op", expr.CMP_OPS_SYMS)
def test_simple_cmp_ops(self, cmp_op):
bool_lhses = (
DataFrame(tm.randbool(size=(10, 5))),
Series(tm.randbool((5,))),
tm.randbool(),
)
bool_rhses = (
DataFrame(tm.randbool(size=(10, 5))),
Series(tm.randbool((5,))),
tm.randbool(),
)
def test_simple_cmp_ops(self, cmp_op, lhs, rhs):
lhs = lhs < 0
rhs = rhs < 0

if self.parser == "python" and cmp_op in ["in", "not in"]:
msg = "'(In|NotIn)' nodes are not implemented"
for lhs, rhs in product(bool_lhses, bool_rhses):

with pytest.raises(NotImplementedError, match=msg):
self.check_simple_cmp_op(lhs, cmp_op, rhs)
with pytest.raises(NotImplementedError, match=msg):
self.check_simple_cmp_op(lhs, cmp_op, rhs)
return

for lhs, rhs in product(bool_lhses, bool_rhses):
self.check_simple_cmp_op(lhs, cmp_op, rhs)

@pytest.mark.parametrize("op", _good_arith_ops)
def test_binary_arith_ops(self, op, lhs, rhs, request):
self.check_binary_arith_op(lhs, op, rhs)

def test_modulus(self, lhs, rhs):
self.check_modulus(lhs, "%", rhs)

def test_floor_division(self, lhs, rhs):
self.check_floor_division(lhs, "//", rhs)

@td.skip_if_windows
def test_pow(self, lhs, rhs):
# odd failure on win32 platform, so skip
self.check_pow(lhs, "**", rhs)

@pytest.mark.parametrize("op", expr.CMP_OPS_SYMS)
def test_single_invert_op(self, op, lhs):
self.check_single_invert_op(lhs, op)
self.check_simple_cmp_op(lhs, cmp_op, rhs)

@pytest.mark.parametrize("op", expr.CMP_OPS_SYMS)
def test_compound_invert_op(self, op, lhs, rhs, request):
Expand Down Expand Up @@ -266,11 +239,16 @@ def check_equal(self, result, expected):
assert result == expected

def check_chained_cmp_op(self, lhs, cmp1, mid, cmp2, rhs):
def check_operands(left, right, cmp_op):
return _eval_single_bin(left, cmp_op, right, self.engine)

lhs_new = check_operands(lhs, mid, cmp1)
rhs_new = check_operands(mid, rhs, cmp2)
if self.parser == "python":
ex1 = f"lhs {cmp1} mid {cmp2} rhs"
msg = "'BoolOp' nodes are not implemented"
with pytest.raises(NotImplementedError, match=msg):
pd.eval(ex1, engine=self.engine, parser=self.parser)
return

lhs_new = _eval_single_bin(lhs, cmp1, mid, self.engine)
rhs_new = _eval_single_bin(mid, cmp2, rhs, self.engine)

if lhs_new is not None and rhs_new is not None:
ex1 = f"lhs {cmp1} mid {cmp2} rhs"
Expand Down Expand Up @@ -306,7 +284,8 @@ def check_simple_cmp_op(self, lhs, cmp1, rhs):
result = pd.eval(ex, engine=self.engine, parser=self.parser)
self.check_equal(result, expected)

def check_binary_arith_op(self, lhs, arith1, rhs):
@pytest.mark.parametrize("arith1", _good_arith_ops)
def test_binary_arith_ops(self, arith1, lhs, rhs):
ex = f"lhs {arith1} rhs"
result = pd.eval(ex, engine=self.engine, parser=self.parser)
expected = _eval_single_bin(lhs, arith1, rhs, self.engine)
Expand Down Expand Up @@ -339,20 +318,24 @@ def check_alignment(self, result, nlhs, ghs, op):

# modulus, pow, and floor division require special casing

def check_modulus(self, lhs, arith1, rhs):
ex = f"lhs {arith1} rhs"
def test_modulus(self, lhs, rhs):
ex = r"lhs % rhs"
result = pd.eval(ex, engine=self.engine, parser=self.parser)
expected = lhs % rhs

tm.assert_almost_equal(result, expected)
expected = self.ne.evaluate(f"expected {arith1} rhs")
if isinstance(result, (DataFrame, Series)):
tm.assert_almost_equal(result.values, expected)

if self.engine == "numexpr":
expected = self.ne.evaluate(r"expected % rhs")
if isinstance(result, (DataFrame, Series)):
tm.assert_almost_equal(result.values, expected)
else:
tm.assert_almost_equal(result, expected.item())
else:
tm.assert_almost_equal(result, expected.item())
expected = _eval_single_bin(expected, "%", rhs, self.engine)
tm.assert_almost_equal(result, expected)

def check_floor_division(self, lhs, arith1, rhs):
ex = f"lhs {arith1} rhs"
def test_floor_division(self, lhs, rhs):
ex = "lhs // rhs"

if self.engine == "python":
res = pd.eval(ex, engine=self.engine, parser=self.parser)
Expand All @@ -371,8 +354,10 @@ def check_floor_division(self, lhs, arith1, rhs):
parser=self.parser,
)

def check_pow(self, lhs, arith1, rhs):
ex = f"lhs {arith1} rhs"
@td.skip_if_windows
def test_pow(self, lhs, rhs):
# odd failure on win32 platform, so skip
ex = "lhs ** rhs"
expected = _eval_single_bin(lhs, "**", rhs, self.engine)
result = pd.eval(ex, engine=self.engine, parser=self.parser)

Expand All @@ -387,19 +372,19 @@ def check_pow(self, lhs, arith1, rhs):
else:
tm.assert_almost_equal(result, expected)

ex = f"(lhs {arith1} rhs) {arith1} rhs"
ex = "(lhs ** rhs) ** rhs"
result = pd.eval(ex, engine=self.engine, parser=self.parser)

middle = _eval_single_bin(lhs, "**", rhs, self.engine)
expected = _eval_single_bin(middle, "**", rhs, self.engine)
tm.assert_almost_equal(result, expected)

def check_single_invert_op(self, elem, cmp1):
def check_single_invert_op(self, lhs):
# simple
try:
elb = elem.astype(bool)
elb = lhs.astype(bool)
except AttributeError:
elb = np.array([bool(elem)])
elb = np.array([bool(lhs)])
expected = ~elb
result = pd.eval("~elb", engine=self.engine, parser=self.parser)
tm.assert_almost_equal(expected, result)
Expand Down Expand Up @@ -766,43 +751,6 @@ def test_disallow_python_keywords(self):
df.query("lambda == 0")


@td.skip_if_no_ne
class TestEvalNumexprPython(TestEvalNumexprPandas):
engine = "numexpr"
parser = "python"

def check_chained_cmp_op(self, lhs, cmp1, mid, cmp2, rhs):
ex1 = f"lhs {cmp1} mid {cmp2} rhs"
msg = "'BoolOp' nodes are not implemented"
with pytest.raises(NotImplementedError, match=msg):
pd.eval(ex1, engine=self.engine, parser=self.parser)


class TestEvalPythonPython(TestEvalNumexprPython):
engine = "python"
parser = "python"

def check_modulus(self, lhs, arith1, rhs):
ex = f"lhs {arith1} rhs"
result = pd.eval(ex, engine=self.engine, parser=self.parser)

expected = lhs % rhs
tm.assert_almost_equal(result, expected)

expected = _eval_single_bin(expected, arith1, rhs, self.engine)
tm.assert_almost_equal(result, expected)


class TestEvalPythonPandas(TestEvalPythonPython):
engine = "python"
parser = "pandas"

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


f = lambda *args, **kwargs: np.random.randn()


Expand Down