Skip to content

Commit e29d261

Browse files
committed
BUG: fix KeyError with list of a single, missing, element
closes pandas-dev#27148
1 parent af7f2ef commit e29d261

File tree

4 files changed

+33
-22
lines changed

4 files changed

+33
-22
lines changed

doc/source/whatsnew/v0.25.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -768,6 +768,7 @@ Indexing
768768
- Bug in which :meth:`DataFrame.to_csv` caused a segfault for a reindexed data frame, when the indices were single-level :class:`MultiIndex` (:issue:`26303`).
769769
- Fixed bug where assigning a :class:`arrays.PandasArray` to a :class:`pandas.core.frame.DataFrame` would raise error (:issue:`26390`)
770770
- Allow keyword arguments for callable local reference used in the :meth:`DataFrame.query` string (:issue:`26426`)
771+
- Fixed a ``KeyError`` when indexing a :class:`MultiIndex`` level with a list containing exactly one label, which is missing (:issue:`27148`)
771772

772773

773774
Missing

pandas/core/indexing.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1818,7 +1818,7 @@ def _getitem_axis(self, key, axis=None):
18181818
"multidimensional key is not "
18191819
"implemented")
18201820

1821-
if (not isinstance(key, tuple) and len(key) > 1 and
1821+
if (not isinstance(key, tuple) and len(key) and
18221822
not isinstance(key[0], tuple)):
18231823
key = tuple([key])
18241824

pandas/tests/indexing/multiindex/test_loc.py

+26-16
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,17 @@ def test_loc_multiindex_missing_label_raises(self):
123123
with pytest.raises(KeyError, match=r"^2$"):
124124
df.loc[2]
125125

126+
# Lists with missing labels do not raise:
127+
expected = df.iloc[[0, 1]]
128+
result = df.loc[[2, 4]]
129+
tm.assert_frame_equal(result, expected)
130+
131+
# ... even when none is found:
132+
expected = df.iloc[[]]
133+
for l in [2], [2, 3]: # GH 27148
134+
result = df.loc[l]
135+
tm.assert_frame_equal(result, expected)
136+
126137
def test_loc_multiindex_too_many_dims_raises(self):
127138
# GH 14885
128139
s = Series(range(8), index=MultiIndex.from_product(
@@ -264,16 +275,16 @@ def convert_nested_indexer(indexer_type, keys):
264275
tm.assert_series_equal(result, expected)
265276

266277

267-
@pytest.mark.parametrize('indexer, is_level1, expected_error', [
268-
([], False, None), # empty ok
269-
(['A'], False, None),
270-
(['A', 'D'], False, None),
271-
(['D'], False, r"\['D'\] not in index"), # not any values found
272-
(pd.IndexSlice[:, ['foo']], True, None),
273-
(pd.IndexSlice[:, ['foo', 'bah']], True, None)
278+
@pytest.mark.parametrize('indexer, level', [
279+
([], 0), # empty ok
280+
(['A'], 0),
281+
(['A', 'D'], 0),
282+
(['D', 'E'], -1), # no values found - fine
283+
(['D'], -1), # same, with single item list: GH 27148
284+
(pd.IndexSlice[:, ['foo']], 1),
285+
(pd.IndexSlice[:, ['foo', 'bah']], 1)
274286
])
275-
def test_loc_getitem_duplicates_multiindex_missing_indexers(indexer, is_level1,
276-
expected_error):
287+
def test_loc_getitem_duplicates_multiindex_missing_indexers(indexer, level):
277288
# GH 7866
278289
# multi-index slicing with missing indexers
279290
idx = MultiIndex.from_product([['A', 'B', 'C'],
@@ -283,21 +294,20 @@ def test_loc_getitem_duplicates_multiindex_missing_indexers(indexer, is_level1,
283294

284295
if indexer == []:
285296
expected = s.iloc[[]]
286-
elif is_level1:
297+
elif level == 1:
287298
expected = Series([0, 3, 6], index=MultiIndex.from_product(
288299
[['A', 'B', 'C'], ['foo']], names=['one', 'two'])).sort_index()
300+
elif level == -1:
301+
# Empty: use idx[:0] as index to have appropriate (hidden) labels
302+
expected = Series([], index=idx[:0], dtype='int64')
289303
else:
290304
exp_idx = MultiIndex.from_product([['A'], ['foo', 'bar', 'baz']],
291305
names=['one', 'two'])
292306
expected = Series(np.arange(3, dtype='int64'),
293307
index=exp_idx).sort_index()
294308

295-
if expected_error is not None:
296-
with pytest.raises(KeyError, match=expected_error):
297-
s.loc[indexer]
298-
else:
299-
result = s.loc[indexer]
300-
tm.assert_series_equal(result, expected)
309+
result = s.loc[indexer]
310+
tm.assert_series_equal(result, expected)
301311

302312

303313
def test_series_loc_getitem_fancy(

pandas/tests/indexing/multiindex/test_slice.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,11 @@ def test_per_axis_per_level_getitem(self):
104104
with pytest.raises(ValueError):
105105
df.loc[(slice(None), np.array([True, False])), :]
106106

107-
# ambiguous cases
108-
# these can be multiply interpreted (e.g. in this case
109-
# as df.loc[slice(None),[1]] as well
110-
with pytest.raises(KeyError, match=r"'\[1\] not in index'"):
111-
df.loc[slice(None), [1]]
107+
# ambiguous notation:
108+
# this is interpreted as slicing on both axes (GH #16396)
109+
result = df.loc[slice(None), [1]]
110+
expected = df.iloc[:, []]
111+
tm.assert_frame_equal(result, expected)
112112

113113
result = df.loc[(slice(None), [1]), :]
114114
expected = df.iloc[[0, 3]]

0 commit comments

Comments
 (0)