From 05c9e046ca64fa7ae990d7a1cf15dea947d0a6ec Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Thu, 5 Sep 2019 10:42:46 -0700 Subject: [PATCH 1/2] treat listlikes symmetrically in logical ops --- pandas/core/ops/__init__.py | 28 ++++++++++++++++++--------- pandas/tests/series/test_operators.py | 12 ++++-------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/pandas/core/ops/__init__.py b/pandas/core/ops/__init__.py index a94a4ccff0efe..a01a386bab1cf 100644 --- a/pandas/core/ops/__init__.py +++ b/pandas/core/ops/__init__.py @@ -806,7 +806,13 @@ def na_op(x, y): return result fill_int = lambda x: x.fillna(0) - fill_bool = lambda x: x.fillna(False).astype(bool) + + def fill_bool(x, left=None): + # if `left` is specifically not-boolean, we do not cast to bool + x = x.fillna(False) + if left is None or is_bool_dtype(left.dtype): + x = x.astype(bool) + return x def wrapper(self, other): is_self_int_dtype = is_integer_dtype(self.dtype) @@ -835,17 +841,21 @@ def wrapper(self, other): elif isinstance(other, (ABCSeries, ABCIndexClass)): is_other_int_dtype = is_integer_dtype(other.dtype) - other = other if is_other_int_dtype else fill_bool(other) + other = other if is_other_int_dtype else fill_bool(other, self) - else: - # scalars, list, tuple, np.array - is_other_int_dtype = is_integer_dtype(np.asarray(other).dtype) - if is_list_like(other) and not isinstance(other, np.ndarray): - # TODO: Can we do this before the is_integer_dtype check? - # could the is_integer_dtype check be checking the wrong - # thing? e.g. other = [[0, 1], [2, 3], [4, 5]]? + elif is_list_like(other): + # list, tuple, np.ndarray + if not isinstance(other, np.ndarray): other = construct_1d_object_array_from_listlike(other) + is_other_int_dtype = is_integer_dtype(other.dtype) + other = type(self)(other) + other = other if is_other_int_dtype else fill_bool(other, self) + + else: + # i.e. scalar + is_other_int_dtype = lib.is_integer(other) + # TODO: use extract_array once we handle EA correctly, see GH#27959 ovalues = lib.values_from_object(other) diff --git a/pandas/tests/series/test_operators.py b/pandas/tests/series/test_operators.py index aa44760dcd918..bf725a04de058 100644 --- a/pandas/tests/series/test_operators.py +++ b/pandas/tests/series/test_operators.py @@ -103,11 +103,8 @@ def test_logical_operators_int_dtype_with_float(self): s_0123 & [0.1, 4, 3.14, 2] with pytest.raises(TypeError): s_0123 & np.array([0.1, 4, 3.14, 2]) - - # FIXME: this should be consistent with the list case above - expected = Series([False, True, False, True]) - result = s_0123 & Series([0.1, 4, -3.14, 2]) - assert_series_equal(result, expected) + with pytest.raises(TypeError): + s_0123 & Series([0.1, 4, -3.14, 2]) def test_logical_operators_int_dtype_with_str(self): s_1111 = Series([1] * 4, dtype="int8") @@ -145,9 +142,8 @@ def test_logical_operators_int_dtype_with_object(self): assert_series_equal(result, expected) s_abNd = Series(["a", "b", np.NaN, "d"]) - result = s_0123 & s_abNd - expected = Series([False, True, False, True]) - assert_series_equal(result, expected) + with pytest.raises(TypeError, match="unsupported.* 'int' and 'str'"): + s_0123 & s_abNd def test_logical_operators_bool_dtype_with_int(self): index = list("bca") From a3488841713dc3789cb176806c86c9206d0c307d Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Mon, 9 Sep 2019 14:48:02 -0700 Subject: [PATCH 2/2] add test --- pandas/tests/series/test_operators.py | 35 +++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/pandas/tests/series/test_operators.py b/pandas/tests/series/test_operators.py index bf725a04de058..c2cf91e582c47 100644 --- a/pandas/tests/series/test_operators.py +++ b/pandas/tests/series/test_operators.py @@ -159,6 +159,41 @@ def test_logical_operators_bool_dtype_with_int(self): expected = s_tft assert_series_equal(res, expected) + def test_logical_ops_bool_dtype_with_ndarray(self): + # make sure we operate on ndarray the same as Series + left = pd.Series([True, True, True, False, True]) + right = [True, False, None, True, np.nan] + + expected = pd.Series([True, False, False, False, False]) + result = left & right + tm.assert_series_equal(result, expected) + result = left & np.array(right) + tm.assert_series_equal(result, expected) + result = left & pd.Index(right) + tm.assert_series_equal(result, expected) + result = left & pd.Series(right) + tm.assert_series_equal(result, expected) + + expected = pd.Series([True, True, True, True, True]) + result = left | right + tm.assert_series_equal(result, expected) + result = left | np.array(right) + tm.assert_series_equal(result, expected) + result = left | pd.Index(right) + tm.assert_series_equal(result, expected) + result = left | pd.Series(right) + tm.assert_series_equal(result, expected) + + expected = pd.Series([False, True, True, True, True]) + result = left ^ right + tm.assert_series_equal(result, expected) + result = left ^ np.array(right) + tm.assert_series_equal(result, expected) + result = left ^ pd.Index(right) + tm.assert_series_equal(result, expected) + result = left ^ pd.Series(right) + tm.assert_series_equal(result, expected) + def test_logical_operators_int_dtype_with_bool_dtype_and_reindex(self): # GH#9016: support bitwise op for integer types