多索引/高级索引
Note
This post will be structured in the following manner:
The questions put forth in the OP will be addressed, one by one
For each question, one or more methods applicable to solving this problem and getting the expected result will be demonstrated.
Notes (much like this one) will be included for readers interested in learning about additional functionality, implementation details,
and other info cursory to the topic at hand. These notes have been
compiled through scouring the docs and uncovering various obscure
features, and from my own (admittedly limited) experience.
All code samples have created and tested on pandas v0.23.4, python3.7. If something is not clear, or factually incorrect, or if you did not
find a solution applicable to your use case, please feel free to
suggest an edit, request clarification in the comments, or open a new
question, ....as applicable.
下面介绍一些我们以后会经常用到的常用成语(以下简称“四大成语”)
DataFrame.loc - A general solution for selection by label (+ pd.IndexSlice for more complex applications involving slices)
DataFrame.xs - Extract a particular cross section from a Series/DataFrame.
DataFrame.query - Specify slicing and/or filtering operations dynamically (i.e., as an expression that is evaluated dynamically. Is more applicable to some scenarios than others. Also see this section of the docs for querying on MultiIndexes.
Boolean indexing with a mask generated using MultiIndex.get_level_values (often in conjunction with Index.isin, especially when filtering with multiple values). This is also quite useful in some circumstances.
从四个习语的角度来看各种切片和过滤问题将有助于更好地理解什么可以应用于给定的情况。重要的是要明白,并非所有的习语在每种情况下都同样有效(如果有的话)。如果一个习语没有被列为下面某个问题的潜在解决方案,那就意味着这个习语不能有效地应用于该问题。
问题1
我如何选择行有“a”级别“一”?
上校
一个两个
at0
你1
v 2
w 3
你可以使用loc,作为适用于大多数情况的通用解决方案:
df.loc[['a']]
此时,如果你得到
TypeError: Expected tuple, got str
这意味着你用的是熊猫的旧版本。考虑升级!否则,使用df。loc[('a', slice(None)),:]。
或者,你可以在这里使用xs,因为我们提取的是单个横截面。注意级别和轴参数(这里可以假设合理的默认值)。
df.xs('a', level=0, axis=0, drop_level=False)
# df.xs('a', drop_level=False)
在这里,drop_level=False参数是为了防止xs在结果中删除级别“1”(我们所切割的级别)。
这里的另一个选项是使用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”?
上校
两个
t 0
你1
v 2
w 3
这可以很容易地使用任何一个
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
You may notice that a filtered DataFrame may still have all the levels, even if they do not show when printing the DataFrame out. For example,
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'])
You can get rid of these levels using MultiIndex.remove_unused_levels:
v.index = v.index.remove_unused_levels()
打印(v.index)
MultiIndex(level =[['a'], ['t', 'u', 'v', 'w']],
标签=[[0,0,0,0],[0,1,2,3]],
名称=['一',' 2 '])
问题1 b
我如何在级别“2”上切片所有值为“t”的行?
上校
一个两个
at0
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
Why is the trailing slice : across the columns required? This is because, loc can be used to select and slice along both axes (axis=0 or
axis=1). Without explicitly making it clear which axis the slicing
is to be done on, the operation becomes ambiguous. See the big red box in the documentation on slicing.
If you want to remove any shade of ambiguity, loc accepts an axis
parameter:
df.loc(axis=0)[pd.IndexSlice[:, 't']]
Without the axis parameter (i.e., just by doing df.loc[pd.IndexSlice[:, 't']]), slicing is assumed to be on the columns,
and a KeyError will be raised in this circumstance.
This is documented in slicers. For the purpose of this post, however, we will explicitly specify all axes.
对于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
如何在“一级”中选择与“b”和“d”项对应的行?
上校
一个两个
B t 4
你5
v 6
w 7
t 8
D w 11
t 12
你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')
请注意
是的,默认的解析器是'pandas',但重要的是要强调这种语法不是常规的python。的
Pandas解析器生成的解析树与
表达式。这样做是为了使一些操作更加直观
指定。更多信息,请阅读我的帖子
使用pd.eval()在pandas中的动态表达式求值。
并且,get_level_values + Index.isin:
df[df.index.get_level_values("one").isin(['b', 'd'])]
问题2 b
我如何在“第二层”中获得与“t”和“w”对应的所有值?
上校
一个两个
at0
w 3
B t 4
w 7
t 8
D w 11
t 12
w 15
对于loc,这只能与pd.IndexSlice结合使用。
df.loc[pd.IndexSlice[:, ['t', 'w']], :]
第一个冒号:in pd。IndexSlice[:, ['t', 'w']]]表示对第一个层进行切片。随着所查询级别的深度增加,您将需要指定更多的切片,每个级别都被切片。但是,除了被切片的层之外,您不需要指定更多的层。
对于查询,这是
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')的分段,由
上校
一个两个
C u 9
通过指定键元组来使用loc:
df.loc[('c', 'u'), :]
Or,
df.loc[pd.IndexSlice[('c', 'u')]]
Note
At this point, you may run into a PerformanceWarning that looks like this:
PerformanceWarning: indexing past lexsort depth may impact performance.
This just means that your index is not sorted. pandas depends on the index being sorted (in this case, lexicographically, since we are dealing with string values) for optimal search and retrieval. A quick fix would be to sort your
DataFrame in advance using DataFrame.sort_index. This is especially desirable from a performance standpoint if you plan on doing
multiple such queries in tandem:
df_sort = df.sort_index()
df_sort.loc[('c', 'u')]
You can also use MultiIndex.is_lexsorted() to check whether the index
is sorted or not. This function returns True or False accordingly.
You can call this function to determine whether an additional sorting
step is required or not.
对于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')的两行?
上校
一个两个
C u 9
A w 3
使用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%不推荐!但这是可能的。
如果我有多个层次呢?
该场景中的一个选项是使用droplevel删除未检查的级别,然后使用isin测试成员关系,然后在最终结果上使用布尔索引。
df[df.index.droplevel(unused_level).isin([('c', 'u'), ('a', 'w')])]
问题5
我如何检索对应“a”在级别“一”或所有行
第二级的"t" ?
上校
一个两个
at0
你1
v 2
w 3
B t 4
t 8
D t 12
这实际上很难用loc做到,同时确保正确性并保持代码清晰。df.loc [pd。IndexSlice['a', 't']]不正确,它被解释为df.loc[pd. loc]。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”的行。
上校
一个两个
A u 1
v 2
B u 5
v 6
D w 11
w 15
这是一个特殊的情况,我添加它是为了帮助理解四个习语的适用性——在这种情况下,它们都不能有效地工作,因为切片非常具体,并且不遵循任何真实的模式。
通常,这样的切片问题需要显式地将键列表传递给loc。其中一种方法是:
keys = [('a', 'u'), ('a', 'v'), ('b', 'u'), ('b', 'v'), ('d', 'w')]
df.loc[keys, :]
如果你想节省一些输入,你会发现对“a”,“b”及其子级别进行切片是有模式的,所以我们可以将切片任务分成两部分并连接结果:
pd.concat([
df.loc[(('a', 'b'), ('u', 'v')), :],
df.loc[('d', 'w'), :]
], axis=0)
“a”和“b”的切片规范稍微干净一些(('a', 'b'), ('u', 'v')),因为每个层被索引的相同子层是相同的。
问题7
我如何获得级别“2”中值大于5的所有行?
上校
一个两个
B 7 4
9点5
C 7 10
D 6 11
8日12
8日13
6日15
这可以使用query,
df2.query("two > 5")
和get_level_values。
df2[df2.index.get_level_values('two') > 5]
请注意
与此示例类似,我们可以使用这些构造基于任意条件进行筛选。一般来说,记住loc和xs是专门用于基于标签的索引,而查询和
Get_level_values有助于构建一般的条件掩码
过滤。
奖金问题
如果我需要切片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
您需要对四个成语进行以下更改,以使它们与列一起工作。
To slice with loc, use
df3.loc[:, ....] # Notice how we slice across the index with `:`.
or,
df3.loc[:, pd.IndexSlice[...]]
To use xs as appropriate, just pass an argument axis=1.
You can access the column level values directly using df.columns.get_level_values. You will then need to do something like
df.loc[:, {condition}]
Where {condition} represents some condition built using columns.get_level_values.
To use query, your only option is to transpose, query on the index, and transpose again:
df3.T.query(...).T
Not recommended, use one of the other 3 options.