Skip to content

Commit 8b34417

Browse files
committed
BUG: fix DataFrame.__getitem__ and .loc with non-list listlikes
close #21294
1 parent 4274b84 commit 8b34417

File tree

3 files changed

+40
-38
lines changed

3 files changed

+40
-38
lines changed

doc/source/whatsnew/v0.23.1.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ Indexing
8585
- Bug in :meth:`Series.reset_index` where appropriate error was not raised with an invalid level name (:issue:`20925`)
8686
- Bug in :func:`interval_range` when ``start``/``periods`` or ``end``/``periods`` are specified with float ``start`` or ``end`` (:issue:`21161`)
8787
- Bug in :meth:`MultiIndex.set_names` where error raised for a ``MultiIndex`` with ``nlevels == 1`` (:issue:`21149`)
88+
- Bug in :meth:`DataFrame.__getitem__` and :meth:`DataFrame.loc` which did not accept columns keys passed as non-list iterables (:issue:`21294`)
8889
-
8990

9091
I/O

pandas/core/frame.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2664,27 +2664,27 @@ def __getitem__(self, key):
26642664
key = com._apply_if_callable(key, self)
26652665

26662666
# shortcut if we are an actual column
2667-
is_mi_columns = isinstance(self.columns, MultiIndex)
26682667
try:
2669-
if key in self.columns and not is_mi_columns:
2670-
return self._getitem_column(key)
2671-
except:
2672-
pass
2668+
return self._getitem_column(key)
2669+
except (KeyError, TypeError, ValueError) as exc:
2670+
exception = exc
26732671

26742672
# see if we can slice the rows
26752673
indexer = convert_to_index_sliceable(self, key)
26762674
if indexer is not None:
26772675
return self._getitem_slice(indexer)
26782676

2679-
if isinstance(key, (Series, np.ndarray, Index, list)):
2677+
# indexing with Boolean dataframe
2678+
if isinstance(key, DataFrame):
2679+
return self._getitem_frame(key)
2680+
elif is_list_like(key) and not isinstance(key, tuple):
26802681
# either boolean or fancy integer index
26812682
return self._getitem_array(key)
2682-
elif isinstance(key, DataFrame):
2683-
return self._getitem_frame(key)
2684-
elif is_mi_columns:
2683+
# partial indexing of MultiIndex columns
2684+
if isinstance(self.columns, MultiIndex):
26852685
return self._getitem_multilevel(key)
2686-
else:
2687-
return self._getitem_column(key)
2686+
2687+
raise exception
26882688

26892689
def _getitem_column(self, key):
26902690
""" return the actual column """

pandas/tests/frame/test_indexing.py

Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -92,45 +92,46 @@ def test_get(self):
9292
result = df.get(None)
9393
assert result is None
9494

95-
def test_getitem_iterator(self):
95+
def test_loc_iterable(self):
9696
idx = iter(['A', 'B', 'C'])
9797
result = self.frame.loc[:, idx]
9898
expected = self.frame.loc[:, ['A', 'B', 'C']]
9999
assert_frame_equal(result, expected)
100100

101-
idx = iter(['A', 'B', 'C'])
102-
result = self.frame.loc[:, idx]
103-
expected = self.frame.loc[:, ['A', 'B', 'C']]
104-
assert_frame_equal(result, expected)
101+
@pytest.mark.parametrize(
102+
"idx_type",
103+
[list, iter, Index, set,
104+
lambda l: dict(zip(l, range(len(l)))),
105+
lambda l: dict(zip(l, range(len(l)))).keys()],
106+
ids=["list", "iter", "Index", "set", "dict", "dict_keys"])
107+
@pytest.mark.parametrize("levels", [1, 2])
108+
def test_getitem_listlike(self, idx_type, levels):
109+
# GH 21294
110+
111+
if levels == 1:
112+
frame, missing = self.frame, 'food'
113+
else:
114+
# MultiIndex columns
115+
frame = DataFrame(randn(8, 3),
116+
columns=Index([('foo', 'bar'), ('baz', 'qux'),
117+
('peek', 'aboo')],
118+
name=('sth', 'sth2')))
119+
missing = ('good', 'food')
105120

106-
def test_getitem_list(self):
107-
self.frame.columns.name = 'foo'
121+
keys = [frame.columns[1], frame.columns[0]]
122+
idx = idx_type(keys)
123+
idx_check = list(idx_type(keys))
108124

109-
result = self.frame[['B', 'A']]
110-
result2 = self.frame[Index(['B', 'A'])]
125+
result = frame[idx]
111126

112-
expected = self.frame.loc[:, ['B', 'A']]
113-
expected.columns.name = 'foo'
127+
expected = frame.loc[:, idx_check]
128+
expected.columns.names = frame.columns.names
114129

115130
assert_frame_equal(result, expected)
116-
assert_frame_equal(result2, expected)
117131

118-
assert result.columns.name == 'foo'
119-
120-
with tm.assert_raises_regex(KeyError, 'not in index'):
121-
self.frame[['B', 'A', 'food']]
132+
idx = idx_type(keys + [missing])
122133
with tm.assert_raises_regex(KeyError, 'not in index'):
123-
self.frame[Index(['B', 'A', 'foo'])]
124-
125-
# tuples
126-
df = DataFrame(randn(8, 3),
127-
columns=Index([('foo', 'bar'), ('baz', 'qux'),
128-
('peek', 'aboo')], name=('sth', 'sth2')))
129-
130-
result = df[[('foo', 'bar'), ('baz', 'qux')]]
131-
expected = df.iloc[:, :2]
132-
assert_frame_equal(result, expected)
133-
assert result.columns.names == ('sth', 'sth2')
134+
frame[idx]
134135

135136
def test_getitem_callable(self):
136137
# GH 12533

0 commit comments

Comments
 (0)