Подтвердить что ты не робот

Как срезать один мультииндекс DataFrame с помощью MultiIndex другого

У меня есть pandas dataframe с 3 уровнями MultiIndex. Я пытаюсь вывести строки этого блока данных в соответствии со списком значений, которые соответствуют двум уровням.

У меня есть что-то вроде этого:

ix = pd.MultiIndex.from_product([[1, 2, 3], ['foo', 'bar'], ['baz', 'can']], names=['a', 'b', 'c'])
data = np.arange(len(ix))
df = pd.DataFrame(data, index=ix, columns=['hi'])
print(df)

           hi
a b   c      
1 foo baz   0
      can   1
  bar baz   2
      can   3
2 foo baz   4
      can   5
  bar baz   6
      can   7
3 foo baz   8
      can   9
  bar baz  10
      can  11

Теперь я хочу взять все строки, в которых индексы "b" и "c" находятся в этом индексе:

ix_use = pd.MultiIndex.from_tuples([('foo', 'can'), ('bar', 'baz')], names=['b', 'c'])

то есть. значения hi, имеющие ('foo', 'can') или ('bar', 'baz') в уровнях b и c соответственно: (1, 2, 5, 6, 9, 10).

Итак, я хотел бы взять slice(None) на первом уровне и вытащить определенные кортежи на втором и третьем уровнях.

Первоначально я думал, что передача объекта с несколькими индексами в .loc выдержит значения/уровни, которые я хотел, но это не работает. Какой лучший способ сделать что-то подобное?

4b9b3361

Ответ 1

Вот способ получить этот фрагмент:

df.sort_index(inplace=True)
idx = pd.IndexSlice
df.loc[idx[:, ('foo','bar'), 'can'], :]

дает

           hi
a b   c      
1 bar can   3
  foo can   1
2 bar can   7
  foo can   5
3 bar can  11
  foo can   9

Обратите внимание, что вам может понадобиться отсортировать MultiIndex, прежде чем вы сможете его отрезать. Хорошо pandas достаточно любезно предупредить, если вам нужно это сделать:

KeyError: 'MultiIndex Slicing requires the index to be fully lexsorted tuple len (3), lexsort depth (1)'

Вы можете узнать больше о том, как использовать slicers в docs

Если по какой-либо причине использование slicers не является вариантом, это способ получить один и тот же фрагмент с помощью метода .isin():

df[df.index.get_level_values('b').isin(ix_use.get_level_values(0)) & df.index.get_level_values('c').isin(ix_use.get_level_values(1))]

Это явно не так красно.

UPDATE:

Для условий, которые вы обновили здесь, это способ сделать это:

cond1 = (df.index.get_level_values('b').isin(['foo'])) & (df.index.get_level_values('c').isin(['can']))
cond2 = (df.index.get_level_values('b').isin(['bar'])) & (df.index.get_level_values('c').isin(['baz']))
df[cond1 | cond2]

производства:

           hi
a b   c      
1 foo can   1
  bar baz   2
2 foo can   5
  bar baz   6
3 foo can   9
  bar baz  10

Ответ 2

Я бы рекомендовал метод query(), как в этот Q & A.

Просто используя это, что я считаю более естественным способом выразить:

In [27]: df.query("(b == 'foo' and c == 'can') or (b == 'bar' and c == 'baz')")
Out[27]: 
           hi
a b   c      
1 foo can   1
  bar baz   2
2 foo can   5
  bar baz   6
3 foo can   9
  bar baz  10

Ответ 3

Мне интересно, что это не работает:

In [45]: df.loc[(idx[:, 'foo', 'can'], idx[:, 'bar', 'baz']), ]
Out[45]: 
           hi
a b   c      
1 bar baz   2
      can   3
  foo baz   0
      can   1
2 bar baz   6
      can   7
  foo baz   4
      can   5
3 bar baz  10
      can  11
  foo baz   8
      can   9

Кажется, что это похоже на "должно". В любом случае, здесь разумное решение:

Предположим, что кортежи, которые вы хотите отрезать, находятся в индексе другого DataFrame (поскольку это похоже на то, что они, вероятно, в вашем случае!).

In [53]: ix_use = pd.MultiIndex.from_tuples([('foo', 'can'), ('bar', 'baz')], names=['b', 'c'])
In [55]: other = pd.DataFrame(dict(a=1), index=ix_use)
In [56]: other
Out[56]: 
         a
b   c     
foo can  1
bar baz  1

Теперь, чтобы отрезать df индексом other, мы можем использовать тот факт, что .loc/.ix позволяет вам указывать список кортежей (см. последний пример здесь).

Сначала создадим список кортежей, который мы хотим:

In [13]: idx = [(x, ) + y for x in df.index.levels[0] for y in other.index.values]
In [14]: idx
Out[14]: 
[(1, 'foo', 'can'),
 (1, 'bar', 'baz'),
 (2, 'foo', 'can'),
 (2, 'bar', 'baz'),
 (3, 'foo', 'can'),
 (3, 'bar', 'baz')]

Теперь мы можем передать этот список в .ix или .loc:

In [17]: df.ix[idx]
Out[17]: 
           hi
a b   c      
1 foo can   1
  bar baz   2
2 foo can   5
  bar baz   6
3 foo can   9
  bar baz  10