`.loc` и`.iloc` с MultiIndex'd DataFrame
При индексировании многоканального DataFrame, похоже, что .iloc
предполагает, что вы ссылаетесь на "внутренний уровень" индекса, а .loc
смотрит на внешний уровень.
Например:
np.random.seed(123)
iterables = [['bar', 'baz', 'foo', 'qux'], ['one', 'two']]
idx = pd.MultiIndex.from_product(iterables, names=['first', 'second'])
df = pd.DataFrame(np.random.randn(8, 4), index=idx)
# .loc looks at the outer index:
print(df.loc['qux'])
# df.loc['two'] would throw KeyError
0 1 2 3
second
one -1.25388 -0.63775 0.90711 -1.42868
two -0.14007 -0.86175 -0.25562 -2.79859
# while .iloc looks at the inner index:
print(df.iloc[-1])
0 -0.14007
1 -0.86175
2 -0.25562
3 -2.79859
Name: (qux, two), dtype: float64
Два вопроса:
Во-первых, почему это? Это умышленное дизайнерское решение?
Во-вторых, могу ли я использовать .iloc
для ссылки на внешний уровень индекса, чтобы получить результат ниже? Я знаю, что сначала могу найти последний элемент индекса с get_level_values
, а затем .loc
-index с этим, но блуждающий, если его можно сделать более непосредственно, либо с фанк-синтаксисом .iloc
, либо с какой-либо существующей функцией, разработанной специально для случая.
# df.iloc[-1]
qux one 0.89071 1.75489 1.49564 1.06939
two -0.77271 0.79486 0.31427 -1.32627
Ответы
Ответ 1
Да, это преднамеренное дизайнерское решение:
.iloc
- строгий позиционный индекс, он не рассматривает структуру на самом деле, только первое фактическое поведение.... .loc
делает принимать учитывайте поведение уровня. [выделено мной]
Таким образом, желаемый результат, заданный в вопросе, не может быть гибким с .iloc
. Ближайшим обходным решением, используемым в нескольких похожих вопросах, является
print(df.loc[[df.index.get_level_values(0)[-1]]])
0 1 2 3
first second
qux one -1.25388 -0.63775 0.90711 -1.42868
two -0.14007 -0.86175 -0.25562 -2.79859
Использование двойных скобок сохранит первый индексный уровень.
Ответ 2
Вы можете использовать:
df.iloc[[6, 7], :]
Out[1]:
0 1 2 3
first second
qux one -1.253881 -0.637752 0.907105 -1.428681
two -0.140069 -0.861755 -0.255619 -2.798589
Где [6, 7]
соответствуют фактическим индексам строк этих строк, как вы можете видеть ниже:
df.reset_index()
Out[]:
first second 0 1 2 3
0 bar one -1.085631 0.997345 0.282978 -1.506295
1 bar two -0.578600 1.651437 -2.426679 -0.428913
2 baz one 1.265936 -0.866740 -0.678886 -0.094709
3 baz two 1.491390 -0.638902 -0.443982 -0.434351
4 foo one 2.205930 2.186786 1.004054 0.386186
5 foo two 0.737369 1.490732 -0.935834 1.175829
6 qux one -1.253881 -0.637752 0.907105 -1.428681
7 qux two -0.140069 -0.861755 -0.255619 -2.798589
Это также работает с df.iloc[[-2, -1], :]
или df.iloc[range(-2, 0), :]
.
EDIT: превращение его в более общее решение
Тогда можно получить общую функцию:
def multindex_iloc(df, index):
label = df.index.levels[0][index]
return df.iloc[df.index.get_loc(label)]
multiindex_loc(df, -1)
Out[]:
0 1 2 3
first second
qux one -1.253881 -0.637752 0.907105 -1.428681
two -0.140069 -0.861755 -0.255619 -2.798589
multiindex_loc(df, 2)
Out[]:
0 1 2 3
first second
foo one 2.205930 2.186786 1.004054 0.386186
two 0.737369 1.490732 -0.935834 1.175829
Ответ 3
Вы можете использовать метод swaplevel
, чтобы переупорядочить индекс перед использованием loc
.
df.swaplevel(0,-1).loc['two']
С примерами данных из вашего вопроса это выглядит так:
>>> df
0 1 2 3
first second
bar one -1.085631 0.997345 0.282978 -1.506295
two -0.578600 1.651437 -2.426679 -0.428913
baz one 1.265936 -0.866740 -0.678886 -0.094709
two 1.491390 -0.638902 -0.443982 -0.434351
foo one 2.205930 2.186786 1.004054 0.386186
two 0.737369 1.490732 -0.935834 1.175829
qux one -1.253881 -0.637752 0.907105 -1.428681
two -0.140069 -0.861755 -0.255619 -2.798589
>>> df.loc['bar']
0 1 2 3
second
one -1.085631 0.997345 0.282978 -1.506295
two -0.578600 1.651437 -2.426679 -0.428913
>>> df.swaplevel().loc['two']
0 1 2 3
first
bar -0.578600 1.651437 -2.426679 -0.428913
baz 1.491390 -0.638902 -0.443982 -0.434351
foo 0.737369 1.490732 -0.935834 1.175829
qux -0.140069 -0.861755 -0.255619 -2.798589
swaplevel
- это метод MultiIndex, но вы можете вызвать его непосредственно в DataFrame.
По умолчанию происходит переключение между двумя внутренними уровнями, поэтому, если у вас есть более двух уровней в мультииндексе, вы должны явно указать уровень, который вы хотите поменять местами.
df.swaplevel(0,-1).loc['two']