Skip to content

Commit f7e995c

Browse files
committed
Merge pull request #7015 from cpcloud/bool-raises-on-exprs-err-msg
ERR: more informative error message on bool arith op failures
2 parents d7b2fa7 + 26890ee commit f7e995c

File tree

5 files changed

+47
-18
lines changed

5 files changed

+47
-18
lines changed

doc/source/release.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,8 @@ API Changes
206206
- Added ``factorize`` functions to ``Index`` and ``Series`` to get indexer and unique values (:issue:`7090`)
207207
- :meth:`DataFrame.describe` on a DataFrame with a mix of Timestamp and string like objects
208208
returns a different Index (:issue:`7088`). Previously the index was unintentionally sorted.
209+
- arithmetic operations with **only** ``bool`` dtypes now raise an error
210+
(:issue:`7011`, :issue:`6762`, :issue:`7015`)
209211

210212
Deprecations
211213
~~~~~~~~~~~~

doc/source/v0.14.0.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,18 @@ Display Changes
228228
length of the series (:issue:`7101`)
229229
- Fixed a bug in the HTML repr of a truncated Series or DataFrame not showing the class name with the
230230
`large_repr` set to 'info' (:issue:`7105`)
231+
- arithmetic operations with **only** ``bool`` dtypes now raise an error
232+
(:issue:`7011`, :issue:`6762`, :issue:`7015`)
233+
234+
.. code-block:: python
235+
236+
x = pd.Series(np.random.rand(10) > 0.5)
237+
y = True
238+
x * y
239+
240+
# this now raises for arith ops like ``+``, ``*``, etc.
241+
NotImplementedError: operator '*' not implemented for bool dtypes
242+
231243

232244
.. _whatsnew_0140.groupby:
233245

pandas/computation/expressions.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,10 @@ def _has_bool_dtype(x):
158158
try:
159159
return x.dtype == bool
160160
except AttributeError:
161-
return 'bool' in x.blocks
161+
try:
162+
return 'bool' in x.blocks
163+
except AttributeError:
164+
return isinstance(x, (bool, np.bool_))
162165

163166

164167
def _bool_arith_check(op_str, a, b, not_allowed=frozenset(('+', '*', '-', '/',

pandas/core/ops.py

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -61,25 +61,25 @@ def names(x):
6161
default_axis=default_axis, fill_zeros=np.inf),
6262
# Causes a floating point exception in the tests when numexpr
6363
# enabled, so for now no speedup
64-
mod=arith_method(operator.mod, names('mod'), default_axis=default_axis,
65-
fill_zeros=np.nan),
64+
mod=arith_method(operator.mod, names('mod'), None,
65+
default_axis=default_axis, fill_zeros=np.nan),
6666
pow=arith_method(operator.pow, names('pow'), op('**'),
6767
default_axis=default_axis),
6868
# not entirely sure why this is necessary, but previously was included
6969
# so it's here to maintain compatibility
70-
rmul=arith_method(operator.mul, names('rmul'),
70+
rmul=arith_method(operator.mul, names('rmul'), op('*'),
7171
default_axis=default_axis),
72-
rsub=arith_method(lambda x, y: y - x, names('rsub'),
72+
rsub=arith_method(lambda x, y: y - x, names('rsub'), op('-'),
7373
default_axis=default_axis),
7474
rtruediv=arith_method(lambda x, y: operator.truediv(y, x),
75-
names('rtruediv'), truediv=True,
75+
names('rtruediv'), op('/'), truediv=True,
7676
fill_zeros=np.inf, default_axis=default_axis),
7777
rfloordiv=arith_method(lambda x, y: operator.floordiv(y, x),
78-
names('rfloordiv'), default_axis=default_axis,
79-
fill_zeros=np.inf),
80-
rpow=arith_method(lambda x, y: y ** x, names('rpow'),
78+
names('rfloordiv'), op('//'),
79+
default_axis=default_axis, fill_zeros=np.inf),
80+
rpow=arith_method(lambda x, y: y ** x, names('rpow'), op('**'),
8181
default_axis=default_axis),
82-
rmod=arith_method(lambda x, y: y % x, names('rmod'),
82+
rmod=arith_method(lambda x, y: y % x, names('rmod'), op('%'),
8383
default_axis=default_axis),
8484
)
8585
new_methods['div'] = new_methods['truediv']
@@ -100,11 +100,11 @@ def names(x):
100100
and_=bool_method(operator.and_, names('and_'), op('&')),
101101
or_=bool_method(operator.or_, names('or_'), op('|')),
102102
# For some reason ``^`` wasn't used in original.
103-
xor=bool_method(operator.xor, names('xor')),
103+
xor=bool_method(operator.xor, names('xor'), op('^')),
104104
rand_=bool_method(lambda x, y: operator.and_(y, x),
105-
names('rand_')),
106-
ror_=bool_method(lambda x, y: operator.or_(y, x), names('ror_')),
107-
rxor=bool_method(lambda x, y: operator.xor(y, x), names('rxor'))
105+
names('rand_'), op('&')),
106+
ror_=bool_method(lambda x, y: operator.or_(y, x), names('ror_'), op('|')),
107+
rxor=bool_method(lambda x, y: operator.xor(y, x), names('rxor'), op('^'))
108108
))
109109

110110
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):
431431
return cls(left, right, name)
432432

433433

434-
def _arith_method_SERIES(op, name, str_rep=None, fill_zeros=None,
434+
def _arith_method_SERIES(op, name, str_rep, fill_zeros=None,
435435
default_axis=None, **eval_kwargs):
436436
"""
437437
Wrapper function for Series arithmetic operations, to avoid
@@ -506,7 +506,7 @@ def wrapper(left, right, name=name):
506506
return wrapper
507507

508508

509-
def _comp_method_SERIES(op, name, str_rep=None, masker=False):
509+
def _comp_method_SERIES(op, name, str_rep, masker=False):
510510
"""
511511
Wrapper function for Series arithmetic operations, to avoid
512512
code duplication.
@@ -578,7 +578,7 @@ def wrapper(self, other):
578578
return wrapper
579579

580580

581-
def _bool_method_SERIES(op, name, str_rep=None):
581+
def _bool_method_SERIES(op, name, str_rep):
582582
"""
583583
Wrapper function for Series arithmetic operations, to avoid
584584
code duplication.
@@ -647,7 +647,7 @@ def _radd_compat(left, right):
647647
return output
648648

649649

650-
def _flex_method_SERIES(op, name, str_rep=None, default_axis=None,
650+
def _flex_method_SERIES(op, name, str_rep, default_axis=None,
651651
fill_zeros=None, **eval_kwargs):
652652
doc = """
653653
Binary operator %s with support to substitute a fill_value for missing data

pandas/tests/test_expressions.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,18 @@ def test_bool_ops_raise_on_arithmetic(self):
357357
with tm.assertRaisesRegexp(NotImplementedError, err_msg):
358358
f(df.a, df.b)
359359

360+
with tm.assertRaisesRegexp(NotImplementedError, err_msg):
361+
f(df.a, True)
362+
363+
with tm.assertRaisesRegexp(NotImplementedError, err_msg):
364+
f(False, df.a)
365+
366+
with tm.assertRaisesRegexp(TypeError, err_msg):
367+
f(False, df)
368+
369+
with tm.assertRaisesRegexp(TypeError, err_msg):
370+
f(df, True)
371+
360372

361373
if __name__ == '__main__':
362374
import nose

0 commit comments

Comments
 (0)