Skip to content

Commit f5b262b

Browse files
committed
simplify bool ops closures
1 parent 7cf9a7a commit f5b262b

File tree

3 files changed

+51
-32
lines changed

3 files changed

+51
-32
lines changed

doc/source/whatsnew/v0.23.0.txt

+1
Original file line numberDiff line numberDiff line change
@@ -866,3 +866,4 @@ Other
866866
^^^^^
867867

868868
- Improved error message when attempting to use a Python keyword as an identifier in a ``numexpr`` backed query (:issue:`18221`)
869+
- Boolean operations ``&, |, ^`` between a ``Series`` and an ``Index`` will no longer raise ``TypeError`` (:issue:`19792`)

pandas/core/ops.py

+39-32
Original file line numberDiff line numberDiff line change
@@ -1058,69 +1058,76 @@ def _bool_method_SERIES(cls, op, special):
10581058
Wrapper function for Series arithmetic operations, to avoid
10591059
code duplication.
10601060
"""
1061+
name = _get_op_name(op, special)
10611062

10621063
def na_op(x, y):
10631064
try:
10641065
result = op(x, y)
10651066
except TypeError:
1066-
if isinstance(y, list):
1067-
y = construct_1d_object_array_from_listlike(y)
1068-
1069-
if isinstance(y, (np.ndarray, ABCSeries)):
1070-
if (is_bool_dtype(x.dtype) and is_bool_dtype(y.dtype)):
1071-
result = op(x, y) # when would this be hit?
1072-
else:
1073-
x = _ensure_object(x)
1074-
y = _ensure_object(y)
1075-
result = lib.vec_binop(x, y, op)
1067+
assert not isinstance(y, (list, ABCSeries, pd.Index))
1068+
if isinstance(y, np.ndarray):
1069+
# This next assertion is here because there used to be
1070+
# a branch that specifically caught this case.
1071+
assert not (is_bool_dtype(x) and is_bool_dtype(y))
1072+
x = _ensure_object(x)
1073+
y = _ensure_object(y)
1074+
result = lib.vec_binop(x, y, op)
10761075
else:
10771076
# let null fall thru
1077+
assert is_scalar(y)
10781078
if not isna(y):
10791079
y = bool(y)
10801080
try:
10811081
result = lib.scalar_binop(x, y, op)
10821082
except:
1083-
msg = ("cannot compare a dtyped [{dtype}] array "
1084-
"with a scalar of type [{type}]"
1085-
).format(dtype=x.dtype, type=type(y).__name__)
1086-
raise TypeError(msg)
1083+
raise TypeError("cannot compare a dtyped [{dtype}] array "
1084+
"with a scalar of type [{typ}]"
1085+
.format(dtype=x.dtype,
1086+
typ=type(y).__name__))
10871087

10881088
return result
10891089

1090+
fill_int = lambda x: x.fillna(0)
1091+
fill_bool = lambda x: x.fillna(False).astype(bool)
1092+
10901093
def wrapper(self, other):
10911094
is_self_int_dtype = is_integer_dtype(self.dtype)
10921095

1093-
fill_int = lambda x: x.fillna(0)
1094-
fill_bool = lambda x: x.fillna(False).astype(bool)
1095-
10961096
self, other = _align_method_SERIES(self, other, align_asobject=True)
10971097

1098+
res_name = _get_series_op_result_name(self, other)
1099+
10981100
if isinstance(other, ABCDataFrame):
10991101
# Defer to DataFrame implementation; fail early
11001102
return NotImplemented
11011103

1102-
elif isinstance(other, ABCSeries):
1103-
name = com._maybe_match_name(self, other)
1104+
elif isinstance(other, (ABCSeries, pd.Index)):
11041105
is_other_int_dtype = is_integer_dtype(other.dtype)
11051106
other = fill_int(other) if is_other_int_dtype else fill_bool(other)
1106-
1107-
filler = (fill_int if is_self_int_dtype and is_other_int_dtype
1108-
else fill_bool)
1109-
1110-
res_values = na_op(self.values, other.values)
1111-
unfilled = self._constructor(res_values,
1112-
index=self.index, name=name)
1113-
return filler(unfilled)
1107+
ovalues = other.values
11141108

11151109
else:
11161110
# scalars, list, tuple, np.array
1117-
filler = (fill_int if is_self_int_dtype and
1118-
is_integer_dtype(np.asarray(other)) else fill_bool)
1111+
is_other_int_dtype = is_integer_dtype(np.asarray(other))
1112+
if isinstance(other, list):
1113+
# TODO: Can we do this before the is_integer_dtype check?
1114+
# could the is_integer_dtype check be checking the wrong
1115+
# thing? e.g. other = [[0, 1], [2, 3], [4, 5]]?
1116+
other = construct_1d_object_array_from_listlike(other)
1117+
ovalues = other
11191118

1120-
res_values = na_op(self.values, other)
1121-
unfilled = self._constructor(res_values, index=self.index)
1122-
return filler(unfilled).__finalize__(self)
1119+
filler = (fill_int if is_self_int_dtype and is_other_int_dtype
1120+
else fill_bool)
11231121

1122+
res_values = na_op(self.values, ovalues)
1123+
unfilled = self._constructor(res_values, index=self.index,
1124+
name=res_name)
1125+
result = filler(unfilled)
1126+
if not isinstance(other, ABCSeries):
1127+
result = result.__finalize__(self)
1128+
return result
1129+
1130+
wrapper.__name__ = name
11241131
return wrapper
11251132

11261133

pandas/tests/series/test_operators.py

+11
Original file line numberDiff line numberDiff line change
@@ -1853,6 +1853,17 @@ def test_operators_bitwise_int_series_with_float_series(self):
18531853
result = s_0123 & Series([0.1, 4, -3.14, 2])
18541854
assert_series_equal(result, s_ftft)
18551855

1856+
@pytest.mark.xfail(reason='GH#19792 Series op doesnt support categorical')
1857+
def test_operators_bitwise_with_int_categorical(self):
1858+
# GH#9016: support bitwise op for integer types
1859+
# GH#??? allow for operating with Index
1860+
s_0123 = Series(range(4), dtype='int64').astype('category')
1861+
s_3333 = Series([3] * 4).astype('category')
1862+
1863+
res = s_0123 & pd.Categorical(s_3333)
1864+
expected = Series(range(4), dtype='int64').astype('category')
1865+
assert_series_equal(res, expected)
1866+
18561867
@pytest.mark.parametrize('box', [np.array, pd.Index, pd.Series])
18571868
def test_operators_bitwise_with_int_arraylike(self, box):
18581869
# GH#9016: support bitwise op for integer types

0 commit comments

Comments
 (0)