Skip to content

ENH: Use numexpr for all arithmetic operators + refactor arithmetic operations #4051

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

66 changes: 58 additions & 8 deletions doc/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -248,12 +248,30 @@ Binary operator functions
:toctree: generated/

Series.add
Series.div
Series.mul
Series.sub
Series.mul
Series.div
Series.truediv
Series.floordiv
Series.mod
Series.pow
Series.radd
Series.rsub
Series.rmul
Series.rdiv
Series.rtruediv
Series.rfloordiv
Series.rmod
Series.rpow
Series.combine
Series.combine_first
Series.round
Series.lt
Series.gt
Series.le
Series.ge
Series.ne
Series.eq

Function application, GroupBy
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -451,13 +469,27 @@ Binary operator functions
:toctree: generated/

DataFrame.add
DataFrame.div
DataFrame.mul
DataFrame.sub
DataFrame.mul
DataFrame.div
DataFrame.truediv
DataFrame.floordiv
DataFrame.mod
DataFrame.pow
DataFrame.radd
DataFrame.rdiv
DataFrame.rmul
DataFrame.rsub
DataFrame.rmul
DataFrame.rdiv
DataFrame.rtruediv
DataFrame.rfloordiv
DataFrame.rmod
DataFrame.rpow
DataFrame.lt
DataFrame.gt
DataFrame.le
DataFrame.ge
DataFrame.ne
DataFrame.eq
DataFrame.combine
DataFrame.combineAdd
DataFrame.combine_first
Expand Down Expand Up @@ -680,9 +712,27 @@ Binary operator functions
:toctree: generated/

Panel.add
Panel.div
Panel.mul
Panel.sub
Panel.mul
Panel.div
Panel.truediv
Panel.floordiv
Panel.mod
Panel.pow
Panel.radd
Panel.rsub
Panel.rmul
Panel.rdiv
Panel.rtruediv
Panel.rfloordiv
Panel.rmod
Panel.rpow
Panel.lt
Panel.gt
Panel.le
Panel.ge
Panel.ne
Panel.eq

Function application, GroupBy
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down
3 changes: 3 additions & 0 deletions doc/source/release.rst
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ pandas 0.12
- Access to historical Google Finance data in pandas.io.data (:issue:`3814`)
- DataFrame plotting methods can sample column colors from a Matplotlib
colormap via the ``colormap`` keyword. (:issue:`3860`)
- All non-Index NDFrames (``Series``, ``DataFrame``, ``Panel``, ``Panel4D``,
``SparsePanel``, etc.), now support the entire set of arithmetic operators
and arithmetic flex methods (add, sub, mul, pow, etc.). (:issue:`3765`)

**Improvements to existing features**

Expand Down
4 changes: 4 additions & 0 deletions doc/source/v0.12.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ API changes
p / p
p / 0

- All non-Index NDFrames (``Series``, ``DataFrame``, ``Panel``, ``Panel4D``,
``SparsePanel``, etc.), now support the entire set of arithmetic operators
and arithmetic flex methods (add, sub, mul, pow, etc.). (:issue:`3765`)

- Add ``squeeze`` keyword to ``groupby`` to allow reduction from
DataFrame -> Series if groups are unique. This is a Regression from 0.10.1.
We are reverting back to the prior behavior. This means groupby will return the
Expand Down
26 changes: 25 additions & 1 deletion pandas/core/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import itertools
import re
from datetime import datetime
import types

from numpy.lib.format import read_array, write_array
import numpy as np
Expand Down Expand Up @@ -38,6 +38,30 @@ class AmbiguousIndexError(PandasError, KeyError):
pass


def bind_method(cls, name, func):
"""Bind a method to class, python 2 and python 3 compatible.

Parameters
----------

cls : type
class to receive bound method
name : basestring
name of method on class instance
func : function
function to be bound as method


Returns
-------
None
"""
# only python 2 has bound/unbound method issue
if not py3compat.PY3:
setattr(cls, name, types.MethodType(func, None, cls))
else:
setattr(cls, name, func)

_POSSIBLY_CAST_DTYPES = set([ np.dtype(t) for t in ['M8[ns]','m8[ns]','O','int8','uint8','int16','uint16','int32','uint32','int64','uint64'] ])
_NS_DTYPE = np.dtype('M8[ns]')
_TD_DTYPE = np.dtype('m8[ns]')
Expand Down
70 changes: 45 additions & 25 deletions pandas/core/expressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
except ImportError: # pragma: no cover
_NUMEXPR_INSTALLED = False

_TEST_MODE = None
_TEST_RESULT = None
_USE_NUMEXPR = _NUMEXPR_INSTALLED
_evaluate = None
_where = None
Expand Down Expand Up @@ -53,12 +55,14 @@ def set_numexpr_threads(n = None):

def _evaluate_standard(op, op_str, a, b, raise_on_error=True, **eval_kwargs):
""" standard evaluation """
if _TEST_MODE:
_store_test_result(False)
return op(a,b)

def _can_use_numexpr(op, op_str, a, b, dtype_check):
""" return a boolean if we WILL be using numexpr """
if op_str is not None:

# required min elements (otherwise we are adding overhead)
if np.prod(a.shape) > _MIN_ELEMENTS:

Expand All @@ -81,31 +85,30 @@ def _can_use_numexpr(op, op_str, a, b, dtype_check):

def _evaluate_numexpr(op, op_str, a, b, raise_on_error = False, **eval_kwargs):
result = None

if _can_use_numexpr(op, op_str, a, b, 'evaluate'):
try:
a_value, b_value = a, b
if hasattr(a_value,'values'):
a_value = a_value.values
if hasattr(b_value,'values'):
b_value = b_value.values
result = ne.evaluate('a_value %s b_value' % op_str,
local_dict={ 'a_value' : a_value,
'b_value' : b_value },
a_value = getattr(a, "values", a)
b_value = getattr(b, "values", b)
result = ne.evaluate('a_value %s b_value' % op_str,
local_dict={ 'a_value' : a_value,
'b_value' : b_value },
casting='safe', **eval_kwargs)
except (ValueError), detail:
except ValueError as detail:
if 'unknown type object' in str(detail):
pass
except (Exception), detail:
except Exception as detail:
if raise_on_error:
raise TypeError(str(detail))

if _TEST_MODE:
_store_test_result(result is not None)

if result is None:
result = _evaluate_standard(op,op_str,a,b,raise_on_error)

return result

def _where_standard(cond, a, b, raise_on_error=True):
def _where_standard(cond, a, b, raise_on_error=True):
return np.where(cond, a, b)

def _where_numexpr(cond, a, b, raise_on_error = False):
Expand All @@ -114,22 +117,18 @@ def _where_numexpr(cond, a, b, raise_on_error = False):
if _can_use_numexpr(None, 'where', a, b, 'where'):

try:
cond_value, a_value, b_value = cond, a, b
if hasattr(cond_value,'values'):
cond_value = cond_value.values
if hasattr(a_value,'values'):
a_value = a_value.values
if hasattr(b_value,'values'):
b_value = b_value.values
cond_value = getattr(cond, 'values', cond)
a_value = getattr(a, 'values', a)
b_value = getattr(b, 'values', b)
result = ne.evaluate('where(cond_value,a_value,b_value)',
local_dict={ 'cond_value' : cond_value,
'a_value' : a_value,
'b_value' : b_value },
'a_value' : a_value,
'b_value' : b_value },
casting='safe')
except (ValueError), detail:
except ValueError as detail:
if 'unknown type object' in str(detail):
pass
except (Exception), detail:
except Exception as detail:
if raise_on_error:
raise TypeError(str(detail))

Expand All @@ -156,7 +155,6 @@ def evaluate(op, op_str, a, b, raise_on_error=False, use_numexpr=True, **eval_kw
otherwise evaluate the op with and return the results
use_numexpr : whether to try to use numexpr (default True)
"""

if use_numexpr:
return _evaluate(op, op_str, a, b, raise_on_error=raise_on_error, **eval_kwargs)
return _evaluate_standard(op, op_str, a, b, raise_on_error=raise_on_error)
Expand All @@ -178,3 +176,25 @@ def where(cond, a, b, raise_on_error=False, use_numexpr=True):
if use_numexpr:
return _where(cond, a, b, raise_on_error=raise_on_error)
return _where_standard(cond, a, b, raise_on_error=raise_on_error)

def set_test_mode(v = True):
"""
Keeps track of whether numexpr was used. Stores an additional ``True`` for
every successful use of evaluate with numexpr since the last
``get_test_result``
"""
global _TEST_MODE, _TEST_RESULT
_TEST_MODE = v
_TEST_RESULT = []

def _store_test_result(used_numexpr):
global _TEST_RESULT
if used_numexpr:
_TEST_RESULT.append(used_numexpr)

def get_test_result():
"""get test result and reset test_results"""
global _TEST_RESULT
res = _TEST_RESULT
_TEST_RESULT = []
return res
Loading