Skip to content

Commit 0f81a63

Browse files
authored
BUG: BooleanArray raising on comparison to string (#44533)
1 parent 19f0095 commit 0f81a63

File tree

4 files changed

+30
-1
lines changed

4 files changed

+30
-1
lines changed

doc/source/whatsnew/v1.4.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -712,6 +712,7 @@ ExtensionArray
712712
- Bug in :func:`array` failing to preserve :class:`PandasArray` (:issue:`43887`)
713713
- NumPy ufuncs ``np.abs``, ``np.positive``, ``np.negative`` now correctly preserve dtype when called on ExtensionArrays that implement ``__abs__, __pos__, __neg__``, respectively. In particular this is fixed for :class:`TimedeltaArray` (:issue:`43899`)
714714
- Avoid raising ``PerformanceWarning`` about fragmented DataFrame when using many columns with an extension dtype (:issue:`44098`)
715+
- Bug in :meth:`BooleanArray.__eq__` and :meth:`BooleanArray.__ne__` raising ``TypeError`` on comparison with an incompatible type (like a string). This caused :meth:`DataFrame.replace` to sometimes raise a ``TypeError`` if a nullable boolean column was included (:issue:`44499`)
715716
-
716717

717718
Styler

pandas/core/arrays/boolean.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
BaseMaskedArray,
4545
BaseMaskedDtype,
4646
)
47+
from pandas.core.ops import invalid_comparison
4748

4849
if TYPE_CHECKING:
4950
import pyarrow
@@ -653,7 +654,11 @@ def _cmp_method(self, other, op):
653654
with warnings.catch_warnings():
654655
warnings.filterwarnings("ignore", "elementwise", FutureWarning)
655656
with np.errstate(all="ignore"):
656-
result = op(self._data, other)
657+
method = getattr(self._data, f"__{op.__name__}__")
658+
result = method(other)
659+
660+
if result is NotImplemented:
661+
result = invalid_comparison(self._data, other, op)
657662

658663
# nans propagate
659664
if mask is None:

pandas/tests/arrays/boolean/test_logical.py

+14
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,20 @@ def test_empty_ok(self, all_logical_operators):
4141
result = getattr(a, op_name)(pd.NA)
4242
tm.assert_extension_array_equal(a, result)
4343

44+
@pytest.mark.parametrize(
45+
"other", ["a", pd.Timestamp(2017, 1, 1, 12), np.timedelta64(4)]
46+
)
47+
def test_eq_mismatched_type(self, other):
48+
# GH-44499
49+
arr = pd.array([True, False])
50+
result = arr == other
51+
expected = pd.array([False, False])
52+
tm.assert_extension_array_equal(result, expected)
53+
54+
result = arr != other
55+
expected = pd.array([True, True])
56+
tm.assert_extension_array_equal(result, expected)
57+
4458
def test_logical_length_mismatch_raises(self, all_logical_operators):
4559
op_name = all_logical_operators
4660
a = pd.array([True, False, None], dtype="boolean")

pandas/tests/frame/methods/test_replace.py

+9
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,15 @@ def test_replace_mixed3(self):
624624
expected.iloc[1, 1] = m[1]
625625
tm.assert_frame_equal(result, expected)
626626

627+
@pytest.mark.parametrize("dtype", ["boolean", "Int64", "Float64"])
628+
def test_replace_with_nullable_column(self, dtype):
629+
# GH-44499
630+
nullable_ser = Series([1, 0, 1], dtype=dtype)
631+
df = DataFrame({"A": ["A", "B", "x"], "B": nullable_ser})
632+
result = df.replace("x", "X")
633+
expected = DataFrame({"A": ["A", "B", "X"], "B": nullable_ser})
634+
tm.assert_frame_equal(result, expected)
635+
627636
def test_replace_simple_nested_dict(self):
628637
df = DataFrame({"col": range(1, 5)})
629638
expected = DataFrame({"col": ["a", 2, 3, "b"]})

0 commit comments

Comments
 (0)