diff --git a/pandas/core/missing.py b/pandas/core/missing.py index 039d868bccd16..6a4a3183757ce 100644 --- a/pandas/core/missing.py +++ b/pandas/core/missing.py @@ -124,11 +124,25 @@ def mask_missing(arr: ArrayLike, values_to_mask) -> npt.NDArray[np.bool_]: new_mask = np.zeros(arr.shape, dtype=np.bool_) new_mask[arr_mask] = arr[arr_mask] == x else: - new_mask = arr == x + # GH#47101 + # Fix where type bool has no attribute to_numpy() by first + # attempting to broadcast with np.equal for some cases, and then + # an explicit type check when checking the mask for any straggling + # cases. Where a literal comparison would fail np.equal we fall back + # to the original equality check. + try: + # In case of an uncastable type, this will emit TypeError + new_mask = np.equal(arr, x) # type: ignore[arg-type] + except TypeError: + # Old behaviour for uncastable types + new_mask = arr == x if not isinstance(new_mask, np.ndarray): # usually BooleanArray - new_mask = new_mask.to_numpy(dtype=bool, na_value=False) + if isinstance(new_mask, bool): + new_mask = np.array([new_mask], dtype= bool) + else: + new_mask = new_mask.to_numpy(dtype=bool, na_value=False) mask |= new_mask if na_mask.any(): diff --git a/pandas/tests/frame/methods/test_replace.py b/pandas/tests/frame/methods/test_replace.py index 6b872bf48d550..c24868ee7a641 100644 --- a/pandas/tests/frame/methods/test_replace.py +++ b/pandas/tests/frame/methods/test_replace.py @@ -1518,3 +1518,10 @@ def test_replace_object_splitting(self, using_infer_string): assert len(df._mgr.blocks) == 2 else: assert len(df._mgr.blocks) == 1 + + def test_replace_bool_to_numpy_attributeerror(self): + # GH#47101 + pass_pre_patch = DataFrame({"d":[None]}) + tm.assert_frame_equal(pass_pre_patch, pass_pre_patch.replace("", pd.NA)) + fail_pre_patch = DataFrame({"d":[pd.NA]}) + tm.assert_frame_equal(fail_pre_patch, fail_pre_patch.replace("", pd.NA))