diff --git a/doc/source/whatsnew/v2.1.0.rst b/doc/source/whatsnew/v2.1.0.rst index da95bca7fb2a6..c511626f060cb 100644 --- a/doc/source/whatsnew/v2.1.0.rst +++ b/doc/source/whatsnew/v2.1.0.rst @@ -370,6 +370,7 @@ Interval Indexing ^^^^^^^^ - Bug in :meth:`DataFrame.__setitem__` losing dtype when setting a :class:`DataFrame` into duplicated columns (:issue:`53143`) +- Bug in :meth:`DataFrame.__setitem__` with a boolean mask and :meth:`DataFrame.putmask` with mixed non-numeric dtypes and a value other than ``NaN`` incorrectly raising ``TypeError`` (:issue:`53291`) - Missing diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 8f6698cab000c..e3c5f2facbe94 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -4074,7 +4074,6 @@ def _setitem_frame(self, key, value): "Must pass DataFrame or 2-d ndarray with boolean values only" ) - self._check_inplace_setting(value) self._check_setitem_copy() self._where(-key, value, inplace=True) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index dd0092a58f8e7..e651ab45225c2 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -114,7 +114,6 @@ is_bool_dtype, is_dict_like, is_extension_array_dtype, - is_float, is_list_like, is_number, is_numeric_dtype, @@ -6201,21 +6200,6 @@ def _is_mixed_type(self) -> bool_t: return self.dtypes.nunique() > 1 - @final - def _check_inplace_setting(self, value) -> bool_t: - """check whether we allow in-place setting with this type of value""" - if self._is_mixed_type and not self._mgr.is_numeric_mixed_type: - # allow an actual np.nan through - if (is_float(value) and np.isnan(value)) or value is lib.no_default: - return True - - raise TypeError( - "Cannot do inplace boolean setting on " - "mixed-types with a non np.nan value" - ) - - return True - @final def _get_numeric_data(self) -> Self: return self._constructor(self._mgr.get_numeric_data()).__finalize__(self) @@ -10036,7 +10020,6 @@ def _where( # we may have different type blocks come out of putmask, so # reconstruct the block manager - self._check_inplace_setting(other) new_data = self._mgr.putmask(mask=cond, new=other, align=align) result = self._constructor(new_data) return self._update_inplace(result) diff --git a/pandas/core/internals/array_manager.py b/pandas/core/internals/array_manager.py index f04ac76998adc..75eaaa80a9961 100644 --- a/pandas/core/internals/array_manager.py +++ b/pandas/core/internals/array_manager.py @@ -443,10 +443,6 @@ def to_native_types(self, **kwargs) -> Self: def is_mixed_type(self) -> bool: return True - @property - def is_numeric_mixed_type(self) -> bool: - return all(is_numeric_dtype(t) for t in self.get_dtypes()) - @property def any_extension_types(self) -> bool: """Whether any of the blocks in this manager are extension blocks""" diff --git a/pandas/core/internals/managers.py b/pandas/core/internals/managers.py index 8e60bf02fed75..2a7c0536c66a4 100644 --- a/pandas/core/internals/managers.py +++ b/pandas/core/internals/managers.py @@ -514,10 +514,6 @@ def to_native_types(self, **kwargs) -> Self: """ return self.apply("to_native_types", **kwargs) - @property - def is_numeric_mixed_type(self) -> bool: - return all(block.is_numeric for block in self.blocks) - @property def any_extension_types(self) -> bool: """Whether any of the blocks in this manager are extension blocks""" diff --git a/pandas/tests/frame/indexing/test_where.py b/pandas/tests/frame/indexing/test_where.py index c5e1e3c02c26e..562f2fbe55c25 100644 --- a/pandas/tests/frame/indexing/test_where.py +++ b/pandas/tests/frame/indexing/test_where.py @@ -401,10 +401,23 @@ def test_where_none(self): {"A": np.nan, "B": "Test", "C": np.nan}, ] ) - msg = "boolean setting on mixed-type" - with pytest.raises(TypeError, match=msg): - df.where(~isna(df), None, inplace=True) + orig = df.copy() + + mask = ~isna(df) + df.where(mask, None, inplace=True) + expected = DataFrame( + { + "A": [1.0, np.nan], + "B": [None, "Test"], + "C": ["Test", None], + } + ) + tm.assert_frame_equal(df, expected) + + df = orig.copy() + df[~mask] = None + tm.assert_frame_equal(df, expected) def test_where_empty_df_and_empty_cond_having_non_bool_dtypes(self): # see gh-21947