Skip to content

Commit 6bcb37e

Browse files
authored
DEPR: ignoring missing labels when indexing on MultiIndex level (pandas-dev#42351)
1 parent f61347b commit 6bcb37e

File tree

4 files changed

+57
-8
lines changed

4 files changed

+57
-8
lines changed

doc/source/whatsnew/v1.4.0.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ Deprecations
154154
- Deprecated ``method`` argument in :meth:`Index.get_loc`, use ``index.get_indexer([label], method=...)`` instead (:issue:`42269`)
155155
- Deprecated treating integer keys in :meth:`Series.__setitem__` as positional when the index is a :class:`Float64Index` not containing the key, a :class:`IntervalIndex` with no entries containing the key, or a :class:`MultiIndex` with leading :class:`Float64Index` level not containing the key (:issue:`33469`)
156156
- Deprecated treating ``numpy.datetime64`` objects as UTC times when passed to the :class:`Timestamp` constructor along with a timezone. In a future version, these will be treated as wall-times. To retain the old behavior, use ``Timestamp(dt64).tz_localize("UTC").tz_convert(tz)`` (:issue:`24559`)
157-
-
157+
- Deprecated ignoring missing labels when indexing with a sequence of labels on a level of a MultiIndex (:issue:`42351`)
158158

159159
.. ---------------------------------------------------------------------------
160160

pandas/core/indexes/multi.py

+12
Original file line numberDiff line numberDiff line change
@@ -3268,6 +3268,18 @@ def _update_indexer(idxr: Index, indexer: Index) -> Index:
32683268
)
32693269
except KeyError:
32703270
# ignore not founds; see discussion in GH#39424
3271+
warnings.warn(
3272+
"The behavior of indexing on a MultiIndex with a nested "
3273+
"sequence of labels is deprecated and will change in a "
3274+
"future version. `series.loc[label, sequence]` will "
3275+
"raise if any members of 'sequence' or not present in "
3276+
"the index's second level. To retain the old behavior, "
3277+
"use `series.index.isin(sequence, level=1)`",
3278+
# TODO: how to opt in to the future behavior?
3279+
# TODO: how to handle IntervalIndex level? (no test cases)
3280+
FutureWarning,
3281+
stacklevel=7,
3282+
)
32713283
continue
32723284
else:
32733285
idxrs = _convert_to_indexer(item_lvl_indexer)

pandas/tests/indexing/multiindex/test_loc.py

+11-4
Original file line numberDiff line numberDiff line change
@@ -398,14 +398,21 @@ def test_loc_getitem_duplicates_multiindex_missing_indexers(indexer, pos):
398398
idx = MultiIndex.from_product(
399399
[["A", "B", "C"], ["foo", "bar", "baz"]], names=["one", "two"]
400400
)
401-
s = Series(np.arange(9, dtype="int64"), index=idx).sort_index()
402-
expected = s.iloc[pos]
401+
ser = Series(np.arange(9, dtype="int64"), index=idx).sort_index()
402+
expected = ser.iloc[pos]
403403

404404
if expected.size == 0 and indexer != []:
405405
with pytest.raises(KeyError, match=str(indexer)):
406-
s.loc[indexer]
406+
ser.loc[indexer]
407407
else:
408-
result = s.loc[indexer]
408+
warn = None
409+
msg = "MultiIndex with a nested sequence"
410+
if indexer == (slice(None), ["foo", "bah"]):
411+
# "bah" is not in idx.levels[1], so is ignored, will raise KeyError
412+
warn = FutureWarning
413+
414+
with tm.assert_produces_warning(warn, match=msg):
415+
result = ser.loc[indexer]
409416
tm.assert_series_equal(result, expected)
410417

411418

pandas/tests/io/formats/style/test_style.py

+33-3
Original file line numberDiff line numberDiff line change
@@ -627,10 +627,27 @@ def test_applymap_subset(self, slice_):
627627
def test_applymap_subset_multiindex(self, slice_):
628628
# GH 19861
629629
# edited for GH 33562
630+
warn = None
631+
msg = "indexing on a MultiIndex with a nested sequence of labels"
632+
if (
633+
isinstance(slice_[-1], tuple)
634+
and isinstance(slice_[-1][-1], list)
635+
and "C" in slice_[-1][-1]
636+
):
637+
warn = FutureWarning
638+
elif (
639+
isinstance(slice_[0], tuple)
640+
and isinstance(slice_[0][1], list)
641+
and 3 in slice_[0][1]
642+
):
643+
warn = FutureWarning
644+
630645
idx = MultiIndex.from_product([["a", "b"], [1, 2]])
631646
col = MultiIndex.from_product([["x", "y"], ["A", "B"]])
632647
df = DataFrame(np.random.rand(4, 4), columns=col, index=idx)
633-
df.style.applymap(lambda x: "color: red;", subset=slice_).render()
648+
649+
with tm.assert_produces_warning(warn, match=msg, check_stacklevel=False):
650+
df.style.applymap(lambda x: "color: red;", subset=slice_).render()
634651

635652
def test_applymap_subset_multiindex_code(self):
636653
# https://github.com/pandas-dev/pandas/issues/25858
@@ -1438,6 +1455,19 @@ def test_non_reducing_multi_slice_on_multiindex(self, slice_):
14381455
idxs = MultiIndex.from_product([["U", "V"], ["W", "X"], ["Y", "Z"]])
14391456
df = DataFrame(np.arange(64).reshape(8, 8), columns=cols, index=idxs)
14401457

1441-
expected = df.loc[slice_]
1442-
result = df.loc[non_reducing_slice(slice_)]
1458+
msg = "indexing on a MultiIndex with a nested sequence of labels"
1459+
warn = None
1460+
for lvl in [0, 1]:
1461+
key = slice_[lvl]
1462+
if isinstance(key, tuple):
1463+
for subkey in key:
1464+
if isinstance(subkey, list) and "-" in subkey:
1465+
# not present in the index level, ignored, will raise in future
1466+
warn = FutureWarning
1467+
1468+
with tm.assert_produces_warning(warn, match=msg):
1469+
expected = df.loc[slice_]
1470+
1471+
with tm.assert_produces_warning(warn, match=msg):
1472+
result = df.loc[non_reducing_slice(slice_)]
14431473
tm.assert_frame_equal(result, expected)

0 commit comments

Comments
 (0)