Skip to content

Commit 61e0eef

Browse files
authored
DEPR: Deprecate set and dict as indexers (#45052)
1 parent f549cb4 commit 61e0eef

File tree

9 files changed

+176
-5
lines changed

9 files changed

+176
-5
lines changed

doc/source/whatsnew/v1.4.0.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -608,6 +608,7 @@ Other Deprecations
608608
- A deprecation warning is now shown for :meth:`DataFrame.to_latex` indicating the arguments signature may change and emulate more the arguments to :meth:`.Styler.to_latex` in future versions (:issue:`44411`)
609609
- Deprecated behavior of :func:`concat` between objects with bool-dtype and numeric-dtypes; in a future version these will cast to object dtype instead of coercing bools to numeric values (:issue:`39817`)
610610
- Deprecated :meth:`Categorical.replace`, use :meth:`Series.replace` instead (:issue:`44929`)
611+
- Deprecated passing ``set`` or ``dict`` as indexer for :meth:`DataFrame.loc.__setitem__`, :meth:`DataFrame.loc.__getitem__`, :meth:`Series.loc.__setitem__`, :meth:`Series.loc.__getitem__`, :meth:`DataFrame.__getitem__`, :meth:`Series.__getitem__` and :meth:`Series.__setitem__` (:issue:`42825`)
611612
- Deprecated :meth:`Index.__getitem__` with a bool key; use ``index.values[key]`` to get the old behavior (:issue:`44051`)
612613
- Deprecated downcasting column-by-column in :meth:`DataFrame.where` with integer-dtypes (:issue:`44597`)
613614
- Deprecated :meth:`DatetimeIndex.union_many`, use :meth:`DatetimeIndex.union` instead (:issue:`44091`)

pandas/core/frame.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@
170170
)
171171
from pandas.core.indexing import (
172172
check_bool_indexer,
173+
check_deprecated_indexers,
173174
convert_to_index_sliceable,
174175
)
175176
from pandas.core.internals import (
@@ -3458,6 +3459,7 @@ def _iter_column_arrays(self) -> Iterator[ArrayLike]:
34583459
yield self._get_column_array(i)
34593460

34603461
def __getitem__(self, key):
3462+
check_deprecated_indexers(key)
34613463
key = lib.item_from_zerodim(key)
34623464
key = com.apply_if_callable(key, self)
34633465

pandas/core/indexing.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -641,6 +641,10 @@ def _get_setitem_indexer(self, key):
641641
if self.name == "loc":
642642
self._ensure_listlike_indexer(key)
643643

644+
if isinstance(key, tuple):
645+
for x in key:
646+
check_deprecated_indexers(x)
647+
644648
if self.axis is not None:
645649
return self._convert_tuple(key)
646650

@@ -698,6 +702,7 @@ def _ensure_listlike_indexer(self, key, axis=None, value=None):
698702
)
699703

700704
def __setitem__(self, key, value):
705+
check_deprecated_indexers(key)
701706
if isinstance(key, tuple):
702707
key = tuple(list(x) if is_iterator(x) else x for x in key)
703708
key = tuple(com.apply_if_callable(x, self.obj) for x in key)
@@ -890,6 +895,9 @@ def _getitem_nested_tuple(self, tup: tuple):
890895
# we have a nested tuple so have at least 1 multi-index level
891896
# we should be able to match up the dimensionality here
892897

898+
for key in tup:
899+
check_deprecated_indexers(key)
900+
893901
# we have too many indexers for our dim, but have at least 1
894902
# multi-index dimension, try to see if we have something like
895903
# a tuple passed to a series with a multi-index
@@ -943,6 +951,7 @@ def _convert_to_indexer(self, key, axis: int):
943951
raise AbstractMethodError(self)
944952

945953
def __getitem__(self, key):
954+
check_deprecated_indexers(key)
946955
if type(key) is tuple:
947956
key = tuple(list(x) if is_iterator(x) else x for x in key)
948957
key = tuple(com.apply_if_callable(x, self.obj) for x in key)
@@ -2444,3 +2453,29 @@ def need_slice(obj: slice) -> bool:
24442453
or obj.stop is not None
24452454
or (obj.step is not None and obj.step != 1)
24462455
)
2456+
2457+
2458+
def check_deprecated_indexers(key) -> None:
2459+
"""Checks if the key is a deprecated indexer."""
2460+
if (
2461+
isinstance(key, set)
2462+
or isinstance(key, tuple)
2463+
and any(isinstance(x, set) for x in key)
2464+
):
2465+
warnings.warn(
2466+
"Passing a set as an indexer is deprecated and will raise in "
2467+
"a future version. Use a list instead.",
2468+
FutureWarning,
2469+
stacklevel=find_stack_level(),
2470+
)
2471+
if (
2472+
isinstance(key, dict)
2473+
or isinstance(key, tuple)
2474+
and any(isinstance(x, dict) for x in key)
2475+
):
2476+
warnings.warn(
2477+
"Passing a dict as an indexer is deprecated and will raise in "
2478+
"a future version. Use a list instead.",
2479+
FutureWarning,
2480+
stacklevel=find_stack_level(),
2481+
)

pandas/core/series.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,10 @@
124124
ensure_index,
125125
)
126126
import pandas.core.indexes.base as ibase
127-
from pandas.core.indexing import check_bool_indexer
127+
from pandas.core.indexing import (
128+
check_bool_indexer,
129+
check_deprecated_indexers,
130+
)
128131
from pandas.core.internals import (
129132
SingleArrayManager,
130133
SingleBlockManager,
@@ -939,6 +942,7 @@ def _slice(self, slobj: slice, axis: int = 0) -> Series:
939942
return self._get_values(slobj)
940943

941944
def __getitem__(self, key):
945+
check_deprecated_indexers(key)
942946
key = com.apply_if_callable(key, self)
943947

944948
if key is Ellipsis:
@@ -1065,6 +1069,7 @@ def _get_value(self, label, takeable: bool = False):
10651069
return self.index._get_values_for_loc(self, loc, label)
10661070

10671071
def __setitem__(self, key, value) -> None:
1072+
check_deprecated_indexers(key)
10681073
key = com.apply_if_callable(key, self)
10691074
cacher_needs_updating = self._check_is_chained_assignment_possible()
10701075

pandas/tests/frame/indexing/test_getitem.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,11 @@ def test_getitem_listlike(self, idx_type, levels, float_frame):
134134
idx = idx_type(keys)
135135
idx_check = list(idx_type(keys))
136136

137-
result = frame[idx]
137+
if isinstance(idx, (set, dict)):
138+
with tm.assert_produces_warning(FutureWarning):
139+
result = frame[idx]
140+
else:
141+
result = frame[idx]
138142

139143
expected = frame.loc[:, idx_check]
140144
expected.columns.names = frame.columns.names
@@ -143,7 +147,8 @@ def test_getitem_listlike(self, idx_type, levels, float_frame):
143147

144148
idx = idx_type(keys + [missing])
145149
with pytest.raises(KeyError, match="not in index"):
146-
frame[idx]
150+
with tm.assert_produces_warning(FutureWarning):
151+
frame[idx]
147152

148153
def test_getitem_iloc_generator(self):
149154
# GH#39614
@@ -388,3 +393,14 @@ def test_getitem_datetime_slice(self):
388393
),
389394
)
390395
tm.assert_frame_equal(result, expected)
396+
397+
398+
class TestGetitemDeprecatedIndexers:
399+
@pytest.mark.parametrize("key", [{"a", "b"}, {"a": "a"}])
400+
def test_getitem_dict_and_set_deprecated(self, key):
401+
# GH#42825
402+
df = DataFrame(
403+
[[1, 2], [3, 4]], columns=MultiIndex.from_tuples([("a", 1), ("b", 2)])
404+
)
405+
with tm.assert_produces_warning(FutureWarning):
406+
df[key]

pandas/tests/frame/indexing/test_indexing.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1526,3 +1526,65 @@ def test_loc_iloc_setitem_non_categorical_rhs(
15261526
# "c" not part of the categories
15271527
with pytest.raises(TypeError, match=msg1):
15281528
indexer(df)[key] = ["c", "c"]
1529+
1530+
1531+
class TestDepreactedIndexers:
1532+
@pytest.mark.parametrize(
1533+
"key", [{1}, {1: 1}, ({1}, "a"), ({1: 1}, "a"), (1, {"a"}), (1, {"a": "a"})]
1534+
)
1535+
def test_getitem_dict_and_set_deprecated(self, key):
1536+
# GH#42825
1537+
df = DataFrame([[1, 2], [3, 4]], columns=["a", "b"])
1538+
with tm.assert_produces_warning(FutureWarning):
1539+
df.loc[key]
1540+
1541+
@pytest.mark.parametrize(
1542+
"key",
1543+
[
1544+
{1},
1545+
{1: 1},
1546+
(({1}, 2), "a"),
1547+
(({1: 1}, 2), "a"),
1548+
((1, 2), {"a"}),
1549+
((1, 2), {"a": "a"}),
1550+
],
1551+
)
1552+
def test_getitem_dict_and_set_deprecated_multiindex(self, key):
1553+
# GH#42825
1554+
df = DataFrame(
1555+
[[1, 2], [3, 4]],
1556+
columns=["a", "b"],
1557+
index=MultiIndex.from_tuples([(1, 2), (3, 4)]),
1558+
)
1559+
with tm.assert_produces_warning(FutureWarning):
1560+
df.loc[key]
1561+
1562+
@pytest.mark.parametrize(
1563+
"key", [{1}, {1: 1}, ({1}, "a"), ({1: 1}, "a"), (1, {"a"}), (1, {"a": "a"})]
1564+
)
1565+
def test_setitem_dict_and_set_deprecated(self, key):
1566+
# GH#42825
1567+
df = DataFrame([[1, 2], [3, 4]], columns=["a", "b"])
1568+
with tm.assert_produces_warning(FutureWarning):
1569+
df.loc[key] = 1
1570+
1571+
@pytest.mark.parametrize(
1572+
"key",
1573+
[
1574+
{1},
1575+
{1: 1},
1576+
(({1}, 2), "a"),
1577+
(({1: 1}, 2), "a"),
1578+
((1, 2), {"a"}),
1579+
((1, 2), {"a": "a"}),
1580+
],
1581+
)
1582+
def test_setitem_dict_and_set_deprecated_multiindex(self, key):
1583+
# GH#42825
1584+
df = DataFrame(
1585+
[[1, 2], [3, 4]],
1586+
columns=["a", "b"],
1587+
index=MultiIndex.from_tuples([(1, 2), (3, 4)]),
1588+
)
1589+
with tm.assert_produces_warning(FutureWarning):
1590+
df.loc[key] = 1

pandas/tests/indexing/multiindex/test_loc.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -339,8 +339,11 @@ def convert_nested_indexer(indexer_type, keys):
339339
convert_nested_indexer(indexer_type, k)
340340
for indexer_type, k in zip(types, keys)
341341
)
342-
343-
result = df.loc[indexer, "Data"]
342+
if indexer_type_1 is set or indexer_type_2 is set:
343+
with tm.assert_produces_warning(FutureWarning):
344+
result = df.loc[indexer, "Data"]
345+
else:
346+
result = df.loc[indexer, "Data"]
344347
expected = Series(
345348
[1, 2, 4, 5], name="Data", index=MultiIndex.from_product(keys)
346349
)

pandas/tests/series/indexing/test_getitem.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -696,3 +696,19 @@ def test_duplicated_index_getitem_positional_indexer(index_vals):
696696
s = Series(range(5), index=list(index_vals))
697697
result = s[3]
698698
assert result == 3
699+
700+
701+
class TestGetitemDeprecatedIndexers:
702+
@pytest.mark.parametrize("key", [{1}, {1: 1}])
703+
def test_getitem_dict_and_set_deprecated(self, key):
704+
# GH#42825
705+
ser = Series([1, 2, 3])
706+
with tm.assert_produces_warning(FutureWarning):
707+
ser[key]
708+
709+
@pytest.mark.parametrize("key", [{1}, {1: 1}])
710+
def test_setitem_dict_and_set_deprecated(self, key):
711+
# GH#42825
712+
ser = Series([1, 2, 3])
713+
with tm.assert_produces_warning(FutureWarning):
714+
ser[key] = 1

pandas/tests/series/indexing/test_indexing.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from pandas import (
99
DataFrame,
1010
IndexSlice,
11+
MultiIndex,
1112
Series,
1213
Timedelta,
1314
Timestamp,
@@ -318,3 +319,33 @@ def test_frozenset_index():
318319
assert s[idx1] == 2
319320
s[idx1] = 3
320321
assert s[idx1] == 3
322+
323+
324+
class TestDepreactedIndexers:
325+
@pytest.mark.parametrize("key", [{1}, {1: 1}])
326+
def test_getitem_dict_and_set_deprecated(self, key):
327+
# GH#42825
328+
ser = Series([1, 2])
329+
with tm.assert_produces_warning(FutureWarning):
330+
ser.loc[key]
331+
332+
@pytest.mark.parametrize("key", [{1}, {1: 1}, ({1}, 2), ({1: 1}, 2)])
333+
def test_getitem_dict_and_set_deprecated_multiindex(self, key):
334+
# GH#42825
335+
ser = Series([1, 2], index=MultiIndex.from_tuples([(1, 2), (3, 4)]))
336+
with tm.assert_produces_warning(FutureWarning):
337+
ser.loc[key]
338+
339+
@pytest.mark.parametrize("key", [{1}, {1: 1}])
340+
def test_setitem_dict_and_set_deprecated(self, key):
341+
# GH#42825
342+
ser = Series([1, 2])
343+
with tm.assert_produces_warning(FutureWarning):
344+
ser.loc[key] = 1
345+
346+
@pytest.mark.parametrize("key", [{1}, {1: 1}, ({1}, 2), ({1: 1}, 2)])
347+
def test_setitem_dict_and_set_deprecated_multiindex(self, key):
348+
# GH#42825
349+
ser = Series([1, 2], index=MultiIndex.from_tuples([(1, 2), (3, 4)]))
350+
with tm.assert_produces_warning(FutureWarning):
351+
ser.loc[key] = 1

0 commit comments

Comments
 (0)