从 pandas MultiIndex 中选择列

Posted

技术标签:

【中文标题】从 pandas MultiIndex 中选择列【英文标题】:Selecting columns from pandas MultiIndex 【发布时间】:2013-08-30 11:44:15 【问题描述】:

我有带有 MultiIndex 列的 DataFrame,如下所示:

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

从第二级仅选择特定列(例如['a', 'c'],而不是范围)的正确、简单方法是什么?

目前我正在这样做:

import itertools
tuples = [i for i in itertools.product(['one', 'two'], ['a', 'c'])]
new_index = pd.MultiIndex.from_tuples(tuples)
print(new_index)
data.reindex_axis(new_index, axis=1)

但是,这感觉不是一个好的解决方案,因为我必须退出 itertools,手动构建另一个 MultiIndex,然后重新索引(我的实际代码更加混乱,因为列列表不是这样易于获取)。我很确定必须有一些ixxs 这样做的方式,但我尝试的一切都导致了错误。

【问题讨论】:

你试过用字典吗? 不,我没有。你的意思是更快地构建MultiIndex?如果是这样,那不是重点-我想避免它并直接使用data.xs(['a', 'c'], axis=1, level=1)之类的内容进行索引 你有这个级别作为第二级而不是第一级的原因吗? 对于我拥有的数据类型,我在视觉上更直观。另外,我想学习如何通用地做到这一点 - 对于任意级别。 在更高版本的 pandas 中,您可以使用 locpd.IndexSlice API,它现在是切片 MultIndexs 的首选方式。请参阅this answer 和 this post. 【参考方案1】:

最直接的方法是.loc:

>>> data.loc[:, (['one', 'two'], ['a', 'b'])]


   one       two     
     a    b    a    b
0  0.4 -0.6 -0.7  0.9
1  0.1  0.4  0.5 -0.3
2  0.7 -1.6  0.7 -0.8
3 -0.9  2.6  1.9  0.6

记住[]() 在处理MultiIndex 对象时具有特殊含义:

(...) 元组被解释为一个多级

(...) 一个列表用于指定多个键[在同一级别]

(...) 一个列表元组引用一个级别中的多个值

当我们编写(['one', 'two'], ['a', 'b']) 时,元组中的第一个列表指定了我们想要的来自MultiIndex 的第一级的所有值。元组中的第二个列表指定了我们想要从 MultiIndex 的第二级获得的所有值。

编辑 1: 另一种可能性是使用slice(None) 来指定我们想要第一级的任何内容(类似于在列表中使用: 进行切片)。然后指定我们想要的第二层的哪些列。

>>> data.loc[:, (slice(None), ["a", "b"])]

   one       two     
     a    b    a    b
0  0.4 -0.6 -0.7  0.9
1  0.1  0.4  0.5 -0.3
2  0.7 -1.6  0.7 -0.8
3 -0.9  2.6  1.9  0.6

如果语法 slice(None) 确实对您有吸引力,那么另一种可能性是使用 pd.IndexSlice,它有助于使用更精细的索引对帧进行切片。

>>> data.loc[:, pd.IndexSlice[:, ["a", "b"]]]

   one       two     
     a    b    a    b
0  0.4 -0.6 -0.7  0.9
1  0.1  0.4  0.5 -0.3
2  0.7 -1.6  0.7 -0.8
3 -0.9  2.6  1.9  0.6

在使用pd.IndexSlice时,我们可以像往常一样使用:对帧进行切片。

来源:MultiIndex / Advanced Indexing,How to use slice(None)

【讨论】:

请注意,生成的 DataFrame 的列名是 a b a b 而不是 a c a c @SilvanMühlemann 我已修复,如果有其他问题,请查看并告诉我!感谢您的帮助。 这是最直观的方式。【参考方案2】:

ixselect 已弃用!

使用pd.IndexSlice 使loc 成为比ixselect 更可取的选项。


DataFrame.locpd.IndexSlice

# Setup
col = pd.MultiIndex.from_arrays([['one', 'one', 'one', 'two', 'two', 'two'],
                                ['a', 'b', 'c', 'a', 'b', 'c']])
data = pd.DataFrame('x', index=range(4), columns=col)
data

  one       two      
    a  b  c   a  b  c
0   x  x  x   x  x  x
1   x  x  x   x  x  x
2   x  x  x   x  x  x
3   x  x  x   x  x  x

data.loc[:, pd.IndexSlice[:, ['a', 'c']]]

  one    two   
    a  c   a  c
0   x  x   x  x
1   x  x   x  x
2   x  x   x  x
3   x  x   x  x

您也可以将axis 参数设置为loc,以明确您从哪个轴开始索引:

data.loc(axis=1)[pd.IndexSlice[:, ['a', 'c']]]

  one    two   
    a  c   a  c
0   x  x   x  x
1   x  x   x  x
2   x  x   x  x
3   x  x   x  x

MultiIndex.get_level_values

调用data.columns.get_level_values 以使用loc 进行过滤是另一种选择:

data.loc[:, data.columns.get_level_values(1).isin(['a', 'c'])]

  one    two   
    a  c   a  c
0   x  x   x  x
1   x  x   x  x
2   x  x   x  x
3   x  x   x  x

这自然允许在单个级别上过滤任何条件表达式。这是一个字典过滤的随机示例:

data.loc[:, data.columns.get_level_values(1) > 'b']

  one two
    c   c
0   x   x
1   x   x
2   x   x
3   x   x

有关切片和过滤 MultiIndex 的更多信息,请访问Select rows in pandas MultiIndex DataFrame。

【讨论】:

两种方法都对我有用,但后者似乎更快。我观察到pd.IndexSlice 的时间是原来的三倍(至少对于我的数据集,它有一个两级列多索引和(3610, 30) 的形状)。 --> pd.IndexSlice670 µs ± 4.49 µs per loopdata.loc[:, data.columns.get_level_values(1).isin(['a', 'b', 'c'])]215 µs ± 3.05 µs per loop 也:pd.IndexSlice 在我的例子中不保留列的顺序 (pandas==1.2.4),第二个可以。 很好的标注,谢谢。【参考方案3】:

这不是很好,但也许:

>>> data
        one                           two                    
          a         b         c         a         b         c
0 -0.927134 -1.204302  0.711426  0.854065 -0.608661  1.140052
1 -0.690745  0.517359 -0.631856  0.178464 -0.312543 -0.418541
2  1.086432  0.194193  0.808235 -0.418109  1.055057  1.886883
3 -0.373822 -0.012812  1.329105  1.774723 -2.229428 -0.617690
>>> data.loc[:,data.columns.get_level_values(1).isin("a", "c")]
        one                 two          
          a         c         a         c
0 -0.927134  0.711426  0.854065  1.140052
1 -0.690745 -0.631856  0.178464 -0.418541
2  1.086432  0.808235 -0.418109  1.886883
3 -0.373822  1.329105  1.774723 -0.617690

会有用吗?

【讨论】:

实际上我认为这是在不创建所有元组的情况下过滤掉任意级别的 MultiIndex 中的标签列表的最佳方式。为了清楚起见,我只想使用loc 为了保持列的顺序,最好使用isin(["a", "b"]) @Peaceful:什么?这不会改变任何事情。 isin 调用的结果是一个 bool Series,其顺序由原始 Series 的顺序决定,而不是 isin 的参数。 我试过了。因为"a", "b" 是字典,所以它给了我按"b", "a" 排序的列。当然,我有不同的列名。发生了什么事? "a", "b" 是一个集合,而不是字典,这与 isin 的工作方式无关。如果您对 pandas 的行为方式有任何疑问,请打开一个新问题,而不是评论一个四年前的答案。【参考方案4】:

在我看来,对Marc P. 的answer using slice 进行即兴演奏会稍微容易一些:

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

data.loc[:, pd.IndexSlice[:, ['a', 'c']]]

        one                 two          
          a         c         a         c
0 -1.731008  0.718260 -1.088025 -1.489936
1 -0.681189  1.055909  1.825839  0.149438
2 -1.674623  0.769062  1.857317  0.756074
3  0.408313  1.291998  0.833145 -0.471879

截至 pandas 0.21 左右,.select is deprecated in favour of .loc。

【讨论】:

【参考方案5】:

要在列索引器的第二级选择所有名为 'a''c' 的列,您可以使用切片器:

>>> data.loc[:, (slice(None), ('a', 'c'))]

        one                 two          
          a         c         a         c
0 -0.983172 -2.495022 -0.967064  0.124740
1  0.282661 -0.729463 -0.864767  1.716009
2  0.942445  1.276769 -0.595756 -0.973924
3  2.182908 -0.267660  0.281916 -0.587835

Here你可以阅读更多关于切片器的信息。

【讨论】:

【参考方案6】:

我认为(现在)有更好的方法,这就是为什么我费心把这个问题(这是谷歌的最高结果)从阴影中拉出来:

data.select(lambda x: x[1] in ['a', 'b'], axis=1)

以一种快速而干净的方式提供您预期的输出:

        one                 two          
          a         b         a         b
0 -0.341326  0.374504  0.534559  0.429019
1  0.272518  0.116542 -0.085850 -0.330562
2  1.982431 -0.420668 -0.444052  1.049747
3  0.162984 -0.898307  1.762208 -0.101360

大部分是不言自明的,[1]指的是等级。

【讨论】:

注:FutureWarning: 'select' is deprecated and will be removed in a future release. You can use .loc[labels.map(crit)] as a replacement.【参考方案7】:

您可以使用locix 中的任何一个,我将使用loc 展示一个示例:

data.loc[:, [('one', 'a'), ('one', 'c'), ('two', 'a'), ('two', 'c')]]

当您有一个 MultiIndexed DataFrame,并且您只想过滤掉一些列时,您必须传递与这些列匹配的元组列表。所以 itertools 的方法非常好,但你不必创建一个新的 MultiIndex:

data.loc[:, list(itertools.product(['one', 'two'], ['a', 'c']))]

【讨论】:

甚至.loc 和类似的都不是必需的。 data[[('one', 'a'), ('one', 'c'), ('two', 'a'), ('two', 'c')]] 也可以。在 0.23.4 测试。

以上是关于从 pandas MultiIndex 中选择列的主要内容,如果未能解决你的问题,请参考以下文章

Pandas:从 DataFrame 分配 MultiIndex 列

从具有多个切片的 pandas MultiIndex 中检索列 [重复]

如何在 pandas DataFrame 中选择具有 MultiIndex 的列(用于 seaborn 散点图)?

Pandas DataFrame 图:从 MultiIndex 中为 secondary_y 指定列

将 MultiIndex DataFrame 格式从列排序到 Pandas 中的变量

如果存在多索引,熊猫将不允许选择列?