@@ -147,9 +147,6 @@ def lhs(request):
147
147
148
148
@td .skip_if_no_ne
149
149
class TestEvalNumexprPandas :
150
- exclude_cmp : list [str ] = []
151
- exclude_bool : list [str ] = []
152
-
153
150
engine = "numexpr"
154
151
parser = "pandas"
155
152
@@ -171,8 +168,7 @@ def current_engines(self):
171
168
@pytest .mark .parametrize ("cmp2" , [">" , "<" ], ids = ["gt" , "lt" ])
172
169
@pytest .mark .parametrize ("binop" , expr .BOOL_OPS_SYMS )
173
170
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" ]:
176
172
msg = "'BoolOp' nodes are not implemented"
177
173
with pytest .raises (NotImplementedError , match = msg ):
178
174
ex = f"(lhs { cmp1 } rhs) { binop } (lhs { cmp2 } rhs)"
@@ -200,7 +196,7 @@ def test_simple_cmp_ops(self, cmp_op):
200
196
tm .randbool (),
201
197
)
202
198
203
- if cmp_op in self . exclude_cmp :
199
+ if self . parser == "python" and cmp_op in [ "in" , "not in" ] :
204
200
msg = "'(In|NotIn)' nodes are not implemented"
205
201
for lhs , rhs in product (bool_lhses , bool_rhses ):
206
202
@@ -232,7 +228,7 @@ def test_single_invert_op(self, op, lhs):
232
228
233
229
@pytest .mark .parametrize ("op" , expr .CMP_OPS_SYMS )
234
230
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" ] :
236
232
237
233
msg = "'(In|NotIn)' nodes are not implemented"
238
234
with pytest .raises (NotImplementedError , match = msg ):
@@ -289,11 +285,13 @@ def check_operands(left, right, cmp_op):
289
285
290
286
def check_simple_cmp_op (self , lhs , cmp1 , rhs ):
291
287
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
+ ]
297
295
)
298
296
if cmp1 in ("in" , "not in" ) and not is_list_like (rhs ):
299
297
with pytest .raises (TypeError , match = msg ):
@@ -327,13 +325,17 @@ def check_alignment(self, result, nlhs, ghs, op):
327
325
# TypeError, AttributeError: series or frame with scalar align
328
326
pass
329
327
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 )
337
339
338
340
# modulus, pow, and floor division require special casing
339
341
@@ -369,24 +371,9 @@ def check_floor_division(self, lhs, arith1, rhs):
369
371
parser = self .parser ,
370
372
)
371
373
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
-
387
374
def check_pow (self , lhs , arith1 , rhs ):
388
375
ex = f"lhs { arith1 } rhs"
389
- expected = self . get_expected_pow_result (lhs , rhs )
376
+ expected = _eval_single_bin (lhs , "**" , rhs , self . engine )
390
377
result = pd .eval (ex , engine = self .engine , parser = self .parser )
391
378
392
379
if (
@@ -402,9 +389,9 @@ def check_pow(self, lhs, arith1, rhs):
402
389
403
390
ex = f"(lhs { arith1 } rhs) { arith1 } rhs"
404
391
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 )
408
395
tm .assert_almost_equal (result , expected )
409
396
410
397
def check_single_invert_op (self , elem , cmp1 ):
@@ -426,11 +413,13 @@ def check_compound_invert_op(self, lhs, cmp1, rhs):
426
413
skip_these = ["in" , "not in" ]
427
414
ex = f"~(lhs { cmp1 } rhs)"
428
415
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
+ ]
434
423
)
435
424
if is_scalar (rhs ) and cmp1 in skip_these :
436
425
with pytest .raises (TypeError , match = msg ):
@@ -457,11 +446,8 @@ def check_compound_invert_op(self, lhs, cmp1, rhs):
457
446
ev = pd .eval (ex , engine = self .engine , parser = self .parser )
458
447
tm .assert_almost_equal (ev , result )
459
448
460
- def ex (self , op , var_name = "lhs" ):
461
- return f"{ op } { var_name } "
462
-
463
449
def test_frame_invert (self ):
464
- expr = self . ex ( "~" )
450
+ expr = "~lhs"
465
451
466
452
# ~ ##
467
453
# frame
@@ -505,7 +491,7 @@ def test_frame_invert(self):
505
491
506
492
def test_series_invert (self ):
507
493
# ~ ####
508
- expr = self . ex ( "~" )
494
+ expr = "~lhs"
509
495
510
496
# series
511
497
# float raises
@@ -551,7 +537,7 @@ def test_series_invert(self):
551
537
result = pd .eval (expr , engine = self .engine , parser = self .parser )
552
538
553
539
def test_frame_negate (self ):
554
- expr = self . ex ( "-" )
540
+ expr = "-lhs"
555
541
556
542
# float
557
543
lhs = DataFrame (np .random .randn (5 , 2 ))
@@ -577,7 +563,7 @@ def test_frame_negate(self):
577
563
tm .assert_frame_equal (expect , result )
578
564
579
565
def test_series_negate (self ):
580
- expr = self . ex ( "-" )
566
+ expr = "-lhs"
581
567
582
568
# float
583
569
lhs = Series (np .random .randn (5 ))
@@ -614,7 +600,7 @@ def test_series_negate(self):
614
600
],
615
601
)
616
602
def test_frame_pos (self , lhs ):
617
- expr = self . ex ( "+" )
603
+ expr = "+lhs"
618
604
expect = lhs
619
605
620
606
result = pd .eval (expr , engine = self .engine , parser = self .parser )
@@ -632,7 +618,7 @@ def test_frame_pos(self, lhs):
632
618
],
633
619
)
634
620
def test_series_pos (self , lhs ):
635
- expr = self . ex ( "+" )
621
+ expr = "+lhs"
636
622
expect = lhs
637
623
638
624
result = pd .eval (expr , engine = self .engine , parser = self .parser )
@@ -782,19 +768,9 @@ def test_disallow_python_keywords(self):
782
768
783
769
@td .skip_if_no_ne
784
770
class TestEvalNumexprPython (TestEvalNumexprPandas ):
785
- exclude_cmp : list [str ] = ["in" , "not in" ]
786
- exclude_bool : list [str ] = ["and" , "or" ]
787
-
788
771
engine = "numexpr"
789
772
parser = "python"
790
773
791
- @classmethod
792
- def setup_class (cls ):
793
- super ().setup_class ()
794
- import numexpr as ne
795
-
796
- cls .ne = ne
797
-
798
774
def check_chained_cmp_op (self , lhs , cmp1 , mid , cmp2 , rhs ):
799
775
ex1 = f"lhs { cmp1 } mid { cmp2 } rhs"
800
776
msg = "'BoolOp' nodes are not implemented"
@@ -816,25 +792,14 @@ def check_modulus(self, lhs, arith1, rhs):
816
792
expected = _eval_single_bin (expected , arith1 , rhs , self .engine )
817
793
tm .assert_almost_equal (result , expected )
818
794
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
-
830
795
831
796
class TestEvalPythonPandas (TestEvalPythonPython ):
832
797
engine = "python"
833
798
parser = "pandas"
834
- exclude_bool : list [str ] = []
835
- exclude_cmp : list [str ] = []
836
799
837
800
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.
838
803
TestEvalNumexprPandas .check_chained_cmp_op (self , lhs , cmp1 , mid , cmp2 , rhs )
839
804
840
805
0 commit comments