diff --git a/pandas/core/ops/__init__.py b/pandas/core/ops/__init__.py index 7e03b9544ee72..80661cc214fd6 100644 --- a/pandas/core/ops/__init__.py +++ b/pandas/core/ops/__init__.py @@ -791,28 +791,33 @@ def wrapper(self, other): self, other = _align_method_SERIES(self, other, align_asobject=True) res_name = get_op_result_name(self, other) + # TODO: shouldn't we be applying finalize whenever + # not isinstance(other, ABCSeries)? + finalizer = ( + lambda x: x.__finalize__(self) + if not isinstance(other, (ABCSeries, ABCIndexClass)) + else x + ) + if isinstance(other, ABCDataFrame): # Defer to DataFrame implementation; fail early return NotImplemented elif isinstance(other, (ABCSeries, ABCIndexClass)): is_other_int_dtype = is_integer_dtype(other.dtype) - other = fill_int(other) if is_other_int_dtype else fill_bool(other) - - ovalues = other.values - finalizer = lambda x: x + other = other if is_other_int_dtype else fill_bool(other) else: # scalars, list, tuple, np.array - is_other_int_dtype = is_integer_dtype(np.asarray(other)) + 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]]? other = construct_1d_object_array_from_listlike(other) - ovalues = other - finalizer = lambda x: x.__finalize__(self) + # TODO: use extract_array once we handle EA correctly, see GH#27959 + ovalues = lib.values_from_object(other) # For int vs int `^`, `|`, `&` are bitwise operators and return # integer dtypes. Otherwise these are boolean ops diff --git a/pandas/tests/series/test_operators.py b/pandas/tests/series/test_operators.py index 062c07cb6242a..aa44760dcd918 100644 --- a/pandas/tests/series/test_operators.py +++ b/pandas/tests/series/test_operators.py @@ -36,22 +36,14 @@ def test_bool_operators_with_nas(self, bool_op): expected[mask] = False assert_series_equal(result, expected) - def test_operators_bitwise(self): + def test_logical_operators_bool_dtype_with_empty(self): # GH#9016: support bitwise op for integer types index = list("bca") s_tft = Series([True, False, True], index=index) s_fff = Series([False, False, False], index=index) - s_tff = Series([True, False, False], index=index) s_empty = Series([]) - # TODO: unused - # s_0101 = Series([0, 1, 0, 1]) - - s_0123 = Series(range(4), dtype="int64") - s_3333 = Series([3] * 4) - s_4444 = Series([4] * 4) - res = s_tft & s_empty expected = s_fff assert_series_equal(res, expected) @@ -60,6 +52,16 @@ def test_operators_bitwise(self): expected = s_tft assert_series_equal(res, expected) + def test_logical_operators_int_dtype_with_int_dtype(self): + # GH#9016: support bitwise op for integer types + + # TODO: unused + # s_0101 = Series([0, 1, 0, 1]) + + s_0123 = Series(range(4), dtype="int64") + s_3333 = Series([3] * 4) + s_4444 = Series([4] * 4) + res = s_0123 & s_3333 expected = Series(range(4), dtype="int64") assert_series_equal(res, expected) @@ -68,76 +70,129 @@ def test_operators_bitwise(self): expected = Series(range(4, 8), dtype="int64") assert_series_equal(res, expected) - s_a0b1c0 = Series([1], list("b")) - - res = s_tft & s_a0b1c0 - expected = s_tff.reindex(list("abc")) + s_1111 = Series([1] * 4, dtype="int8") + res = s_0123 & s_1111 + expected = Series([0, 1, 0, 1], dtype="int64") assert_series_equal(res, expected) - res = s_tft | s_a0b1c0 - expected = s_tft.reindex(list("abc")) + res = s_0123.astype(np.int16) | s_1111.astype(np.int32) + expected = Series([1, 1, 3, 3], dtype="int32") assert_series_equal(res, expected) - n0 = 0 - res = s_tft & n0 - expected = s_fff - assert_series_equal(res, expected) + def test_logical_operators_int_dtype_with_int_scalar(self): + # GH#9016: support bitwise op for integer types + s_0123 = Series(range(4), dtype="int64") - res = s_0123 & n0 + res = s_0123 & 0 expected = Series([0] * 4) assert_series_equal(res, expected) - n1 = 1 - res = s_tft & n1 - expected = s_tft - assert_series_equal(res, expected) - - res = s_0123 & n1 + res = s_0123 & 1 expected = Series([0, 1, 0, 1]) assert_series_equal(res, expected) - s_1111 = Series([1] * 4, dtype="int8") - res = s_0123 & s_1111 - expected = Series([0, 1, 0, 1], dtype="int64") - assert_series_equal(res, expected) - - res = s_0123.astype(np.int16) | s_1111.astype(np.int32) - expected = Series([1, 1, 3, 3], dtype="int32") - assert_series_equal(res, expected) + def test_logical_operators_int_dtype_with_float(self): + # GH#9016: support bitwise op for integer types + s_0123 = Series(range(4), dtype="int64") - with pytest.raises(TypeError): - s_1111 & "a" - with pytest.raises(TypeError): - s_1111 & ["a", "b", "c", "d"] with pytest.raises(TypeError): s_0123 & np.NaN with pytest.raises(TypeError): s_0123 & 3.14 with pytest.raises(TypeError): s_0123 & [0.1, 4, 3.14, 2] + with pytest.raises(TypeError): + s_0123 & np.array([0.1, 4, 3.14, 2]) - # s_0123 will be all false now because of reindexing like s_tft - exp = Series([False] * 7, index=[0, 1, 2, 3, "a", "b", "c"]) - assert_series_equal(s_tft & s_0123, exp) - - # s_tft will be all false now because of reindexing like s_0123 - exp = Series([False] * 7, index=[0, 1, 2, 3, "a", "b", "c"]) - assert_series_equal(s_0123 & s_tft, exp) - - assert_series_equal(s_0123 & False, Series([False] * 4)) - assert_series_equal(s_0123 ^ False, Series([False, True, True, True])) - assert_series_equal(s_0123 & [False], Series([False] * 4)) - assert_series_equal(s_0123 & (False), Series([False] * 4)) - assert_series_equal( - s_0123 & Series([False, np.NaN, False, False]), Series([False] * 4) - ) + # 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) + + def test_logical_operators_int_dtype_with_str(self): + s_1111 = Series([1] * 4, dtype="int8") + + with pytest.raises(TypeError): + s_1111 & "a" + with pytest.raises(TypeError): + s_1111 & ["a", "b", "c", "d"] + + def test_logical_operators_int_dtype_with_bool(self): + # GH#9016: support bitwise op for integer types + s_0123 = Series(range(4), dtype="int64") + + expected = Series([False] * 4) + + result = s_0123 & False + assert_series_equal(result, expected) + + result = s_0123 & [False] + assert_series_equal(result, expected) + + result = s_0123 & (False,) + assert_series_equal(result, expected) - s_ftft = Series([False, True, False, True]) - assert_series_equal(s_0123 & Series([0.1, 4, -3.14, 2]), s_ftft) + result = s_0123 ^ False + expected = Series([False, True, True, True]) + assert_series_equal(result, expected) + + def test_logical_operators_int_dtype_with_object(self): + # GH#9016: support bitwise op for integer types + s_0123 = Series(range(4), dtype="int64") + + result = s_0123 & Series([False, np.NaN, False, False]) + expected = Series([False] * 4) + assert_series_equal(result, expected) s_abNd = Series(["a", "b", np.NaN, "d"]) - res = s_0123 & s_abNd - expected = s_ftft + result = s_0123 & s_abNd + expected = Series([False, True, False, True]) + assert_series_equal(result, expected) + + def test_logical_operators_bool_dtype_with_int(self): + index = list("bca") + + s_tft = Series([True, False, True], index=index) + s_fff = Series([False, False, False], index=index) + + res = s_tft & 0 + expected = s_fff + assert_series_equal(res, expected) + + res = s_tft & 1 + expected = s_tft + assert_series_equal(res, expected) + + def test_logical_operators_int_dtype_with_bool_dtype_and_reindex(self): + # GH#9016: support bitwise op for integer types + + # with non-matching indexes, logical operators will cast to object + # before operating + index = list("bca") + + s_tft = Series([True, False, True], index=index) + s_tft = Series([True, False, True], index=index) + s_tff = Series([True, False, False], index=index) + + s_0123 = Series(range(4), dtype="int64") + + # s_0123 will be all false now because of reindexing like s_tft + expected = Series([False] * 7, index=[0, 1, 2, 3, "a", "b", "c"]) + result = s_tft & s_0123 + assert_series_equal(result, expected) + + expected = Series([False] * 7, index=[0, 1, 2, 3, "a", "b", "c"]) + result = s_0123 & s_tft + assert_series_equal(result, expected) + + s_a0b1c0 = Series([1], list("b")) + + res = s_tft & s_a0b1c0 + expected = s_tff.reindex(list("abc")) + assert_series_equal(res, expected) + + res = s_tft | s_a0b1c0 + expected = s_tft.reindex(list("abc")) assert_series_equal(res, expected) def test_scalar_na_logical_ops_corners(self): @@ -523,6 +578,7 @@ def test_comparison_operators_with_nas(self): assert_series_equal(result, expected) + # FIXME: dont leave commented-out # fffffffuuuuuuuuuuuu # result = f(val, s) # expected = f(val, s.dropna()).reindex(s.index)