From cd13b005d4dd7b7629476b0b66be743e13335d83 Mon Sep 17 00:00:00 2001 From: Brock Date: Tue, 18 Jan 2022 16:01:29 -0800 Subject: [PATCH 1/3] BUG: Series[bool].__setitem__(BooleanArray) --- pandas/core/dtypes/cast.py | 15 ++++++++------- pandas/tests/series/indexing/test_setitem.py | 13 +++++++++++++ 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/pandas/core/dtypes/cast.py b/pandas/core/dtypes/cast.py index 9d7e07df871d1..7a33d394dc867 100644 --- a/pandas/core/dtypes/cast.py +++ b/pandas/core/dtypes/cast.py @@ -1997,9 +1997,7 @@ def np_can_hold_element(dtype: np.dtype, element: Any) -> Any: elif not isinstance(tipo, np.dtype): # i.e. nullable IntegerDtype; we can put this into an ndarray # losslessly iff it has no NAs - hasnas = element._mask.any() - # TODO: don't rely on implementation detail - if hasnas: + if element._hasnans: raise ValueError return element @@ -2016,9 +2014,7 @@ def np_can_hold_element(dtype: np.dtype, element: Any) -> Any: elif not isinstance(tipo, np.dtype): # i.e. nullable IntegerDtype or FloatingDtype; # we can put this into an ndarray losslessly iff it has no NAs - hasnas = element._mask.any() - # TODO: don't rely on implementation detail - if hasnas: + if element._hasnans: raise ValueError return element return element @@ -2047,7 +2043,12 @@ def np_can_hold_element(dtype: np.dtype, element: Any) -> Any: elif dtype.kind == "b": if tipo is not None: - if tipo.kind == "b": # FIXME: wrong with BooleanArray? + if tipo.kind == "b": + if not isinstance(tipo, np.dtype): + # i.e. we have a BooleanArray + if element._hasnans: + # i.e. there are pd.NA elements + raise ValueError return element raise ValueError if lib.is_bool(element): diff --git a/pandas/tests/series/indexing/test_setitem.py b/pandas/tests/series/indexing/test_setitem.py index 38952ddfca2bb..6508632b36663 100644 --- a/pandas/tests/series/indexing/test_setitem.py +++ b/pandas/tests/series/indexing/test_setitem.py @@ -20,6 +20,7 @@ Series, Timedelta, Timestamp, + array, concat, date_range, period_range, @@ -572,6 +573,18 @@ def test_setitem_non_bool_into_bool(self, val, indexer_sli, unique): expected = Series([val, val], dtype=object, index=[1, 1]) tm.assert_series_equal(ser, expected) + def test_setitem_boolean_array_into_npbool(self): + ser = Series([True, False, True]) + values = ser._values + arr = array([True, False, None]) + + ser[:2] = arr[:2] # no NAs -> can set inplace + assert ser._values is values + + ser[1:] = arr[1:] # has an NA -> cast to boolean dtype + expected = Series(arr) + tm.assert_series_equal(ser, expected) + class SetitemCastingEquivalents: """ From c1e5f7372893cd0682013495639f0603bd8a103c Mon Sep 17 00:00:00 2001 From: Brock Date: Tue, 18 Jan 2022 16:04:15 -0800 Subject: [PATCH 2/3] Whatsnew, GH ref --- doc/source/whatsnew/v1.5.0.rst | 1 + pandas/tests/series/indexing/test_setitem.py | 1 + 2 files changed, 2 insertions(+) diff --git a/doc/source/whatsnew/v1.5.0.rst b/doc/source/whatsnew/v1.5.0.rst index b259f182a1197..f96439b86a6b4 100644 --- a/doc/source/whatsnew/v1.5.0.rst +++ b/doc/source/whatsnew/v1.5.0.rst @@ -212,6 +212,7 @@ Indexing - Bug in :meth:`DataFrame.iloc` where indexing a single row on a :class:`DataFrame` with a single ExtensionDtype column gave a copy instead of a view on the underlying data (:issue:`45241`) - Bug in :meth:`Series.__setitem__` with a non-integer :class:`Index` when using an integer key to set a value that cannot be set inplace where a ``ValueError`` was raised insead of casting to a common dtype (:issue:`45070`) - Bug when setting a value too large for a :class:`Series` dtype failing to coerce to a common type (:issue:`26049`, :issue:`32878`) +- Bug in :meth:`Series.__setitem__` when setting ``boolean`` dtype values containing ``NA`` incorrectly raising instead of casting to ``boolean`` dtype (:issue:`45462`) - Missing diff --git a/pandas/tests/series/indexing/test_setitem.py b/pandas/tests/series/indexing/test_setitem.py index 6508632b36663..b6fcc7914ee3e 100644 --- a/pandas/tests/series/indexing/test_setitem.py +++ b/pandas/tests/series/indexing/test_setitem.py @@ -574,6 +574,7 @@ def test_setitem_non_bool_into_bool(self, val, indexer_sli, unique): tm.assert_series_equal(ser, expected) def test_setitem_boolean_array_into_npbool(self): + # GH#45462 ser = Series([True, False, True]) values = ser._values arr = array([True, False, None]) From 648984706429d0dba12b2db1bc7c71203e8473bb Mon Sep 17 00:00:00 2001 From: Brock Date: Fri, 21 Jan 2022 16:44:35 -0800 Subject: [PATCH 3/3] _hasnans->_hasna --- pandas/core/dtypes/cast.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pandas/core/dtypes/cast.py b/pandas/core/dtypes/cast.py index bd19a7e558d3f..92f3cfdc589ff 100644 --- a/pandas/core/dtypes/cast.py +++ b/pandas/core/dtypes/cast.py @@ -1997,7 +1997,7 @@ def np_can_hold_element(dtype: np.dtype, element: Any) -> Any: elif not isinstance(tipo, np.dtype): # i.e. nullable IntegerDtype; we can put this into an ndarray # losslessly iff it has no NAs - if element._hasnans: + if element._hasna: raise ValueError return element @@ -2014,7 +2014,7 @@ def np_can_hold_element(dtype: np.dtype, element: Any) -> Any: elif not isinstance(tipo, np.dtype): # i.e. nullable IntegerDtype or FloatingDtype; # we can put this into an ndarray losslessly iff it has no NAs - if element._hasnans: + if element._hasna: raise ValueError return element return element @@ -2046,7 +2046,7 @@ def np_can_hold_element(dtype: np.dtype, element: Any) -> Any: if tipo.kind == "b": if not isinstance(tipo, np.dtype): # i.e. we have a BooleanArray - if element._hasnans: + if element._hasna: # i.e. there are pd.NA elements raise ValueError return element