diff --git a/.travis.yml b/.travis.yml index c263e1dee4115..1f2940404eed0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -152,25 +152,29 @@ before_install: - export DISPLAY=:99.0 install: - - echo "install" + - echo "install start" - ci/prep_ccache.sh - ci/install_travis.sh - ci/submit_ccache.sh + - echo "install done" before_script: - source activate pandas && pip install codecov - ci/install_db.sh script: - - echo "script" + - echo "script start" - ci/run_build_docs.sh - ci/script.sh - ci/lint.sh + - echo "script done" after_success: - source activate pandas && codecov after_script: + - echo "after_script start" - ci/install_test.sh - source activate pandas && ci/print_versions.py - ci/print_skipped.py /tmp/nosetests.xml + - echo "after_script done" diff --git a/doc/source/whatsnew/v0.18.1.txt b/doc/source/whatsnew/v0.18.1.txt index 4cfe82214d0d0..7d0d05d3b946f 100644 --- a/doc/source/whatsnew/v0.18.1.txt +++ b/doc/source/whatsnew/v0.18.1.txt @@ -131,6 +131,7 @@ API changes +- the default for ``.query()/.eval()`` is now ``engine=None`` which will use ``numexpr`` if its installed, else will fallback to the ``python`` engine. This mimics the pre-0.18.1 behavior if ``numexpr`` is installed (and which previously would raise if ``numexpr`` was NOT installed and ``.query()/.eval()`` was used). (:issue:`12749`) - ``CParserError`` is now a ``ValueError`` instead of just an ``Exception`` (:issue:`12551`) - ``read_csv`` no longer allows a combination of strings and integers for the ``usecols`` parameter (:issue:`12678`) diff --git a/pandas/computation/eval.py b/pandas/computation/eval.py index 48459181f5358..6c5c631a6bf0e 100644 --- a/pandas/computation/eval.py +++ b/pandas/computation/eval.py @@ -26,7 +26,19 @@ def _check_engine(engine): * If an invalid engine is passed ImportError * If numexpr was requested but doesn't exist + + Returns + ------- + string engine + """ + + if engine is None: + if _NUMEXPR_INSTALLED: + engine = 'numexpr' + else: + engine = 'python' + if engine not in _engines: raise KeyError('Invalid engine {0!r} passed, valid engines are' ' {1}'.format(engine, list(_engines.keys()))) @@ -41,6 +53,8 @@ def _check_engine(engine): "engine='numexpr' for query/eval " "if 'numexpr' is not installed") + return engine + def _check_parser(parser): """Make sure a valid parser is passed. @@ -131,7 +145,7 @@ def _check_for_locals(expr, stack_level, parser): raise SyntaxError(msg) -def eval(expr, parser='pandas', engine='numexpr', truediv=True, +def eval(expr, parser='pandas', engine=None, truediv=True, local_dict=None, global_dict=None, resolvers=(), level=0, target=None, inplace=None): """Evaluate a Python expression as a string using various backends. @@ -160,10 +174,11 @@ def eval(expr, parser='pandas', engine='numexpr', truediv=True, ``'python'`` parser to retain strict Python semantics. See the :ref:`enhancing performance ` documentation for more details. - engine : string, default 'numexpr', {'python', 'numexpr'} + engine : string or None, default 'numexpr', {'python', 'numexpr'} The engine used to evaluate the expression. Supported engines are + - None : tries to use ``numexpr``, falls back to ``python`` - ``'numexpr'``: This default engine evaluates pandas objects using numexpr for large speed ups in complex expressions with large frames. @@ -230,7 +245,7 @@ def eval(expr, parser='pandas', engine='numexpr', truediv=True, first_expr = True for expr in exprs: expr = _convert_expression(expr) - _check_engine(engine) + engine = _check_engine(engine) _check_parser(parser) _check_resolvers(resolvers) _check_for_locals(expr, level, parser) diff --git a/pandas/tests/frame/test_query_eval.py b/pandas/tests/frame/test_query_eval.py index a52cb018c7bae..9f863bc4f62f3 100644 --- a/pandas/tests/frame/test_query_eval.py +++ b/pandas/tests/frame/test_query_eval.py @@ -19,6 +19,7 @@ makeCustomDataframe as mkdf) import pandas.util.testing as tm +from pandas.computation import _NUMEXPR_INSTALLED from pandas.tests.frame.common import TestData @@ -34,13 +35,59 @@ def skip_if_no_pandas_parser(parser): def skip_if_no_ne(engine='numexpr'): if engine == 'numexpr': - try: - import numexpr as ne # noqa - except ImportError: + if not _NUMEXPR_INSTALLED: raise nose.SkipTest("cannot query engine numexpr when numexpr not " "installed") +class TestCompat(tm.TestCase): + + def setUp(self): + self.df = DataFrame({'A': [1, 2, 3]}) + self.expected1 = self.df[self.df.A > 0] + self.expected2 = self.df.A + 1 + + def test_query_default(self): + + # GH 12749 + # this should always work, whether _NUMEXPR_INSTALLED or not + df = self.df + result = df.query('A>0') + assert_frame_equal(result, self.expected1) + result = df.eval('A+1') + assert_series_equal(result, self.expected2, check_names=False) + + def test_query_None(self): + + df = self.df + result = df.query('A>0', engine=None) + assert_frame_equal(result, self.expected1) + result = df.eval('A+1', engine=None) + assert_series_equal(result, self.expected2, check_names=False) + + def test_query_python(self): + + df = self.df + result = df.query('A>0', engine='python') + assert_frame_equal(result, self.expected1) + result = df.eval('A+1', engine='python') + assert_series_equal(result, self.expected2, check_names=False) + + def test_query_numexpr(self): + + df = self.df + if _NUMEXPR_INSTALLED: + result = df.query('A>0', engine='numexpr') + assert_frame_equal(result, self.expected1) + result = df.eval('A+1', engine='numexpr') + assert_series_equal(result, self.expected2, check_names=False) + else: + self.assertRaises(ImportError, + lambda: df.query('A>0', engine='numexpr')) + self.assertRaises(ImportError, + lambda: df.eval('A+1', engine='numexpr')) + + class TestDataFrameEval(tm.TestCase, TestData): _multiprocess_can_split_ = True diff --git a/pandas/util/testing.py b/pandas/util/testing.py index 8649089a4bbd7..feb8051448396 100644 --- a/pandas/util/testing.py +++ b/pandas/util/testing.py @@ -329,21 +329,16 @@ def _incompat_bottleneck_version(method): def skip_if_no_ne(engine='numexpr'): - import nose - _USE_NUMEXPR = pd.computation.expressions._USE_NUMEXPR + from pandas.computation.expressions import (_USE_NUMEXPR, + _NUMEXPR_INSTALLED) if engine == 'numexpr': - try: - import numexpr as ne - except ImportError: - raise nose.SkipTest("numexpr not installed") - if not _USE_NUMEXPR: - raise nose.SkipTest("numexpr disabled") - - if ne.__version__ < LooseVersion('2.0'): - raise nose.SkipTest("numexpr version too low: " - "%s" % ne.__version__) + import nose + raise nose.SkipTest("numexpr enabled->{enabled}, " + "installed->{installed}".format( + enabled=_USE_NUMEXPR, + installed=_NUMEXPR_INSTALLED)) def _skip_if_has_locale():