Skip to content

Commit add16e5

Browse files
committed
Merge pull request pandas-dev#9338 from tvyomkesh/vyom/gh9016_bit_op_weird
GH 9016: Bitwise operation weirdness
2 parents c6c9c0b + 989f445 commit add16e5

File tree

3 files changed

+137
-7
lines changed

3 files changed

+137
-7
lines changed

doc/source/whatsnew/v0.16.0.txt

+29
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,35 @@ Backwards incompatible API changes
103103
The prior style can be achieved with matplotlib's `axhline` or `axvline`
104104
methods (:issue:`9088`).
105105

106+
107+
- ``Series`` now supports bitwise operation for integral types (:issue:`9016`)
108+
109+
Previously even if the input dtypes where integral, the output dtype was coerced to bool.
110+
111+
.. code-block:: python
112+
In [2]: pd.Series([0,1,2,3], list('abcd')) | pd.Series([4,4,4,4], list('abcd'))
113+
Out[2]:
114+
a True
115+
b True
116+
c True
117+
d True
118+
dtype: bool
119+
120+
Now if the input dtypes are integral, the output dtype is also integral and the output
121+
values are the result of the bitwise operation.
122+
123+
.. code-block:: python
124+
125+
In [2]: pd.Series([0,1,2,3], list('abcd')) | pd.Series([4,4,4,4], list('abcd'))
126+
Out[2]:
127+
a 4
128+
b 5
129+
c 6
130+
d 7
131+
dtype: int64
132+
133+
134+
106135
Deprecations
107136
~~~~~~~~~~~~
108137

pandas/core/ops.py

+18-7
Original file line numberDiff line numberDiff line change
@@ -654,20 +654,31 @@ def na_op(x, y):
654654
return result
655655

656656
def wrapper(self, other):
657+
is_self_int_dtype = com.is_integer_dtype(self.dtype)
658+
659+
fill_int = lambda x: x.fillna(0)
660+
fill_bool = lambda x: x.fillna(False).astype(bool)
661+
657662
if isinstance(other, pd.Series):
658663
name = _maybe_match_name(self, other)
664+
other = other.reindex_like(self)
665+
is_other_int_dtype = com.is_integer_dtype(other.dtype)
666+
other = fill_int(other) if is_other_int_dtype else fill_bool(other)
659667

660-
other = other.reindex_like(self).fillna(False).astype(bool)
661-
return self._constructor(na_op(self.values, other.values),
668+
filler = fill_int if is_self_int_dtype and is_other_int_dtype else fill_bool
669+
return filler(self._constructor(na_op(self.values, other.values),
662670
index=self.index,
663-
name=name).fillna(False).astype(bool)
671+
name=name))
672+
664673
elif isinstance(other, pd.DataFrame):
665674
return NotImplemented
675+
666676
else:
667-
# scalars
668-
res = self._constructor(na_op(self.values, other),
669-
index=self.index).fillna(False)
670-
return res.astype(bool).__finalize__(self)
677+
# scalars, list, tuple, np.array
678+
filler = fill_int if is_self_int_dtype and com.is_integer_dtype(np.asarray(other)) else fill_bool
679+
return filler(self._constructor(na_op(self.values, other),
680+
index=self.index)).__finalize__(self)
681+
671682
return wrapper
672683

673684

pandas/tests/test_series.py

+90
Original file line numberDiff line numberDiff line change
@@ -3790,6 +3790,96 @@ def test_comparison_label_based(self):
37903790
for v in [np.nan]:
37913791
self.assertRaises(TypeError, lambda : t & v)
37923792

3793+
def test_operators_bitwise(self):
3794+
# GH 9016: support bitwise op for integer types
3795+
index = list('bca')
3796+
3797+
s_tft = Series([True, False, True], index=index)
3798+
s_fff = Series([False, False, False], index=index)
3799+
s_tff = Series([True, False, False], index=index)
3800+
s_empty = Series([])
3801+
s_0101 = Series([0,1,0,1])
3802+
s_0123 = Series(range(4))
3803+
s_3333 = Series([3] * 4)
3804+
s_4444 = Series([4] * 4)
3805+
3806+
res = s_tft & s_empty
3807+
expected = s_fff
3808+
assert_series_equal(res, expected)
3809+
3810+
res = s_tft | s_empty
3811+
expected = s_tft
3812+
assert_series_equal(res, expected)
3813+
3814+
res = s_0123 & s_3333
3815+
expected = Series(range(4))
3816+
assert_series_equal(res, expected)
3817+
3818+
res = s_0123 | s_4444
3819+
expected = Series(range(4, 8))
3820+
assert_series_equal(res, expected)
3821+
3822+
s_a0b1c0 = Series([1], list('b'))
3823+
3824+
res = s_tft & s_a0b1c0
3825+
expected = s_tff
3826+
assert_series_equal(res, expected)
3827+
3828+
res = s_tft | s_a0b1c0
3829+
expected = s_tft
3830+
assert_series_equal(res, expected)
3831+
3832+
n0 = 0
3833+
res = s_tft & n0
3834+
expected = s_fff
3835+
assert_series_equal(res, expected)
3836+
3837+
res = s_0123 & n0
3838+
expected = Series([0] * 4)
3839+
assert_series_equal(res, expected)
3840+
3841+
n1 = 1
3842+
res = s_tft & n1
3843+
expected = s_tft
3844+
assert_series_equal(res, expected)
3845+
3846+
res = s_0123 & n1
3847+
expected = Series([0, 1, 0, 1])
3848+
assert_series_equal(res, expected)
3849+
3850+
s_1111 = Series([1]*4, dtype='int8')
3851+
res = s_0123 & s_1111
3852+
expected = Series([0, 1, 0, 1], dtype='int64')
3853+
assert_series_equal(res, expected)
3854+
3855+
res = s_0123.astype(np.int16) | s_1111.astype(np.int32)
3856+
expected = Series([1, 1, 3, 3], dtype='int32')
3857+
assert_series_equal(res, expected)
3858+
3859+
self.assertRaises(TypeError, lambda: s_1111 & 'a')
3860+
self.assertRaises(TypeError, lambda: s_1111 & ['a','b','c','d'])
3861+
self.assertRaises(TypeError, lambda: s_0123 & np.NaN)
3862+
self.assertRaises(TypeError, lambda: s_0123 & 3.14)
3863+
self.assertRaises(TypeError, lambda: s_0123 & [0.1, 4, 3.14, 2])
3864+
3865+
# s_0123 will be all false now because of reindexing like s_tft
3866+
assert_series_equal(s_tft & s_0123, Series([False] * 3, list('bca')))
3867+
# s_tft will be all false now because of reindexing like s_0123
3868+
assert_series_equal(s_0123 & s_tft, Series([False] * 4))
3869+
assert_series_equal(s_0123 & False, Series([False] * 4))
3870+
assert_series_equal(s_0123 ^ False, Series([False, True, True, True]))
3871+
assert_series_equal(s_0123 & [False], Series([False] * 4))
3872+
assert_series_equal(s_0123 & (False), Series([False] * 4))
3873+
assert_series_equal(s_0123 & Series([False, np.NaN, False, False]), Series([False] * 4))
3874+
3875+
s_ftft = Series([False, True, False, True])
3876+
assert_series_equal(s_0123 & Series([0.1, 4, -3.14, 2]), s_ftft)
3877+
3878+
s_abNd = Series(['a','b',np.NaN,'d'])
3879+
res = s_0123 & s_abNd
3880+
expected = s_ftft
3881+
assert_series_equal(res, expected)
3882+
37933883
def test_between(self):
37943884
s = Series(bdate_range('1/1/2000', periods=20).asobject)
37953885
s[::2] = np.nan

0 commit comments

Comments
 (0)