Skip to content

Commit aaebb2b

Browse files
anjsudhPingviinituutti
authored andcommitted
Floor and ceil methods during pandas.eval which are provided by numexpr (pandas-dev#24355)
1 parent 0614172 commit aaebb2b

File tree

4 files changed

+51
-7
lines changed

4 files changed

+51
-7
lines changed

doc/source/whatsnew/v0.24.0.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -1441,7 +1441,7 @@ Numeric
14411441
- :meth:`Series.agg` can now handle numpy NaN-aware methods like :func:`numpy.nansum` (:issue:`19629`)
14421442
- Bug in :meth:`Series.rank` and :meth:`DataFrame.rank` when ``pct=True`` and more than 2:sup:`24` rows are present resulted in percentages greater than 1.0 (:issue:`18271`)
14431443
- Calls such as :meth:`DataFrame.round` with a non-unique :meth:`CategoricalIndex` now return expected data. Previously, data would be improperly duplicated (:issue:`21809`).
1444-
- Added ``log10`` to the list of supported functions in :meth:`DataFrame.eval` (:issue:`24139`)
1444+
- Added ``log10``, `floor` and `ceil` to the list of supported functions in :meth:`DataFrame.eval` (:issue:`24139`, :issue:`24353`)
14451445
- Logical operations ``&, |, ^`` between :class:`Series` and :class:`Index` will no longer raise ``ValueError`` (:issue:`22092`)
14461446
- Checking PEP 3141 numbers in :func:`~pandas.api.types.is_scalar` function returns ``True`` (:issue:`22903`)
14471447
- Reduction methods like :meth:`Series.sum` now accept the default value of ``keepdims=False`` when called from a NumPy ufunc, rather than raising a ``TypeError``. Full support for ``keepdims`` has not been implemented (:issue:`24356`).

pandas/core/computation/check.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33

44
_NUMEXPR_INSTALLED = False
55
_MIN_NUMEXPR_VERSION = "2.6.1"
6+
_NUMEXPR_VERSION = None
67

78
try:
89
import numexpr as ne
910
ver = LooseVersion(ne.__version__)
1011
_NUMEXPR_INSTALLED = ver >= LooseVersion(_MIN_NUMEXPR_VERSION)
12+
_NUMEXPR_VERSION = ver
1113

1214
if not _NUMEXPR_INSTALLED:
1315
warnings.warn(
@@ -19,4 +21,4 @@
1921
except ImportError: # pragma: no cover
2022
pass
2123

22-
__all__ = ['_NUMEXPR_INSTALLED']
24+
__all__ = ['_NUMEXPR_INSTALLED', '_NUMEXPR_VERSION']

pandas/core/computation/ops.py

+13-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"""
33

44
from datetime import datetime
5+
from distutils.version import LooseVersion
56
from functools import partial
67
import operator as op
78

@@ -23,8 +24,11 @@
2324

2425
_unary_math_ops = ('sin', 'cos', 'exp', 'log', 'expm1', 'log1p',
2526
'sqrt', 'sinh', 'cosh', 'tanh', 'arcsin', 'arccos',
26-
'arctan', 'arccosh', 'arcsinh', 'arctanh', 'abs', 'log10')
27+
'arctan', 'arccosh', 'arcsinh', 'arctanh', 'abs', 'log10',
28+
'floor', 'ceil'
29+
)
2730
_binary_math_ops = ('arctan2',)
31+
2832
_mathops = _unary_math_ops + _binary_math_ops
2933

3034

@@ -539,11 +543,17 @@ def __unicode__(self):
539543

540544

541545
class FuncNode(object):
542-
543546
def __init__(self, name):
544-
if name not in _mathops:
547+
from pandas.core.computation.check import (_NUMEXPR_INSTALLED,
548+
_NUMEXPR_VERSION)
549+
if name not in _mathops or (
550+
_NUMEXPR_INSTALLED and
551+
_NUMEXPR_VERSION < LooseVersion('2.6.9') and
552+
name in ('floor', 'ceil')
553+
):
545554
raise ValueError(
546555
"\"{0}\" is not a supported function".format(name))
556+
547557
self.name = name
548558
self.func = getattr(np, name)
549559

pandas/tests/computation/test_eval.py

+34-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import warnings
22
import operator
3+
from distutils.version import LooseVersion
34
from itertools import product
45

56
import pytest
@@ -14,6 +15,7 @@
1415
from pandas.util.testing import makeCustomDataframe as mkdf
1516

1617
from pandas.core.computation import pytables
18+
from pandas.core.computation.check import _NUMEXPR_VERSION
1719
from pandas.core.computation.engines import _engines, NumExprClobberingError
1820
from pandas.core.computation.expr import PythonExprVisitor, PandasExprVisitor
1921
from pandas.core.computation.expressions import (
@@ -32,6 +34,7 @@
3234
assert_produces_warning)
3335
from pandas.compat import PY3, reduce
3436

37+
3538
_series_frame_incompatible = _bool_ops_syms
3639
_scalar_skip = 'in', 'not in'
3740

@@ -54,6 +57,25 @@ def parser(request):
5457
return request.param
5558

5659

60+
@pytest.fixture
61+
def ne_lt_2_6_9():
62+
if _NUMEXPR_INSTALLED and _NUMEXPR_VERSION >= LooseVersion('2.6.9'):
63+
pytest.skip("numexpr is >= 2.6.9")
64+
return 'numexpr'
65+
66+
67+
@pytest.fixture
68+
def unary_fns_for_ne():
69+
if _NUMEXPR_INSTALLED:
70+
if _NUMEXPR_VERSION >= LooseVersion('2.6.9'):
71+
return _unary_math_ops
72+
else:
73+
return tuple(x for x in _unary_math_ops
74+
if x not in ("floor", "ceil"))
75+
else:
76+
pytest.skip("numexpr is not present")
77+
78+
5779
def engine_has_neg_frac(engine):
5880
return _engines[engine].has_neg_frac
5981

@@ -1622,16 +1644,26 @@ def eval(self, *args, **kwargs):
16221644
kwargs['level'] = kwargs.pop('level', 0) + 1
16231645
return pd.eval(*args, **kwargs)
16241646

1625-
def test_unary_functions(self):
1647+
def test_unary_functions(self, unary_fns_for_ne):
16261648
df = DataFrame({'a': np.random.randn(10)})
16271649
a = df.a
1628-
for fn in self.unary_fns:
1650+
1651+
for fn in unary_fns_for_ne:
16291652
expr = "{0}(a)".format(fn)
16301653
got = self.eval(expr)
16311654
with np.errstate(all='ignore'):
16321655
expect = getattr(np, fn)(a)
16331656
tm.assert_series_equal(got, expect, check_names=False)
16341657

1658+
def test_floor_and_ceil_functions_raise_error(self,
1659+
ne_lt_2_6_9,
1660+
unary_fns_for_ne):
1661+
for fn in ('floor', 'ceil'):
1662+
msg = "\"{0}\" is not a supported function".format(fn)
1663+
with pytest.raises(ValueError, match=msg):
1664+
expr = "{0}(100)".format(fn)
1665+
self.eval(expr)
1666+
16351667
def test_binary_functions(self):
16361668
df = DataFrame({'a': np.random.randn(10),
16371669
'b': np.random.randn(10)})

0 commit comments

Comments
 (0)