From 7490f47b1d0d1e8ecb8284dc842e9242cf24fa4e Mon Sep 17 00:00:00 2001 From: Phillip Cloud Date: Wed, 30 Apr 2014 16:56:36 -0400 Subject: [PATCH 1/2] ERR: more informative error message on bool arith op failures --- pandas/computation/expressions.py | 5 ++++- pandas/core/ops.py | 34 +++++++++++++++---------------- pandas/tests/test_expressions.py | 12 +++++++++++ 3 files changed, 33 insertions(+), 18 deletions(-) diff --git a/pandas/computation/expressions.py b/pandas/computation/expressions.py index 128aa5bf2b511..bfb29e0d4fa10 100644 --- a/pandas/computation/expressions.py +++ b/pandas/computation/expressions.py @@ -158,7 +158,10 @@ def _has_bool_dtype(x): try: return x.dtype == bool except AttributeError: - return 'bool' in x.blocks + try: + return 'bool' in x.blocks + except AttributeError: + return isinstance(x, (bool, np.bool_)) def _bool_arith_check(op_str, a, b, not_allowed=frozenset(('+', '*', '-', '/', diff --git a/pandas/core/ops.py b/pandas/core/ops.py index d4e756371001b..0b044f727b6a6 100644 --- a/pandas/core/ops.py +++ b/pandas/core/ops.py @@ -61,25 +61,25 @@ def names(x): default_axis=default_axis, fill_zeros=np.inf), # Causes a floating point exception in the tests when numexpr # enabled, so for now no speedup - mod=arith_method(operator.mod, names('mod'), default_axis=default_axis, - fill_zeros=np.nan), + mod=arith_method(operator.mod, names('mod'), op('%'), + default_axis=default_axis, fill_zeros=np.nan), pow=arith_method(operator.pow, names('pow'), op('**'), default_axis=default_axis), # not entirely sure why this is necessary, but previously was included # so it's here to maintain compatibility - rmul=arith_method(operator.mul, names('rmul'), + rmul=arith_method(operator.mul, names('rmul'), op('*'), default_axis=default_axis), - rsub=arith_method(lambda x, y: y - x, names('rsub'), + rsub=arith_method(lambda x, y: y - x, names('rsub'), op('-'), default_axis=default_axis), rtruediv=arith_method(lambda x, y: operator.truediv(y, x), - names('rtruediv'), truediv=True, + names('rtruediv'), op('/'), truediv=True, fill_zeros=np.inf, default_axis=default_axis), rfloordiv=arith_method(lambda x, y: operator.floordiv(y, x), - names('rfloordiv'), default_axis=default_axis, - fill_zeros=np.inf), - rpow=arith_method(lambda x, y: y ** x, names('rpow'), + names('rfloordiv'), op('//'), + default_axis=default_axis, fill_zeros=np.inf), + rpow=arith_method(lambda x, y: y ** x, names('rpow'), op('**'), default_axis=default_axis), - rmod=arith_method(lambda x, y: y % x, names('rmod'), + rmod=arith_method(lambda x, y: y % x, names('rmod'), op('%'), default_axis=default_axis), ) new_methods['div'] = new_methods['truediv'] @@ -100,11 +100,11 @@ def names(x): and_=bool_method(operator.and_, names('and_'), op('&')), or_=bool_method(operator.or_, names('or_'), op('|')), # For some reason ``^`` wasn't used in original. - xor=bool_method(operator.xor, names('xor')), + xor=bool_method(operator.xor, names('xor'), op('^')), rand_=bool_method(lambda x, y: operator.and_(y, x), - names('rand_')), - ror_=bool_method(lambda x, y: operator.or_(y, x), names('ror_')), - rxor=bool_method(lambda x, y: operator.xor(y, x), names('rxor')) + names('rand_'), op('&')), + ror_=bool_method(lambda x, y: operator.or_(y, x), names('ror_'), op('|')), + rxor=bool_method(lambda x, y: operator.xor(y, x), names('rxor'), op('^')) )) new_methods = dict((names(k), v) for k, v in new_methods.items()) @@ -431,7 +431,7 @@ def maybe_convert_for_time_op(cls, left, right, name): return cls(left, right, name) -def _arith_method_SERIES(op, name, str_rep=None, fill_zeros=None, +def _arith_method_SERIES(op, name, str_rep, fill_zeros=None, default_axis=None, **eval_kwargs): """ Wrapper function for Series arithmetic operations, to avoid @@ -506,7 +506,7 @@ def wrapper(left, right, name=name): return wrapper -def _comp_method_SERIES(op, name, str_rep=None, masker=False): +def _comp_method_SERIES(op, name, str_rep, masker=False): """ Wrapper function for Series arithmetic operations, to avoid code duplication. @@ -578,7 +578,7 @@ def wrapper(self, other): return wrapper -def _bool_method_SERIES(op, name, str_rep=None): +def _bool_method_SERIES(op, name, str_rep): """ Wrapper function for Series arithmetic operations, to avoid code duplication. @@ -647,7 +647,7 @@ def _radd_compat(left, right): return output -def _flex_method_SERIES(op, name, str_rep=None, default_axis=None, +def _flex_method_SERIES(op, name, str_rep, default_axis=None, fill_zeros=None, **eval_kwargs): doc = """ Binary operator %s with support to substitute a fill_value for missing data diff --git a/pandas/tests/test_expressions.py b/pandas/tests/test_expressions.py index fdea275b7e040..09fc991dc1726 100644 --- a/pandas/tests/test_expressions.py +++ b/pandas/tests/test_expressions.py @@ -357,6 +357,18 @@ def test_bool_ops_raise_on_arithmetic(self): with tm.assertRaisesRegexp(NotImplementedError, err_msg): f(df.a, df.b) + with tm.assertRaisesRegexp(NotImplementedError, err_msg): + f(df.a, True) + + with tm.assertRaisesRegexp(NotImplementedError, err_msg): + f(False, df.a) + + with tm.assertRaisesRegexp(TypeError, err_msg): + f(False, df) + + with tm.assertRaisesRegexp(TypeError, err_msg): + f(df, True) + if __name__ == '__main__': import nose From 26890ee0ac699961d7c47efbd1342afa009b5335 Mon Sep 17 00:00:00 2001 From: Phillip Cloud Date: Fri, 2 May 2014 18:59:04 -0400 Subject: [PATCH 2/2] DOC: add release note and version doc/example --- doc/source/release.rst | 2 ++ doc/source/v0.14.0.txt | 12 ++++++++++++ pandas/core/ops.py | 2 +- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/doc/source/release.rst b/doc/source/release.rst index b8e5b31bd873c..86407ed19a772 100644 --- a/doc/source/release.rst +++ b/doc/source/release.rst @@ -206,6 +206,8 @@ API Changes - Added ``factorize`` functions to ``Index`` and ``Series`` to get indexer and unique values (:issue:`7090`) - :meth:`DataFrame.describe` on a DataFrame with a mix of Timestamp and string like objects returns a different Index (:issue:`7088`). Previously the index was unintentionally sorted. +- arithmetic operations with **only** ``bool`` dtypes now raise an error + (:issue:`7011`, :issue:`6762`, :issue:`7015`) Deprecations ~~~~~~~~~~~~ diff --git a/doc/source/v0.14.0.txt b/doc/source/v0.14.0.txt index 4c099c627e6e5..b5b261d4728be 100644 --- a/doc/source/v0.14.0.txt +++ b/doc/source/v0.14.0.txt @@ -228,6 +228,18 @@ Display Changes length of the series (:issue:`7101`) - Fixed a bug in the HTML repr of a truncated Series or DataFrame not showing the class name with the `large_repr` set to 'info' (:issue:`7105`) +- arithmetic operations with **only** ``bool`` dtypes now raise an error + (:issue:`7011`, :issue:`6762`, :issue:`7015`) + + .. code-block:: python + + x = pd.Series(np.random.rand(10) > 0.5) + y = True + x * y + + # this now raises for arith ops like ``+``, ``*``, etc. + NotImplementedError: operator '*' not implemented for bool dtypes + .. _whatsnew_0140.groupby: diff --git a/pandas/core/ops.py b/pandas/core/ops.py index 0b044f727b6a6..a52c1034e63cb 100644 --- a/pandas/core/ops.py +++ b/pandas/core/ops.py @@ -61,7 +61,7 @@ def names(x): default_axis=default_axis, fill_zeros=np.inf), # Causes a floating point exception in the tests when numexpr # enabled, so for now no speedup - mod=arith_method(operator.mod, names('mod'), op('%'), + mod=arith_method(operator.mod, names('mod'), None, default_axis=default_axis, fill_zeros=np.nan), pow=arith_method(operator.pow, names('pow'), op('**'), default_axis=default_axis),