如何使用另一个MultiIndex DataFrame的MultiIndex来切片一个MultiIndex DataFrame

20

我有一个带有3层MultiIndex的pandas dataframe。我试图根据与两个级别对应的值列表提取此数据框的行。

我的代码大致如下:

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'),在第二和第三层上分别使用bc(1, 2, 5, 6, 9, 10)

因此,我想在第一级上进行slice(None),并在第二和第三级中提取特定的元组。

最初,我认为将多索引对象传递给.loc会提取我想要的值/级别,但这不起作用。如何最好地执行此类操作?


我尝试了几种不同的方法来让它工作。我认为我找到了一个不错的解决方法,因为目前似乎有点困难。试试看吧! - LondonRob
4个回答

21

这里是获取这个切片的方法:

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)'
您可以在文档中了解更多有关如何使用切片器的信息。
如果由于某种原因不能使用切片器,则可以使用 .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))]

这显然不够简明。

更新:

对于您在此处更新的条件,有一种方法可以实现:

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

这非常接近我要找的东西,但我应该更清楚地表达问题。实际上,我需要一个取决于级别“b”值的级别“c”的值。例如,每当级别“b”为“foo”时,我想要级别“c”为“can”的值,每当级别“b”为“bar”时,我想要级别“c”为“baz”的值。 - choldgraf
更新了答案,包含这两个条件,应该可以给出如何处理此问题的思路。 - Primer

2
我建议使用 query()方法,就像在这个问答中一样。
只需使用这个方法,我认为这是更自然的表达方式:
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

0
import itertools
import pandas as pd
import numpy as np
from pandas import DataFrame as df


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

"""
a = (df.index.get_level_values('b') =='foo')
b = (df.index.get_level_values('c') =='can')
ab = (a & b)
dab = df[a & b]
print(dab)

"""
          hi
a b   c      
1 foo can   1
2 foo can   5
3 foo can   9
"""
a1 = (df.index.get_level_values('b') =='bar')
b1 = (df.index.get_level_values('c') =='baz')
ab1 = (a1 & b1)
dab1 = df[a1 & b1]
print(dab1)
"""
           hi
a b   c      
1 bar baz   2
2 bar baz   6
3 bar baz  10
"""

abab = df[ab | ab1]
print(abab)
"""
           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
"""

0

我觉得有趣的是这个不起作用:

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

现在要按other的索引切片df,我们可以利用.loc/.ix允许您提供元组列表的事实(请参见最后一个示例here)。

首先让我们构建我们想要的元组列表:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接