Skip to content

Commit 0535192

Browse files
committed
Merge pull request #7181 from cpcloud/eval-with-inf
BUG: correctly lookup global constants in query/eval
2 parents 8c1f550 + a329b2e commit 0535192

File tree

5 files changed

+49
-19
lines changed

5 files changed

+49
-19
lines changed

doc/source/release.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,8 @@ Bug Fixes
530530
- Bug ``PeriodIndex`` string slicing with out of bounds values (:issue:`5407`)
531531
- Fixed a memory error in the hashtable implementation/factorizer on resizing of large tables (:issue:`7157`)
532532
- Bug in ``isnull`` when applied to 0-dimensional object arrays (:issue:`7176`)
533+
- Bug in ``query``/``eval`` where global constants were not looked up correctly
534+
(:issue:`7178`)
533535

534536
pandas 0.13.1
535537
-------------

pandas/computation/ops.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import pandas.core.common as com
1515
from pandas.core.base import StringMixin
1616
from pandas.computation.common import _ensure_decoded, _result_type_many
17+
from pandas.computation.scope import _DEFAULT_GLOBALS
1718

1819

1920
_reductions = 'sum', 'prod'
@@ -48,7 +49,9 @@ def __init__(self, name, env, side=None, encoding=None):
4849
self._name = name
4950
self.env = env
5051
self.side = side
51-
self.is_local = text_type(name).startswith(_LOCAL_TAG)
52+
tname = text_type(name)
53+
self.is_local = (tname.startswith(_LOCAL_TAG) or
54+
tname in _DEFAULT_GLOBALS)
5255
self._value = self._resolve_name()
5356
self.encoding = encoding
5457

pandas/computation/scope.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,18 @@
22
"""
33

44
import sys
5-
import operator
65
import struct
76
import inspect
87
import datetime
98
import itertools
109
import pprint
1110

11+
import numpy as np
12+
1213
import pandas as pd
1314
from pandas.compat import DeepChainMap, map, StringIO
14-
from pandas.core import common as com
1515
from pandas.core.base import StringMixin
16-
from pandas.computation.ops import UndefinedVariableError, _LOCAL_TAG
16+
import pandas.computation as compu
1717

1818

1919
def _ensure_scope(level, global_dict=None, local_dict=None, resolvers=(),
@@ -45,14 +45,15 @@ def _raw_hex_id(obj):
4545
return ''.join(map(_replacer, packed))
4646

4747

48-
4948
_DEFAULT_GLOBALS = {
5049
'Timestamp': pd.lib.Timestamp,
5150
'datetime': datetime.datetime,
5251
'True': True,
5352
'False': False,
5453
'list': list,
55-
'tuple': tuple
54+
'tuple': tuple,
55+
'inf': np.inf,
56+
'Inf': np.inf,
5657
}
5758

5859

@@ -186,7 +187,7 @@ def resolve(self, key, is_local):
186187
# e.g., df[df > 0]
187188
return self.temps[key]
188189
except KeyError:
189-
raise UndefinedVariableError(key, is_local)
190+
raise compu.ops.UndefinedVariableError(key, is_local)
190191

191192
def swapkey(self, old_key, new_key, new_value=None):
192193
"""Replace a variable name, with a potentially new value.

pandas/computation/tests/test_eval.py

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
#!/usr/bin/env python
22

3-
import functools
43
from itertools import product
54
from distutils.version import LooseVersion
65

76
import nose
8-
from nose.tools import assert_raises, assert_true, assert_false, assert_equal
7+
from nose.tools import assert_raises
98

109
from numpy.random import randn, rand, randint
1110
import numpy as np
@@ -887,7 +886,7 @@ def check_complex_series_frame_alignment(self, engine, parser):
887886
expected = expected2 + df
888887

889888
res = pd.eval('df2 + s + df', engine=engine, parser=parser)
890-
assert_equal(res.shape, expected.shape)
889+
tm.assert_equal(res.shape, expected.shape)
891890
assert_frame_equal(res, expected)
892891

893892
@slow
@@ -930,13 +929,13 @@ def check_performance_warning_for_poor_alignment(self, engine, parser):
930929
pd.eval('df + s', engine=engine, parser=parser)
931930

932931
if not is_python_engine:
933-
assert_equal(len(w), 1)
932+
tm.assert_equal(len(w), 1)
934933
msg = str(w[0].message)
935934
expected = ("Alignment difference on axis {0} is larger"
936935
" than an order of magnitude on term {1!r}, "
937936
"by more than {2:.4g}; performance may suffer"
938937
"".format(1, 'df', np.log10(s.size - df.shape[1])))
939-
assert_equal(msg, expected)
938+
tm.assert_equal(msg, expected)
940939

941940
def test_performance_warning_for_poor_alignment(self):
942941
for engine, parser in ENGINES_PARSERS:
@@ -982,17 +981,17 @@ def test_simple_arith_ops(self):
982981
else:
983982
expec = _eval_single_bin(1, op, 1, self.engine)
984983
x = self.eval(ex, engine=self.engine, parser=self.parser)
985-
assert_equal(x, expec)
984+
tm.assert_equal(x, expec)
986985

987986
expec = _eval_single_bin(x, op, 1, self.engine)
988987
y = self.eval(ex2, local_dict={'x': x}, engine=self.engine,
989988
parser=self.parser)
990-
assert_equal(y, expec)
989+
tm.assert_equal(y, expec)
991990

992991
expec = _eval_single_bin(1, op, x + 1, self.engine)
993992
y = self.eval(ex3, local_dict={'x': x},
994993
engine=self.engine, parser=self.parser)
995-
assert_equal(y, expec)
994+
tm.assert_equal(y, expec)
996995

997996
def test_simple_bool_ops(self):
998997
for op, lhs, rhs in product(expr._bool_ops_syms, (True, False),
@@ -1024,7 +1023,7 @@ def test_4d_ndarray_fails(self):
10241023

10251024
def test_constant(self):
10261025
x = self.eval('1')
1027-
assert_equal(x, 1)
1026+
tm.assert_equal(x, 1)
10281027

10291028
def test_single_variable(self):
10301029
df = DataFrame(randn(10, 2))
@@ -1379,7 +1378,7 @@ def check_no_new_locals(self, engine, parser):
13791378
pd.eval('x + 1', local_dict=lcls, engine=engine, parser=parser)
13801379
lcls2 = locals().copy()
13811380
lcls2.pop('lcls')
1382-
assert_equal(lcls, lcls2)
1381+
tm.assert_equal(lcls, lcls2)
13831382

13841383
def test_no_new_locals(self):
13851384
for engine, parser in product(_engines, expr._parsers):
@@ -1391,7 +1390,7 @@ def check_no_new_globals(self, engine, parser):
13911390
gbls = globals().copy()
13921391
pd.eval('x + 1', engine=engine, parser=parser)
13931392
gbls2 = globals().copy()
1394-
assert_equal(gbls, gbls2)
1393+
tm.assert_equal(gbls, gbls2)
13951394

13961395
def test_no_new_globals(self):
13971396
for engine, parser in product(_engines, expr._parsers):
@@ -1556,14 +1555,27 @@ def check_bool_ops_fails_on_scalars(gen, lhs, cmp, rhs, engine, parser):
15561555
def test_bool_ops_fails_on_scalars():
15571556
_bool_ops_syms = 'and', 'or'
15581557
dtypes = int, float
1559-
gen = {int: lambda : np.random.randint(10), float: np.random.randn}
1558+
gen = {int: lambda: np.random.randint(10), float: np.random.randn}
15601559
for engine, parser, dtype1, cmp, dtype2 in product(_engines, expr._parsers,
15611560
dtypes, _bool_ops_syms,
15621561
dtypes):
15631562
yield (check_bool_ops_fails_on_scalars, gen, gen[dtype1](), cmp,
15641563
gen[dtype2](), engine, parser)
15651564

15661565

1566+
def check_inf(engine, parser):
1567+
tm.skip_if_no_ne(engine)
1568+
s = 'inf + 1'
1569+
expected = np.inf
1570+
result = pd.eval(s, engine=engine, parser=parser)
1571+
tm.assert_equal(result, expected)
1572+
1573+
1574+
def test_inf():
1575+
for engine, parser in ENGINES_PARSERS:
1576+
yield check_inf, engine, parser
1577+
1578+
15671579
if __name__ == '__main__':
15681580
nose.runmodule(argv=[__file__, '-vvs', '-x', '--pdb', '--pdb-failure'],
15691581
exit=False)

pandas/tests/test_frame.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13284,6 +13284,18 @@ def test_index_resolvers_come_after_columns_with_the_same_name(self):
1328413284
expected = df.loc[df.index[df.index > 5]]
1328513285
tm.assert_frame_equal(result, expected)
1328613286

13287+
def test_inf(self):
13288+
n = 10
13289+
df = DataFrame({'a': np.random.rand(n), 'b': np.random.rand(n)})
13290+
df.loc[::2, 0] = np.inf
13291+
ops = '==', '!='
13292+
d = dict(zip(ops, (operator.eq, operator.ne)))
13293+
for op, f in d.items():
13294+
q = 'a %s inf' % op
13295+
expected = df[f(df.a, np.inf)]
13296+
result = df.query(q, engine=self.engine, parser=self.parser)
13297+
tm.assert_frame_equal(result, expected)
13298+
1328713299

1328813300
class TestDataFrameQueryNumExprPython(TestDataFrameQueryNumExprPandas):
1328913301

0 commit comments

Comments
 (0)