多重索引/高级索引
Note
这篇文章的结构如下:
- OP中提出的问题将一一解决
- 对于每个问题,将演示一种或多种适用于解决该问题并获得预期结果的方法。
Notes(很像这个)将包含在内,供有兴趣了解附加功能、实现细节的读者使用,
和其他信息粗略地讨论当前的主题。这些注释已
通过搜索文档并发现各种晦涩难懂的内容进行编译
功能,以及我自己(诚然有限)的经验。
所有代码示例均已在以下平台上创建和测试熊猫 v0.23.4,python3.7。如果某些内容不清楚,或者事实上不正确,或者如果您没有
找到适合您的用例的解决方案,请随时
建议编辑、在评论中请求澄清或打开新的
问题,...如适用。
这里介绍一些我们会经常重温的常见成语(以下简称四大成语)
-
DataFrame.loc- 按标签选择的通用解决方案(+pd.IndexSlice对于涉及切片的更复杂的应用)
-
DataFrame.xs- 从系列/数据帧中提取特定的横截面。
-
DataFrame.query- 动态指定切片和/或过滤操作(即,作为动态计算的表达式。比其他场景更适用于某些场景。另请参阅文档的这一部分用于对多索引进行查询。
-
使用生成的掩码进行布尔索引MultiIndex.get_level_values(通常与Index.isin,尤其是在使用多个值进行过滤时)。这在某些情况下也非常有用。
根据四个习语来研究各种切片和过滤问题将有助于更好地理解什么可以应用于特定情况。理解这一点非常重要,即并非所有习语在每种情况下都同样有效(如果有的话)。如果某个习语没有被列为以下问题的潜在解决方案,则意味着该习语无法有效地应用于该问题。
问题1
如何选择“一”级中具有“a”的行?
col
one two
a t 0
u 1
v 2
w 3
您可以使用loc
,作为适用于大多数情况的通用解决方案:
df.loc[['a']]
此时,如果你得到
TypeError: Expected tuple, got str
这意味着您使用的是旧版本的 pandas。考虑升级!否则,使用df.loc[('a', slice(None)), :]
.
或者,您可以使用xs
在这里,因为我们正在提取单个横截面。请注意levels
and axis
参数(这里可以假设合理的默认值)。
df.xs('a', level=0, axis=0, drop_level=False)
# df.xs('a', drop_level=False)
在这里,drop_level=False
需要论证来防止xs
从结果中删除级别“一”(我们切片的级别)。
这里的另一个选择是使用query
:
df.query("one == 'a'")
如果索引没有名称,则需要将查询字符串更改为"ilevel_0 == 'a'"
.
最后,使用get_level_values
:
df[df.index.get_level_values('one') == 'a']
# If your levels are unnamed, or if you need to select by position (not label),
# df[df.index.get_level_values(0) == 'a']
此外,我如何才能在输出中降低级别“1”?
col
two
t 0
u 1
v 2
w 3
这可以是easily使用任一完成
df.loc['a'] # Notice the single string argument instead the list.
Or,
df.xs('a', level=0, axis=0, drop_level=True)
# df.xs('a')
请注意,我们可以省略drop_level
参数(假设是True
默认情况下)。
Note
您可能会注意到,过滤后的 DataFrame 可能仍然具有所有级别,即使它们在打印 DataFrame 时没有显示。例如,
v = df.loc[['a']]
print(v)
col
one two
a t 0
u 1
v 2
w 3
print(v.index)
MultiIndex(levels=[['a', 'b', 'c', 'd'], ['t', 'u', 'v', 'w']],
labels=[[0, 0, 0, 0], [0, 1, 2, 3]],
names=['one', 'two'])
您可以使用以下方法摆脱这些级别MultiIndex.remove_unused_levels:
v.index = v.index.remove_unused_levels()
print(v.index)
MultiIndex(levels=[['a'], ['t', 'u', 'v', 'w']],
labels=[[0, 0, 0, 0], [0, 1, 2, 3]],
names=['one', 'two'])
问题 1b
如何在“二”级上对具有值“t”的所有行进行切片?
col
one two
a t 0
b t 4
t 8
d t 12
直觉上,你会想要一些涉及slice():
df.loc[(slice(None), 't'), :]
它确实有效!™ 但它很笨重。我们可以使用更自然的切片语法pd.IndexSlice
API在这里。
idx = pd.IndexSlice
df.loc[idx[:, 't'], :]
这干净得多。
Note
为什么尾随切片:
需要跨列吗?这是因为,loc
可用于沿两个轴选择和切片(axis=0
or
axis=1
)。没有明确说明切片是哪个轴
是要完成的,操作变得不明确。见图中红色大框切片的文档.
如果你想消除任何歧义,loc
接受一个axis
范围:
df.loc(axis=0)[pd.IndexSlice[:, 't']]
如果没有axis
参数(即,只需执行df.loc[pd.IndexSlice[:, 't']]
),假设切片是在列上进行的,
和一个KeyError
在这种情况下会被提出。
这记录在slicers。然而,出于本文的目的,我们将明确指定所有轴。
With xs
, it is
df.xs('t', axis=0, level=1, drop_level=False)
With query
, it is
df.query("two == 't'")
# Or, if the first level has no name,
# df.query("ilevel_1 == 't'")
最后,与get_level_values
,你可以这样做
df[df.index.get_level_values('two') == 't']
# Or, to perform selection by position/integer,
# df[df.index.get_level_values(1) == 't']
一切都达到相同的效果。
问题2
如何选择与级别“one”中的项目“b”和“d”相对应的行?
col
one two
b t 4
u 5
v 6
w 7
t 8
d w 11
t 12
u 13
v 14
w 15
使用 loc,可以通过指定列表以类似的方式完成此操作。
df.loc[['b', 'd']]
为了解决上述选择“b”和“d”的问题,还可以使用query
:
items = ['b', 'd']
df.query("one in @items")
# df.query("one == @items", parser='pandas')
# df.query("one in ['b', 'd']")
# df.query("one == ['b', 'd']", parser='pandas')
Note
是的,默认解析器是'pandas'
,但重要的是要强调这种语法不是传统的 python 语法。这
Pandas 解析器生成的解析树与
表达。这样做是为了让一些操作更加直观
指定。欲了解更多信息,请阅读我的帖子使用 pd.eval() 在 pandas 中进行动态表达式评估.
与get_level_values
+ Index.isin
:
df[df.index.get_level_values("one").isin(['b', 'd'])]
问题 2b
我如何获得与级别“二”中的“t”和“w”相对应的所有值?
col
one two
a t 0
w 3
b t 4
w 7
t 8
d w 11
t 12
w 15
With loc
, 这个有可能only和这个结合pd.IndexSlice
.
df.loc[pd.IndexSlice[:, ['t', 'w']], :]
第一个冒号:
in pd.IndexSlice[:, ['t', 'w']]
意味着切过第一层。随着所查询级别的深度增加,您将需要指定更多切片,每个级别被切片一个。您不需要指定更多级别beyond然而,被切片的那个。
With query
, 这是
items = ['t', 'w']
df.query("two in @items")
# df.query("two == @items", parser='pandas')
# df.query("two in ['t', 'w']")
# df.query("two == ['t', 'w']", parser='pandas')
With get_level_values
and Index.isin
(与上面类似):
df[df.index.get_level_values('two').isin(['t', 'w'])]
问题3
如何检索横截面,即具有特定值的单行
对于索引从df
?具体来说,我如何取回十字架
的部分('c', 'u')
,由下式给出
col
one two
c u 9
Use loc
通过指定键的元组:
df.loc[('c', 'u'), :]
Or,
df.loc[pd.IndexSlice[('c', 'u')]]
Note
此时,你可能会遇到PerformanceWarning看起来像这样:
PerformanceWarning: indexing past lexsort depth may impact performance.
这仅意味着您的索引未排序。 pandas 取决于正在排序的索引(在本例中,按字典顺序排序,因为我们正在处理字符串值)以实现最佳搜索和检索。一个快速的解决方法是对你的
DataFrame 提前使用DataFrame.sort_index。如果您打算这样做,从性能的角度来看,这是特别理想的
串联多个此类查询:
df_sort = df.sort_index()
df_sort.loc[('c', 'u')]
您还可以使用MultiIndex.is_lexsorted()检查索引是否
是否已排序。该函数返回True
or False
因此。
可以调用该函数来判断是否需要额外排序
步骤是否需要。
With xs
,这又是简单地传递一个元组作为第一个参数,并将所有其他参数设置为适当的默认值:
df.xs(('c', 'u'))
With query
,事情变得有点笨拙:
df.query("one == 'c' and two == 'u'")
您现在可以看到,这将相对难以概括。但对于这个特定问题仍然可以。
通过跨越多个级别的访问,get_level_values
仍然可以使用,但不推荐:
m1 = (df.index.get_level_values('one') == 'c')
m2 = (df.index.get_level_values('two') == 'u')
df[m1 & m2]
问题4
如何选择对应的两行('c', 'u')
, and ('a', 'w')
?
col
one two
c u 9
a w 3
With loc
,这仍然很简单:
df.loc[[('c', 'u'), ('a', 'w')]]
# df.loc[pd.IndexSlice[[('c', 'u'), ('a', 'w')]]]
With query
,您将需要通过迭代横截面和级别来动态生成查询字符串:
cses = [('c', 'u'), ('a', 'w')]
levels = ['one', 'two']
# This is a useful check to make in advance.
assert all(len(levels) == len(cs) for cs in cses)
query = '(' + ') or ('.join([
' and '.join([f"({l} == {repr(c)})" for l, c in zip(levels, cs)])
for cs in cses
]) + ')'
print(query)
# ((one == 'c') and (two == 'u')) or ((one == 'a') and (two == 'w'))
df.query(query)
100% 不推荐!但这是可能的。
如果我有多个级别怎么办?
在这种情况下,一种选择是使用droplevel删除您不检查的级别,然后使用isin测试成员资格,然后对最终结果进行布尔索引。
df[df.index.droplevel(unused_level).isin([('c', 'u'), ('a', 'w')])]
问题5
如何检索与级别“一”中的“a”相对应的所有行或
“二”级中的“t”?
col
one two
a t 0
u 1
v 2
w 3
b t 4
t 8
d t 12
这实际上很难做到loc
在保证正确性的同时and仍然保持代码的清晰度。df.loc[pd.IndexSlice['a', 't']]
是不正确的,它被解释为df.loc[pd.IndexSlice[('a', 't')]]
(即选择横截面)。您可能会想到一个解决方案pd.concat
单独处理每个标签:
pd.concat([
df.loc[['a'],:], df.loc[pd.IndexSlice[:, 't'],:]
])
col
one two
a t 0
u 1
v 2
w 3
t 0 # Does this look right to you? No, it isn't!
b t 4
t 8
d t 12
但您会注意到其中一行是重复的。这是因为该行满足两个切片条件,因此出现了两次。相反,你需要做
v = pd.concat([
df.loc[['a'],:], df.loc[pd.IndexSlice[:, 't'],:]
])
v[~v.index.duplicated()]
但是,如果您的 DataFrame 本质上包含重复的索引(您想要的),那么这将不会保留它们。请极其谨慎使用.
With query
,这非常简单:
df.query("one == 'a' or two == 't'")
With get_level_values
,这仍然很简单,但没有那么优雅:
m1 = (df.index.get_level_values('one') == 'a')
m2 = (df.index.get_level_values('two') == 't')
df[m1 | m2]
问题6
如何切割特定的横截面?对于“a”和“b”,我想选择具有子级别“u”和“v”的所有行,并且
对于“d”,我想选择具有子级别“w”的行。
col
one two
a u 1
v 2
b u 5
v 6
d w 11
w 15
这是我添加的一个特殊情况,以帮助理解四个习语的适用性 - 在这种情况下,它们都不会有效地工作,因为切片是very具体的,并且不遵循任何实际模式。
通常,像这样的切片问题需要显式地将键列表传递给loc
。这样做的一种方法是:
keys = [('a', 'u'), ('a', 'v'), ('b', 'u'), ('b', 'v'), ('d', 'w')]
df.loc[keys, :]
如果你想节省一些打字,你会认识到有一种模式可以切片“a”,“b”及其子级别,因此我们可以将切片任务分成两部分concat
结果:
pd.concat([
df.loc[(('a', 'b'), ('u', 'v')), :],
df.loc[('d', 'w'), :]
], axis=0)
“a”和“b”的切片规范稍微干净一些(('a', 'b'), ('u', 'v'))
因为每个级别索引的相同子级别都是相同的。
问题7
如何获取“第二”级值大于 5 的所有行?
col
one two
b 7 4
9 5
c 7 10
d 6 11
8 12
8 13
6 15
这可以使用以下方法完成query
,
df2.query("two > 5")
And get_level_values
.
df2[df2.index.get_level_values('two') > 5]
Note
与此示例类似,我们可以使用这些构造根据任何任意条件进行过滤。一般来说,记住这一点很有用loc
and xs
专门用于基于标签的索引,而query
and
get_level_values
有助于构建一般条件掩模
用于过滤。
奖金问题
如果我需要切片怎么办MultiIndex
column?
实际上,这里的大多数解决方案也适用于列,只需稍作更改。考虑:
np.random.seed(0)
mux3 = pd.MultiIndex.from_product([
list('ABCD'), list('efgh')
], names=['one','two'])
df3 = pd.DataFrame(np.random.choice(10, (3, len(mux))), columns=mux3)
print(df3)
one A B C D
two e f g h e f g h e f g h e f g h
0 5 0 3 3 7 9 3 5 2 4 7 6 8 8 1 6
1 7 7 8 1 5 9 8 9 4 3 0 3 5 0 2 3
2 8 1 3 3 3 7 0 1 9 9 0 4 7 3 2 7
您需要对四个习语进行以下更改,以使它们与列一起使用。
-
切片用loc
, use
df3.loc[:, ....] # Notice how we slice across the index with `:`.
or,
df3.loc[:, pd.IndexSlice[...]]
-
To use xs
根据需要,只需传递一个参数axis=1
.
-
您可以直接使用访问列级别值df.columns.get_level_values
。然后你需要做类似的事情
df.loc[:, {condition}]
Where {condition}
表示使用构建的某些条件columns.get_level_values
.
-
To use query
,您唯一的选择是转置,查询索引,然后再次转置:
df3.T.query(...).T
不推荐,使用其他 3 个选项之一。