使用多级索引列选择行

5

我有一个带有多级索引列的数据框,就像这样:

col = pd.MultiIndex.from_arrays([['one', '', '', 'two', 'two', 'two'],
                                ['a', 'b', 'c', 'd', 'e', 'f']])
data = pd.DataFrame(np.random.randn(4, 6), columns=col)
data

enter image description here

我希望能够选择所有行,其中一级列中的值通过某个测试。如果列上没有多重索引,我会这样说:
data[data['d']<1]

当然,在多重索引上这种方法会失败。第一级索引是唯一的,因此我不想指定第0级索引,只需要第1级。我想返回上面的表格,但是缺少d>1的第1行。


1
你的示例数据框代码与你的截图不同。 - Erfan
截图显示了代码在Jupyter中的输出结果。 - Andy Wilson
4个回答

3
如果第二级id中的值是唯一的,则需要将掩码从一个列数据框转换为Series。下面是使用DataFrame.squeeze实现的可能解决方案:
np.random.seed(2019)
col = pd.MultiIndex.from_arrays([['one', '', '', 'two', 'two', 'two'],
                                ['a', 'b', 'c', 'd', 'e', 'f']])
data = pd.DataFrame(np.random.randn(4, 6), columns=col)


print (data.xs('d', axis=1, level=1))
        two
0  1.331864
1  0.953490
2 -0.189313
3  0.064969

print (data.xs('d', axis=1, level=1).squeeze())
0    1.331864
1    0.953490
2   -0.189313
3    0.064969
Name: two, dtype: float64

print (data.xs('d', axis=1, level=1).squeeze().lt(1))
0    False
1     True
2     True
3     True
Name: two, dtype: bool

df = data[data.xs('d', axis=1, level=1).squeeze().lt(1)]

使用DataFrame.iloc进行替代:

df = data[data.xs('d', axis=1, level=1).iloc[:, 0].lt(1)]

print (df)
        one                           two                    
          a         b         c         d         e         f
1  0.573761  0.287728 -0.235634  0.953490 -1.689625 -0.344943
2  0.016905 -0.514984  0.244509 -0.189313  2.672172  0.464802
3  0.845930 -0.503542 -0.963336  0.064969 -3.205040  1.054969

如果使用MultiIndex进行选择后,可以获取多列数据,例如这里通过c级别进行选择:
np.random.seed(2019)
col = pd.MultiIndex.from_arrays([['one', '', '', 'two', 'two', 'two'],
                                ['a', 'b', 'c', 'a', 'b', 'c']])
data = pd.DataFrame(np.random.randn(4, 6), columns=col)

首先使用DataFrame.xs进行选择,再用DataFrame.lt进行比较(小于号:<)。

print (data.xs('c', axis=1, level=1))
                  two
0  1.481278  0.685609
1 -0.235634 -0.344943
2  0.244509  0.464802
3 -0.963336  1.054969

m = data.xs('c', axis=1, level=1).lt(1)
#alternative
#m = data.xs('c', axis=1, level=1) < 1
print (m)
            two
0  False   True
1   True   True
2   True   True
3   True  False

然后通过DataFrame.any测试每行是否至少有一个True,并通过布尔索引进行过滤:

df1 = data[m.any(axis=1)]
print (df1)
        one                           two                    
          a         b         c         a         b         c
0 -0.217679  0.821455  1.481278  1.331864 -0.361865  0.685609
1  0.573761  0.287728 -0.235634  0.953490 -1.689625 -0.344943
2  0.016905 -0.514984  0.244509 -0.189313  2.672172  0.464802
3  0.845930 -0.503542 -0.963336  0.064969 -3.205040  1.054969

或者通过过滤使用DataFrame.any测试每行是否全部为True

df1 = data[m.all(axis=1)]
print (df1)
        one                           two                    
          a         b         c         a         b         c
1  0.573761  0.287728 -0.235634  0.953490 -1.689625 -0.344943
2  0.016905 -0.514984  0.244509 -0.189313  2.672172  0.464802

2
根据您提供的数据,使用xssqueeze的组合可以帮助进行过滤。这基于一级条目是唯一的假设,正如您在问题中所指出的:
np.random.seed(2019)
col = pd.MultiIndex.from_arrays([['one', '', '', 'two', 'two', 'two'],
                                ['a', 'b', 'c', 'd', 'e', 'f']])
data = pd.DataFrame(np.random.randn(4, 6), columns=col)
data

       one                                         two
         a          b         c            d            e          f
0   -0.217679   0.821455    1.481278    1.331864    -0.361865   0.685609
1   0.573761    0.287728    -0.235634   0.953490    -1.689625   -0.344943
2   0.016905    -0.514984   0.244509    -0.189313   2.672172    0.464802
3   0.845930    -0.503542   -0.963336   0.064969    -3.205040   1.054969

说你想筛选出小于1的d:
#squeeze turns it into a series, making it easy to pass to loc via boolean indexing
condition = data.xs('d',axis=1,level=1).lt(1).squeeze()
#or you could use loc : 
# condition = data.loc(axis=1)[:,'d'].lt(1).squeeze()

data.loc[condition]

        one                                              two
       a             b           c         d            e           f
1   0.573761    0.287728    -0.235634   0.953490    -1.689625   -0.344943
2   0.016905    -0.514984   0.244509    -0.189313   2.672172    0.464802
3   0.845930    -0.503542   -0.963336   0.064969    -3.205040   1.054969

我的错,我刚刚注意到你已经挤进去了。这不是你之前的答案中提到的。 - sammywemmy

1

我认为这可以使用query完成;

data.query("some_column <1")

get_level_values

data[data.index.get_level_values('some_column') < 1]

1
你试过了吗? - sammywemmy
嗨。是的,我试过了。data.query("some_column <1")会产生错误name 'd' is not defineddata[data.index.get_level_values('some_column') < 1]会产生错误Requested level (d) does not match index name (None) - Andy Wilson
我应该这样说:data.query("d <1") 会产生错误 name 'd' is not defined,而 data[data.index.get_level_values('d') < 1] 则会产生错误 Requested level (d) does not match index name (None) - Andy Wilson
1
你能看一下这些文档吗: https://pandas.pydata.org/pandas-docs/version/0.15.0/indexing.html#the-query-method-experimental - Yagiz Degirmenci

0

感谢大家的帮助。通常情况下,解决问题的具体答案并不像你在尝试解决它时所学到的知识那样有趣,我学到了很多关于.query.xs等方面的知识。

然而,最终我采取了一个侧面的方法来解决我的具体问题——即将列复制到一个新变量中,删除索引,进行计算,然后放回原始索引。例如:

cols = data.columns
data..droplevel(level=1, axis=1)
# do calculations
data.columns = cols

优点在于我可以修改索引并对操作进行修整,但在中间的所有数据操作都使用了我熟悉的习惯用语。

总有一天我会坐下来详细阅读关于多索引的内容。


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