From 9df9c89963e045f13d8cb9da8cee14b9912ec519 Mon Sep 17 00:00:00 2001 From: phofl Date: Wed, 4 Nov 2020 00:06:07 +0100 Subject: [PATCH 01/17] Fix bug in setitem when indexer was only false changed dtype --- doc/source/whatsnew/v1.2.0.rst | 1 + pandas/core/internals/blocks.py | 3 ++- pandas/tests/frame/indexing/test_setitem.py | 7 +++++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v1.2.0.rst b/doc/source/whatsnew/v1.2.0.rst index 7111d54d65815..4d9be2b9d714a 100644 --- a/doc/source/whatsnew/v1.2.0.rst +++ b/doc/source/whatsnew/v1.2.0.rst @@ -457,6 +457,7 @@ Indexing - Bug in indexing on a :class:`Series` or :class:`DataFrame` with a :class:`MultiIndex` with a level named "0" (:issue:`37194`) - Bug in :meth:`Series.__getitem__` when using an unsigned integer array as an indexer giving incorrect results or segfaulting instead of raising ``KeyError`` (:issue:`37218`) - Bug in :meth:`Index.where` incorrectly casting numeric values to strings (:issue:`37591`) +- Bug in :meth:`DataFrame.loc.__setitem__` changed dtype when indexer was completely ``False``(:issue:`37550`) Missing ^^^^^^^ diff --git a/pandas/core/internals/blocks.py b/pandas/core/internals/blocks.py index 24b00199611bf..810223971ec38 100644 --- a/pandas/core/internals/blocks.py +++ b/pandas/core/internals/blocks.py @@ -919,10 +919,11 @@ def setitem(self, indexer, value): elif lib.is_scalar(value) and not isna(value): dtype, _ = infer_dtype_from_scalar(value, pandas_dtype=True) - else: # e.g. we are bool dtype and value is nan # TODO: watch out for case with listlike value and scalar/empty indexer + if is_list_like(value) and is_empty_indexer(indexer, np.array(value)): + return self dtype, _ = maybe_promote(np.array(value).dtype) return self.astype(dtype).setitem(indexer, value) diff --git a/pandas/tests/frame/indexing/test_setitem.py b/pandas/tests/frame/indexing/test_setitem.py index e1ce10970f07b..41881b628e48d 100644 --- a/pandas/tests/frame/indexing/test_setitem.py +++ b/pandas/tests/frame/indexing/test_setitem.py @@ -285,3 +285,10 @@ def test_iloc_setitem_bool_indexer(self, klass): df.iloc[indexer, 1] = df.iloc[indexer, 1] * 2 expected = DataFrame({"flag": ["x", "y", "z"], "value": [2, 3, 4]}) tm.assert_frame_equal(df, expected) + + def test_setitem_only_false_indexer_dtype_changed(self): + # GH: 37550 + df = DataFrame({"a": ["a"], "b": [1], "c": [1]}) + df.loc[[False], ["b"]] = 10 - df["c"] + expected = DataFrame({"a": ["a"], "b": [1], "c": [1]}) + tm.assert_frame_equal(df, expected) From 26b7dae23aab2a1a71a934474ab50d5aa151f0a0 Mon Sep 17 00:00:00 2001 From: phofl Date: Fri, 6 Nov 2020 22:22:28 +0100 Subject: [PATCH 02/17] Add space --- doc/source/whatsnew/v1.2.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v1.2.0.rst b/doc/source/whatsnew/v1.2.0.rst index 13747ffd48303..13409148047c2 100644 --- a/doc/source/whatsnew/v1.2.0.rst +++ b/doc/source/whatsnew/v1.2.0.rst @@ -465,7 +465,7 @@ Indexing - Bug in indexing on a :class:`Series` or :class:`DataFrame` with a :class:`MultiIndex` with a level named "0" (:issue:`37194`) - Bug in :meth:`Series.__getitem__` when using an unsigned integer array as an indexer giving incorrect results or segfaulting instead of raising ``KeyError`` (:issue:`37218`) - Bug in :meth:`Index.where` incorrectly casting numeric values to strings (:issue:`37591`) -- Bug in :meth:`DataFrame.loc.__setitem__` changed dtype when indexer was completely ``False``(:issue:`37550`) +- Bug in :meth:`DataFrame.loc.__setitem__` changed dtype when indexer was completely ``False`` (:issue:`37550`) Missing ^^^^^^^ From 11d27b1aac5cbffa7bc88fbe66c23cb4a95e0379 Mon Sep 17 00:00:00 2001 From: phofl Date: Sun, 8 Nov 2020 12:09:25 +0100 Subject: [PATCH 03/17] Move condition to elif --- pandas/core/internals/blocks.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pandas/core/internals/blocks.py b/pandas/core/internals/blocks.py index a14e977beedac..67913b9315d8c 100644 --- a/pandas/core/internals/blocks.py +++ b/pandas/core/internals/blocks.py @@ -920,11 +920,11 @@ def setitem(self, indexer, value): elif lib.is_scalar(value) and not isna(value): dtype, _ = infer_dtype_from_scalar(value, pandas_dtype=True) + elif is_list_like(value) and is_empty_indexer(indexer, np.array(value)): + return self else: # e.g. we are bool dtype and value is nan - # TODO: watch out for case with listlike value and scalar/empty indexer - if is_list_like(value) and is_empty_indexer(indexer, np.array(value)): - return self + # TODO: watch out for case with listlike value and scalar indexer dtype, _ = maybe_promote(np.array(value).dtype) return self.astype(dtype).setitem(indexer, value) From a527342b2706a8e23a0af7c41c1ccd94f91dce9e Mon Sep 17 00:00:00 2001 From: phofl Date: Mon, 9 Nov 2020 00:15:08 +0100 Subject: [PATCH 04/17] Move test --- pandas/tests/frame/indexing/test_setitem.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pandas/tests/frame/indexing/test_setitem.py b/pandas/tests/frame/indexing/test_setitem.py index 128caa387bac7..3e052d91b9252 100644 --- a/pandas/tests/frame/indexing/test_setitem.py +++ b/pandas/tests/frame/indexing/test_setitem.py @@ -298,13 +298,6 @@ def test_iloc_setitem_bool_indexer(self, klass): expected = DataFrame({"flag": ["x", "y", "z"], "value": [2, 3, 4]}) tm.assert_frame_equal(df, expected) - def test_setitem_only_false_indexer_dtype_changed(self): - # GH: 37550 - df = DataFrame({"a": ["a"], "b": [1], "c": [1]}) - df.loc[[False], ["b"]] = 10 - df["c"] - expected = DataFrame({"a": ["a"], "b": [1], "c": [1]}) - tm.assert_frame_equal(df, expected) - class TestDataFrameSetItemSlicing: def test_setitem_slice_position(self): @@ -346,3 +339,10 @@ def test_setitem_boolean_mask(self, mask_type, float_frame): expected = df.copy() expected.values[np.array(mask)] = np.nan tm.assert_frame_equal(result, expected) + + def test_setitem_only_false_indexer_dtype_changed(self): + # GH: 37550 + df = DataFrame({"a": ["a"], "b": [1], "c": [1]}) + df.loc[[False], ["b"]] = 10 - df["c"] + expected = DataFrame({"a": ["a"], "b": [1], "c": [1]}) + tm.assert_frame_equal(df, expected) From 345b3c19bfc9c488c6634ba1477cd15619acb43d Mon Sep 17 00:00:00 2001 From: phofl Date: Mon, 9 Nov 2020 02:56:52 +0100 Subject: [PATCH 05/17] Adjust test --- pandas/tests/frame/indexing/test_setitem.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pandas/tests/frame/indexing/test_setitem.py b/pandas/tests/frame/indexing/test_setitem.py index 3e052d91b9252..da3cc47fdef14 100644 --- a/pandas/tests/frame/indexing/test_setitem.py +++ b/pandas/tests/frame/indexing/test_setitem.py @@ -346,3 +346,6 @@ def test_setitem_only_false_indexer_dtype_changed(self): df.loc[[False], ["b"]] = 10 - df["c"] expected = DataFrame({"a": ["a"], "b": [1], "c": [1]}) tm.assert_frame_equal(df, expected) + + df.loc[[False], ["b"]] = 10 - 1 + tm.assert_frame_equal(df, expected) From 3b92d31a4a136be1ebbafce2c5516422a1d59e17 Mon Sep 17 00:00:00 2001 From: phofl Date: Sat, 14 Nov 2020 13:17:37 +0100 Subject: [PATCH 06/17] Fix whatsnew --- doc/source/whatsnew/v1.2.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v1.2.0.rst b/doc/source/whatsnew/v1.2.0.rst index 3a5e1f16e90fe..b3c070a978bb4 100644 --- a/doc/source/whatsnew/v1.2.0.rst +++ b/doc/source/whatsnew/v1.2.0.rst @@ -469,7 +469,7 @@ Indexing - Bug in :meth:`Index.where` incorrectly casting numeric values to strings (:issue:`37591`) - Bug in :meth:`Series.loc` and :meth:`DataFrame.loc` raises when numeric label was given for object :class:`Index` although label was in :class:`Index` (:issue:`26491`) - Bug in :meth:`DataFrame.loc` returned requested key plus missing values when ``loc`` was applied to single level from :class:`MultiIndex` (:issue:`27104`) -- Bug in :meth:`DataFrame.loc.__setitem__` changed dtype when indexer was completely ``False`` (:issue:`37550`) +- Bug in :meth:`DataFrame.loc.__setitem__` changing dtype when indexer was completely ``False`` (:issue:`37550`) Missing ^^^^^^^ From 2f7bf13f778dade9f3dc68bcb1e0d6be4a742aea Mon Sep 17 00:00:00 2001 From: phofl Date: Sat, 14 Nov 2020 13:31:36 +0100 Subject: [PATCH 07/17] Make test more clear --- pandas/tests/frame/indexing/test_setitem.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pandas/tests/frame/indexing/test_setitem.py b/pandas/tests/frame/indexing/test_setitem.py index da3cc47fdef14..da0fe84f29c6f 100644 --- a/pandas/tests/frame/indexing/test_setitem.py +++ b/pandas/tests/frame/indexing/test_setitem.py @@ -340,8 +340,9 @@ def test_setitem_boolean_mask(self, mask_type, float_frame): expected.values[np.array(mask)] = np.nan tm.assert_frame_equal(result, expected) - def test_setitem_only_false_indexer_dtype_changed(self): - # GH: 37550 + def test_setitem_loc_only_false_indexer_dtype_changed(self): + # GH#37550 + # Dtype is only changed when value to set is a Series df = DataFrame({"a": ["a"], "b": [1], "c": [1]}) df.loc[[False], ["b"]] = 10 - df["c"] expected = DataFrame({"a": ["a"], "b": [1], "c": [1]}) From eb3b204dc1eee203427dd6dc276eebe1ad93f19d Mon Sep 17 00:00:00 2001 From: phofl Date: Sun, 15 Nov 2020 19:06:29 +0100 Subject: [PATCH 08/17] Use asarray --- pandas/core/internals/blocks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/core/internals/blocks.py b/pandas/core/internals/blocks.py index a76cf49223280..b9dba6801eaa8 100644 --- a/pandas/core/internals/blocks.py +++ b/pandas/core/internals/blocks.py @@ -928,7 +928,7 @@ def setitem(self, indexer, value): elif lib.is_scalar(value) and not isna(value): dtype, _ = infer_dtype_from_scalar(value, pandas_dtype=True) - elif is_list_like(value) and is_empty_indexer(indexer, np.array(value)): + elif is_list_like(value) and is_empty_indexer(indexer, np.asarray(value)): return self else: # e.g. we are bool dtype and value is nan From 9d59de4d070aaa9ef4d65e834763e310fe372a73 Mon Sep 17 00:00:00 2001 From: phofl Date: Mon, 16 Nov 2020 21:43:09 +0100 Subject: [PATCH 09/17] Parametrize test --- pandas/tests/frame/indexing/test_setitem.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pandas/tests/frame/indexing/test_setitem.py b/pandas/tests/frame/indexing/test_setitem.py index da0fe84f29c6f..b7c45dd320f87 100644 --- a/pandas/tests/frame/indexing/test_setitem.py +++ b/pandas/tests/frame/indexing/test_setitem.py @@ -340,13 +340,16 @@ def test_setitem_boolean_mask(self, mask_type, float_frame): expected.values[np.array(mask)] = np.nan tm.assert_frame_equal(result, expected) - def test_setitem_loc_only_false_indexer_dtype_changed(self): + @pytest.mark.parametrize("func", [list, np.array, Series]) + @pytest.mark.parametrize("value", [[], [False]]) + def test_setitem_loc_only_false_indexer_dtype_changed(self, func, value): # GH#37550 - # Dtype is only changed when value to set is a Series + # Dtype is only changed when value to set is a Series and indexer is + # empty/bool all False df = DataFrame({"a": ["a"], "b": [1], "c": [1]}) - df.loc[[False], ["b"]] = 10 - df["c"] + df.loc[func(value), ["b"]] = 10 - df["c"] expected = DataFrame({"a": ["a"], "b": [1], "c": [1]}) tm.assert_frame_equal(df, expected) - df.loc[[False], ["b"]] = 10 - 1 + df.loc[[False], ["b"]] = 9 tm.assert_frame_equal(df, expected) From b02e629938478edf290eba74e1814a18fab2a209 Mon Sep 17 00:00:00 2001 From: phofl Date: Tue, 17 Nov 2020 12:36:12 +0100 Subject: [PATCH 10/17] Move if condition --- pandas/core/indexing.py | 10 +++++----- pandas/core/internals/blocks.py | 3 +-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/pandas/core/indexing.py b/pandas/core/indexing.py index c5e331a104726..839f25d91b4cc 100644 --- a/pandas/core/indexing.py +++ b/pandas/core/indexing.py @@ -31,7 +31,7 @@ from pandas.core.indexers import ( check_array_indexer, is_list_like_indexer, - length_of_indexer, + length_of_indexer, is_empty_indexer, ) from pandas.core.indexes.api import Index @@ -1683,6 +1683,10 @@ def _setitem_with_indexer_split_path(self, indexer, value): # hasattr first, to avoid coercing to ndarray without reason. # But we may be relying on the ndarray coercion to check ndim. # Why not just convert to an ndarray earlier on if needed? + elif lplane_indexer == 0: + # We get here in one case via .loc with a all-False mask + pass + elif np.ndim(value) == 2: self._setitem_with_indexer_2d_value(indexer, value) @@ -1695,10 +1699,6 @@ def _setitem_with_indexer_split_path(self, indexer, value): # We only get here with len(ilocs) == 1 self._setitem_single_column(ilocs[0], value, plane_indexer) - elif lplane_indexer == 0 and len(value) == len(self.obj.index): - # We get here in one case via .loc with a all-False mask - pass - else: # per-label values if len(ilocs) != len(value): diff --git a/pandas/core/internals/blocks.py b/pandas/core/internals/blocks.py index b9dba6801eaa8..73aef747547e3 100644 --- a/pandas/core/internals/blocks.py +++ b/pandas/core/internals/blocks.py @@ -928,8 +928,7 @@ def setitem(self, indexer, value): elif lib.is_scalar(value) and not isna(value): dtype, _ = infer_dtype_from_scalar(value, pandas_dtype=True) - elif is_list_like(value) and is_empty_indexer(indexer, np.asarray(value)): - return self + else: # e.g. we are bool dtype and value is nan # TODO: watch out for case with listlike value and scalar indexer From 8edc7d0d2db1123d4be1c9d818503a28e5b0d47b Mon Sep 17 00:00:00 2001 From: phofl Date: Tue, 17 Nov 2020 12:48:11 +0100 Subject: [PATCH 11/17] Remove import --- pandas/core/indexing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/core/indexing.py b/pandas/core/indexing.py index 423f6951ec8e1..24983b0b64d00 100644 --- a/pandas/core/indexing.py +++ b/pandas/core/indexing.py @@ -31,7 +31,7 @@ from pandas.core.indexers import ( check_array_indexer, is_list_like_indexer, - length_of_indexer, is_empty_indexer, + length_of_indexer, ) from pandas.core.indexes.api import Index From e57e40744c20a0028d729e07904a3c8220080e52 Mon Sep 17 00:00:00 2001 From: phofl Date: Wed, 18 Nov 2020 00:06:39 +0100 Subject: [PATCH 12/17] Rename parametrization --- pandas/tests/frame/indexing/test_setitem.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pandas/tests/frame/indexing/test_setitem.py b/pandas/tests/frame/indexing/test_setitem.py index b7c45dd320f87..0311f109126e2 100644 --- a/pandas/tests/frame/indexing/test_setitem.py +++ b/pandas/tests/frame/indexing/test_setitem.py @@ -340,16 +340,16 @@ def test_setitem_boolean_mask(self, mask_type, float_frame): expected.values[np.array(mask)] = np.nan tm.assert_frame_equal(result, expected) - @pytest.mark.parametrize("func", [list, np.array, Series]) + @pytest.mark.parametrize("box", [list, np.array, Series]) @pytest.mark.parametrize("value", [[], [False]]) - def test_setitem_loc_only_false_indexer_dtype_changed(self, func, value): + def test_setitem_loc_only_false_indexer_dtype_changed(self, box, value): # GH#37550 # Dtype is only changed when value to set is a Series and indexer is # empty/bool all False df = DataFrame({"a": ["a"], "b": [1], "c": [1]}) - df.loc[func(value), ["b"]] = 10 - df["c"] + df.loc[box(value), ["b"]] = 10 - df["c"] expected = DataFrame({"a": ["a"], "b": [1], "c": [1]}) tm.assert_frame_equal(df, expected) - df.loc[[False], ["b"]] = 9 + df.loc[box(value), ["b"]] = 9 tm.assert_frame_equal(df, expected) From c14d7b22c723cde7d28ae20815c7881fef3f0cb6 Mon Sep 17 00:00:00 2001 From: phofl Date: Mon, 21 Dec 2020 21:00:53 +0100 Subject: [PATCH 13/17] Move whatsnew --- doc/source/whatsnew/v1.3.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v1.3.0.rst b/doc/source/whatsnew/v1.3.0.rst index 7671962018144..58587a739e6ec 100644 --- a/doc/source/whatsnew/v1.3.0.rst +++ b/doc/source/whatsnew/v1.3.0.rst @@ -218,7 +218,7 @@ Indexing - Bug in :meth:`CategoricalIndex.get_indexer` failing to raise ``InvalidIndexError`` when non-unique (:issue:`38372`) - Bug in inserting many new columns into a :class:`DataFrame` causing incorrect subsequent indexing behavior (:issue:`38380`) - Bug in :meth:`DataFrame.iloc.__setitem__` and :meth:`DataFrame.loc.__setitem__` with mixed dtypes when setting with a dictionary value (:issue:`38335`) -- +- Bug in :meth:`DataFrame.loc.__setitem__` changing dtype when indexer was completely ``False`` (:issue:`37550`) - Missing From 85120c90a18d608c56d4336727ad2705ddfaf9f2 Mon Sep 17 00:00:00 2001 From: phofl Date: Sun, 18 Apr 2021 00:34:27 +0200 Subject: [PATCH 14/17] Fix dep warning --- pandas/tests/frame/indexing/test_setitem.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pandas/tests/frame/indexing/test_setitem.py b/pandas/tests/frame/indexing/test_setitem.py index 38e91a9870d34..cd137c43c50f5 100644 --- a/pandas/tests/frame/indexing/test_setitem.py +++ b/pandas/tests/frame/indexing/test_setitem.py @@ -841,11 +841,15 @@ def test_setitem_loc_only_false_indexer_dtype_changed(self, box, value): # Dtype is only changed when value to set is a Series and indexer is # empty/bool all False df = DataFrame({"a": ["a"], "b": [1], "c": [1]}) - df.loc[box(value), ["b"]] = 10 - df["c"] + if box == Series and not value: + indexer = box(value, dtype="object") + else: + indexer = box(value) + df.loc[indexer, ["b"]] = 10 - df["c"] expected = DataFrame({"a": ["a"], "b": [1], "c": [1]}) tm.assert_frame_equal(df, expected) - df.loc[box(value), ["b"]] = 9 + df.loc[indexer, ["b"]] = 9 tm.assert_frame_equal(df, expected) @pytest.mark.parametrize("indexer", [tm.setitem, tm.loc]) From bae602e70d43b7b7e148384de43cd77c538001c3 Mon Sep 17 00:00:00 2001 From: phofl Date: Sun, 18 Apr 2021 01:19:47 +0200 Subject: [PATCH 15/17] Fix corner case --- pandas/core/indexing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/core/indexing.py b/pandas/core/indexing.py index 870010c5bded7..d70f50fa0233a 100644 --- a/pandas/core/indexing.py +++ b/pandas/core/indexing.py @@ -1743,7 +1743,7 @@ def _setitem_with_indexer_split_path(self, indexer, value, name: str): if isinstance(value, ABCDataFrame): self._setitem_with_indexer_frame_value(indexer, value, name) - elif lplane_indexer == 0: + elif lplane_indexer == 0 and not isinstance(pi, slice): # We get here in one case via .loc with a all-False mask pass From d334c7ad76306be49ff3d9a76869cd8a984b07c4 Mon Sep 17 00:00:00 2001 From: phofl Date: Fri, 10 Dec 2021 15:53:35 +0100 Subject: [PATCH 16/17] Move check --- pandas/core/indexing.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pandas/core/indexing.py b/pandas/core/indexing.py index 730c2bf0ebcfd..f043a8cee308c 100644 --- a/pandas/core/indexing.py +++ b/pandas/core/indexing.py @@ -1706,10 +1706,6 @@ def _setitem_with_indexer_split_path(self, indexer, value, name: str): if isinstance(value, ABCDataFrame): self._setitem_with_indexer_frame_value(indexer, value, name) - elif lplane_indexer == 0 and not isinstance(pi, slice): - # We get here in one case via .loc with a all-False mask - pass - elif np.ndim(value) == 2: self._setitem_with_indexer_2d_value(indexer, value) @@ -2062,6 +2058,8 @@ def ravel(i): # we have a frame, with multiple indexers on both axes; and a # series, so need to broadcast (see GH5206) if sum_aligners == self.ndim and all(is_sequence(_) for _ in indexer): + if is_empty_indexer(indexer[0], ser._values): + return ser._values.copy() ser = ser.reindex(obj.axes[0][indexer[0]], copy=True)._values # single indexer From f12ef06664c94143bd143780e12f6caa81a505a9 Mon Sep 17 00:00:00 2001 From: phofl Date: Fri, 10 Dec 2021 16:01:30 +0100 Subject: [PATCH 17/17] Split tests --- doc/source/whatsnew/v1.4.0.rst | 1 + pandas/tests/frame/indexing/test_setitem.py | 21 +++++++++++++++------ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/doc/source/whatsnew/v1.4.0.rst b/doc/source/whatsnew/v1.4.0.rst index c3cf64c84510e..eb3fcfd74819d 100644 --- a/doc/source/whatsnew/v1.4.0.rst +++ b/doc/source/whatsnew/v1.4.0.rst @@ -697,6 +697,7 @@ Indexing - Bug in :meth:`DataFrame.loc.__getitem__` incorrectly raising ``KeyError`` when selecting a single column with a boolean key (:issue:`44322`). - Bug in setting :meth:`DataFrame.iloc` with a single ``ExtensionDtype`` column and setting 2D values e.g. ``df.iloc[:] = df.values`` incorrectly raising (:issue:`44514`) - Bug in indexing on columns with ``loc`` or ``iloc`` using a slice with a negative step with ``ExtensionDtype`` columns incorrectly raising (:issue:`44551`) +- Bug in :meth:`DataFrame.loc.__setitem__` changing dtype when indexer was completely ``False`` (:issue:`37550`) - Bug in :meth:`IntervalIndex.get_indexer_non_unique` returning boolean mask instead of array of integers for a non unique and non monotonic index (:issue:`44084`) - Bug in :meth:`IntervalIndex.get_indexer_non_unique` not handling targets of ``dtype`` 'object' with NaNs correctly (:issue:`44482`) - diff --git a/pandas/tests/frame/indexing/test_setitem.py b/pandas/tests/frame/indexing/test_setitem.py index 65ea67b11864c..9868cec633dff 100644 --- a/pandas/tests/frame/indexing/test_setitem.py +++ b/pandas/tests/frame/indexing/test_setitem.py @@ -922,17 +922,26 @@ def test_setitem_boolean_mask(self, mask_type, float_frame): expected.values[np.array(mask)] = np.nan tm.assert_frame_equal(result, expected) + @pytest.mark.xfail(reason="Currently empty indexers are treated as all False") @pytest.mark.parametrize("box", [list, np.array, Series]) - @pytest.mark.parametrize("value", [[], [False]]) - def test_setitem_loc_only_false_indexer_dtype_changed(self, box, value): + def test_setitem_loc_empty_indexer_raises_with_non_empty_value(self, box): + # GH#37672 + df = DataFrame({"a": ["a"], "b": [1], "c": [1]}) + if box == Series: + indexer = box([], dtype="object") + else: + indexer = box([]) + msg = "Must have equal len keys and value when setting with an iterable" + with pytest.raises(ValueError, match=msg): + df.loc[indexer, ["b"]] = [1] + + @pytest.mark.parametrize("box", [list, np.array, Series]) + def test_setitem_loc_only_false_indexer_dtype_changed(self, box): # GH#37550 # Dtype is only changed when value to set is a Series and indexer is # empty/bool all False df = DataFrame({"a": ["a"], "b": [1], "c": [1]}) - if box == Series and not value: - indexer = box(value, dtype="object") - else: - indexer = box(value) + indexer = box([False]) df.loc[indexer, ["b"]] = 10 - df["c"] expected = DataFrame({"a": ["a"], "b": [1], "c": [1]}) tm.assert_frame_equal(df, expected)