从 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,然后重新索引(我的实际代码更加混乱,因为列列表不是这样易于获取)。我很确定必须有一些ix
或xs
这样做的方式,但我尝试的一切都导致了错误。
【问题讨论】:
你试过用字典吗? 不,我没有。你的意思是更快地构建MultiIndex?如果是这样,那不是重点-我想避免它并直接使用data.xs(['a', 'c'], axis=1, level=1)
之类的内容进行索引
你有这个级别作为第二级而不是第一级的原因吗?
对于我拥有的数据类型,我在视觉上更直观。另外,我想学习如何通用地做到这一点 - 对于任意级别。
在更高版本的 pandas 中,您可以使用 loc
和 pd.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】:
ix
和 select
已弃用!
使用pd.IndexSlice
使loc
成为比ix
和select
更可取的选项。
DataFrame.loc
与 pd.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.IndexSlice
和 670 µs ± 4.49 µs per loop
和 data.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】:
您可以使用loc
或ix
中的任何一个,我将使用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 指定列