选择/过滤索引为MultiIndex的数据框的行的最常见的熊猫方法是什么?
为简单起见假设:
mux = pd.MultiIndex.from_arrays([ list('aaaabbbbbccddddd'), list('tuvwtuvwtuvwtuvw') ], names=['one', 'two']) df = pd.DataFrame({'col': np.arange(len(mux))}, mux) col one two a t 0 u 1 v 2 w 3 b t 4 u 5 v 6 w 7 t 8 c u 9 v 10 d w 11 t 12 u 13 v 14 w 15
问题1:选择单个项目 如何选择级别为“一个”的行为”a”的行?
col one two a t 0 u 1 v 2 w 3
另外,我如何在输出中将级别”one”降低?
col two t 0 u 1 v 2 w 3
问题1b 如何在级别”two”上切片所有值为”t”的行?
col one two a t 0 b t 4 t 8 d t 12
问题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
问题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
问题3:切片单个横截面 (x, y)
如何检索横截面,即具有来自的索引的特定值的一行df?具体来说,如何获取的横截面('c', 'u'),由
('c', 'u')
col one two c u 9
问题4:切片多个横截面 [(a, b), (c, d), …]
如何选择与('c', 'u')和相对应的两行('a', 'w')?
('a', 'w')
col one two c u 9 a w 3
问题5:每个级别切成一个项目
我如何检索与级别一中的a或“级别”二中的t相对应的所有行?
a
t
col one two a t 0 u 1 v 2 w 3 b t 4 t 8 d t 12
问题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
问题7将使用由数字级别组成的唯一设置:
np.random.seed(0) mux2 = pd.MultiIndex.from_arrays([ list('aaaabbbbbccddddd'), np.random.choice(10, size=16) ], names=['one', 'two']) df2 = pd.DataFrame({'col': np.arange(len(mux2))}, mux2) col one two a 5 0 0 1 3 2 3 3 b 7 4 9 5 3 6 5 7 2 8 c 4 9 7 10 d 6 11 8 12 8 13 1 14 6 15
问题7:按数字不等式对多索引的各个级别进行过滤 如何获得“二级”中的值大于5的所有行?
col one two b 7 4 9 5 c 7 10 d 6 11 8 12 8 13 6 15
注意:本文将不介绍如何创建MultiIndexes,如何对其执行赋值操作或任何与性能相关的讨论(这些是下次的单独主题)。
注意: 此帖子的结构如下:
操作规范中提出的问题将一一解决 对于每个问题,将演示一种或多种适用于解决该问题并获得预期结果的方法。 注意 s(非常类似于此内容)将被提供给有兴趣学习更多功能,实现细节以及与当前主题相关的其他信息的读者。这些注释是通过搜集文档并发现各种晦涩难懂的功能以及我自己的(公认的有限)经验而编写的。
所有代码示例均已在pandas v0.23.4,python3.7上创建并测试。如果有不清楚的地方,或者实际上是不正确的,或者您没有找到适用于您的用例的解决方案,请随时提出修改建议,在注释中要求澄清,或打开一个新问题…… 。
pandas v0.23.4,python3.7上
这是一些常见习语(以下简称“四个习语”)的介绍,我们将经常对其进行复习。
DataFrame.loc-按标签选择的一般解决方案(+ pd.IndexSlice表示涉及切片的更复杂应用)
DataFrame.loc
+ pd.IndexSlice
DataFrame.xs -从Series / DataFrame中提取特定的横截面。
DataFrame.xs
Series / DataFrame
DataFrame.query-动态指定切片和/或过滤操作(即,作为动态评估的表达式。比其他情况更适用于某些方案。另请参阅文档的此部分以查询MultiIndexes。
MultiIndexes
使用生成的掩码进行布尔索引MultiIndex.get_level_values(通常与结合使用Index.isin,尤其是在使用多个值进行过滤时)。在某些情况下这也很有用。
MultiIndex.get_level_values
从四个习语的角度来看各种切片和过滤问题将是有益的,以便更好地理解可以应用于给定情况的内容。非常重要的一点是要了解,并非所有习惯用法在每种情况下都一样有效(如果有的话)。如果没有将成语列为以下问题的潜在解决方案,则表示该成语不能有效地应用于该问题。
问题1 如何选择级别为“一个”的行为“ a”的行?
您可以将loc用作适用于大多数情况的通用解决方案:
df.loc[['a']]
此时,如果您得到
TypeError: Expected tuple, got str
这意味着您使用的是旧版熊猫。考虑升级!否则,请使用df.loc[(‘a’, slice(None)), :]。
或者,您可以xs在这里使用,因为我们要提取单个横截面。注意levels和axis参数(此处可以采用合理的默认值)。
df.xs('a', level=0, axis=0, drop_level=False) # df.xs('a', drop_level=False)
在这里,drop_level=False需要使用参数来防止xs在结果(我们切片的水平)上降低“一级”。
drop_level=False
这里的另一个选择是使用query:
df.query("one == 'a'")
如果索引没有名称,则需要将查询字符串更改为"ilevel_0 == 'a'"。
"ilevel_0 == 'a'"
最后,使用get_level_values:
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']
另外,我如何在输出中将级别“ one”降低?
使用以下任一方法均可轻松完成此操作
df.loc['a'] # Notice the single string argument instead the list.
要么,
df.xs('a', level=0, axis=0, drop_level=True) # df.xs('a')
注意,我们可以省略该drop_level参数(True默认情况下假定为该参数)。
注意 您可能会注意到,经过过滤的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 如何在级别“ two”上切片所有值为“ t”的行?
直观地讲,您需要包含以下内容slice():
slice()
df.loc[(slice(None), 't'), :]
它就是行得通!™,但是笨拙。我们可以在pd.IndexSlice此处使用API 促进更自然的切片语法。
idx = pd.IndexSlice df.loc[idx[:, 't'], :]
这要干净得多。
注意 为什么:需要跨列的尾随切片?这是因为loc可以用于沿两个轴(axis=0或 axis=1)进行选择和切片。没有明确说明要在哪个轴上进行切片,操作将变得模棱两可。请参阅切片文档中的红色大框。
如果要消除任何歧义,请loc接受一个axis 参数:
df.loc(axis=0)[pd.IndexSlice[:, 't']]
如果没有该axis参数(即仅执行df.loc[pd.IndexSlice[:, 't']]),则假定切片在列上,并且KeyError在这种情况下将引发a 。
df.loc[pd.IndexSlice[:, 't']])
切片器中对此进行了记录。但是,出于本文的目的,我们将明确指定所有轴。
与xs,是
df.xs('t', axis=0, level=1, drop_level=False)
与query,是
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”相对应的行?
使用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')
注意: 是的,默认解析器为’pandas’,但重要的是要突出此语法不是python常规的语法。Pandas解析器生成的表达式解析树略有不同。这样做是为了使某些操作更直观地指定。有关更多信息,请阅读我关于 使用pd.eval()在熊猫中进行动态表达评估的文章。
并且,用get_level_values+ Index.isin:
df[df.index.get_level_values("one").isin(['b', 'd'])]
问题2b 我如何获得与“第二”级中的“ t”和“ w”相对应的所有值?
使用loc,只有与结合使用才有可能pd.IndexSlice。
df.loc[pd.IndexSlice[:, ['t', 'w']], :]
中的第一个冒号:是pd.IndexSlice[:, [‘t’, ‘w’]]指跨越第一级。随着要查询的级别的深度增加,您将需要指定更多的切片,每个级别将切片一个。您不需要指定多层次超越但被切片的一个。
使用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')
与get_level_values和Index.isin(类似于上面):
df[df.index.get_level_values('two').isin(['t', 'w'])]
问题3 如何检索横截面,即具有来自的索引的特定值的一行df?具体来说,如何获取的横截面(‘c’, ‘u’),由
loc通过指定键的元组来使用:
df.loc[('c', 'u'), :]
df.loc[pd.IndexSlice[('c', 'u')]]
注意 此时,您可能会遇到PerformanceWarning如下所示的:
PerformanceWarning: indexing past lexsort depth may impact performance.
这仅表示您的索引未排序。大熊猫取决于要进行最佳搜索和检索的索引(在这种情况下,按字典顺序排序,因为我们正在处理字符串值)。一种快速的解决方法是使用预先对DataFrame进行排序DataFrame.sort_index。如果您计划一并执行多个此类查询,那么从性能的角度来看,这是特别理想的:
DataFrame.sort_index
df_sort = df.sort_index() df_sort.loc[('c', 'u')]
您还可以MultiIndex.is_lexsorted()用来检查索引是否已排序。此函数返回True或False相应地。您可以调用此函数来确定是否需要其他排序步骤。
使用xs,这再次简单地传递了一个元组作为第一个参数,而所有其他参数都设置为其适当的默认值:
df.xs(('c', 'u'))
使用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’)和相对应的两行(‘a’, ‘w’)?
使用loc,这仍然很简单:
df.loc[[('c', 'u'), ('a', 'w')]] # df.loc[pd.IndexSlice[[('c', 'u'), ('a', 'w')]]]
使用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%不推荐!但是有可能。
问题5
我如何检索与级别“一”中的“ a”或“级别”二中的“ t”相对应的所有行?
loc在确保正确性并仍保持代码清晰的同时,这实际上很难做到。df.loc[pd.IndexSlice['a', 't']]不正确,将其解释为df.loc[pd.IndexSlice[('a', 't')]](即选择横截面)。您可能会想到一种解决方案,pd.concat可以单独处理每个标签:
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固有地包含重复的索引(所需),则将不会保留它们。 使用时要格外小心。
使用query,这非常简单:
df.query("one == 'a' or two == 't'")
使用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的行。
b
u
v
d
w
我添加了这是一个特殊情况,以帮助理解四个惯用语的用法-这是其中的任何一个都无法有效工作的情况,因为切片非常具体,并且不遵循任何实际模式。
通常,像这样的切片问题将需要将键列表显式传递给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'))因为被索引的相同子级别对于每个级别都是相同的。
(('a', 'b'), ('u', 'v'))
问题7 如何获得“二级”中的值大于5的所有行?
可以使用query,
query
df2.query("two > 5")
和get_level_values。
df2[df2.index.get_level_values('two') > 5]
注意 类似于此示例,我们可以使用这些构造基于任意条件进行过滤。在一般情况下,要记住,这是非常有用的loc,并xs是专为基于标签的索引,而query并 get_level_values是构建一般条件口罩用于过滤很有帮助。
奖金问题 如果我需要对MultiIndex 列进行切片怎么办?
MultiIndex
实际上,此处的大多数解决方案也适用于色谱柱,只需稍作更改即可。考虑:
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,请使用
df3.loc[:, ....] # Notice how we slice across the index with `:`.
df3.loc[:, pd.IndexSlice[...]]
要xs适当使用,只需传递一个参数axis=1。
axis=1
您可以使用直接访问列级别值df.columns.get_level_values。然后,您需要做类似的事情
df.columns.get_level_values
df.loc[:, {condition}]
其中{condition}代表使用建立的某些条件columns.get_level_values。
{condition}
columns.get_level_values
要使用query,您唯一的选择是转置,查询索引并再次转置:
df3.T.query(...).T
不建议使用其他3个选项之一。