注释
这篇文章的结构如下:
-
行动计划中提出的问题将逐一解决。
-
对于每个问题,将演示一个或多个适用于解决此问题并获得预期结果的方法。
注释
对于有兴趣了解其他功能、实现细节和
以及其他粗略的信息。这些笔记
通过对文档的梳理和对各种晦涩难懂的内容的揭示而编纂而成
特点,并从我自己(公认的有限)的经验。
所有代码示例都已创建并测试于
熊猫v0.23.4,蟒蛇3.7
. 如果有什么不清楚,或事实上不正确,或如果你没有
找到一个适用于您的用例的解决方案,请随时
建议编辑、在评论中要求澄清或打开新的
问题……如适用。
下面是我们将经常访问的一些常用习语(以下简称“四个习语”)的介绍。
-
DataFrame.loc
-按标签选择的通用解决方案(+
pd.IndexSlice
对于涉及切片的更复杂的应用程序)
-
DataFrame.xs
-从序列/数据帧中提取特定的横截面。
-
DataFrame.query
-动态指定切片和/或筛选操作(即,作为动态计算的表达式)。比其他场景更适用于某些场景。也看到
this section of the docs
用于查询多索引。
-
使用生成的掩码进行布尔索引
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
这意味着你使用的是老版本的熊猫。考虑升级!否则,使用
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
从结果的“一”级下降(我们切下的级别)。
另一个选择是使用
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']
此外,我如何能够在输出中降低级别“一”?
col
two
t 0
u 1
v 2
w 3
这可以
容易地
完成使用
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
默认情况下)。
注释
您可能会注意到,筛选的数据帧可能仍然具有所有级别,即使它们在打印数据帧时不显示。例如,
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
如何在级别“2”上对值为“t”的所有行进行切片?
col
one two
a t 0
b t 4
t 8
d t 12
凭直觉,你会想要一些
slice()
:
df.loc[(slice(None), 't'), :]
它只是工作!_,但是它很笨重。我们可以使用
PDD索引片
API在这里。
idx = pd.IndexSlice
df.loc[idx[:, 't'], :]
这要干净得多。
注释
为什么尾随片
:
是否需要跨越列?这是因为,
洛克
可用于沿两个轴选择和切片(
axis=0
或
axis=1
)没有明确说明切片的轴
将在上执行,操作将变得不明确。看到大红盒在
documentation on slicing
.
如果你想消除任何模棱两可的阴影,
洛克
接受一个
轴
参数:
df.loc(axis=0)[pd.IndexSlice[:, 't']]
没有
轴
参数(即
df.loc[pd.IndexSlice[:, 't']]
,假定切片在柱上,
和A
KeyError
会在这种情况下长大。
记录在
slicers
. 然而,在本文中,我们将明确地指定所有轴。
用
XS
它是
df.xs('t', axis=0, level=1, drop_level=False)
用
查询
它是
df.query("two == 't'")
# Or, if the first level has no name,
# df.query("ilevel_1 == 't'")
最后,用
获取\级别\值
你可以这样做
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”项对应的行?
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”的问题,您还可以使用
查询
:
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解析器生成的解析树与
表达式。这样做是为了使某些操作更直观地
指定。有关更多信息,请阅读我的帖子
Dynamic Expression Evaluation in pandas using pd.eval()
.
以及
获取\级别\值
+
索引索引
:
df[df.index.get_level_values("one").isin(['b', 'd'])]
问题2B
如何获得“2”级中与“t”和“w”对应的所有值?
col
one two
a t 0
w 3
b t 4
w 7
t 8
d w 11
t 12
w 15
用
洛克
,这是可能的
只有
与…结合
PDD索引片
.
df.loc[pd.IndexSlice[:, ['t', 'w']], :]
第一结肠
:
在里面
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')
用
获取\级别\值
和
索引索引
(与上面类似):
df[df.index.get_level_values('two').isin(['t', 'w'])]
问题3
如何检索横截面,即具有特定值的单行
对于来自的索引
df
?具体来说,我如何取回十字架
截面
('c', 'u')
,由
col
one two
c u 9
使用
洛克
通过指定键的元组:
df.loc[('c', 'u'), :]
或者,
df.loc[pd.IndexSlice[('c', 'u')]]
注释
此时,您可能会遇到
PerformanceWarning
看起来是这样的:
PerformanceWarning: indexing past lexsort depth may impact performance.
这只意味着索引没有排序。pandas依赖于被排序的索引(在本例中,是从词典的角度,因为我们处理的是字符串值)来优化搜索和检索。快速解决方法是
数据帧预先使用
DataFrame.sort_index
. 如果您计划这样做,从性能角度来看,这是特别可取的
多个这样的查询串联在一起:
df_sort = df.sort_index()
df_sort.loc[('c', 'u')]
您也可以使用
MultiIndex.is_lexsorted()
检查索引是否
是否排序。此函数返回
真
或
False
因此。
您可以调用此函数来确定附加排序
是否需要步骤。
用
XS
,这再次简单地将一个元组作为第一个参数传递,并将所有其他参数设置为相应的默认值:
df.xs(('c', 'u'))
用
查询
,事情变得有点笨拙:
df.query("one == 'c' and two == 'u'")
现在您可以看到,这将是相对难以概括的。但对于这个特殊的问题还是可以的。
通道跨越多个层次,
获取\级别\值
仍可以使用,但不建议使用:
m1 = (df.index.get_level_values('one') == 'c')
m2 = (df.index.get_level_values('two') == 'u')
df[m1 & m2]
问题4
如何选择对应的两行
(c),“u”
和
('a', 'w')
?
col
one two
c u 9
a w 3
用
洛克
,这仍然简单到:
df.loc[[('c', 'u'), ('a', 'w')]]
# df.loc[pd.IndexSlice[[('c', 'u'), ('a', 'w')]]]
用
查询
,您将需要通过遍历您的横截面和级别动态生成查询字符串:
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
如何检索级别“1”中与“a”对应的所有行,以及
“U”在二级?
col
one two
a t 0
u 1
v 2
w 3
b t 4
t 8
d t 12
这实际上很难处理
洛克
同时确保正确性
和
仍然保持代码清晰。
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()]
但是,如果您的数据帧本身包含重复的索引(您想要的),那么这将不会保留它们。
谨慎使用
.
用
查询
太简单了:
df.query("one == 'a' or two == 't'")
用
获取\级别\值
,这仍然很简单,但没有那么优雅:
m1 = (df.index.get_level_values('one') == 'c')
m2 = (df.index.get_level_values('two') == 'u')
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
这是我为帮助理解四个习惯用法的适用性而添加的一个特殊情况,因为切片是
非常
具体,不遵循任何实际模式。
通常,像这样的切片问题需要显式地将键列表传递给
洛克
. 一种方法是:
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
如何获取级别“2”中的值大于5的所有行?
col
one two
b 7 4
9 5
c 7 10
d 6 11
8 12
8 13
6 15
这可以用
查询
,
df2.query("two > 5")
和
获取\级别\值
.
df2[df2.index.get_level_values('two') > 5]
注释
与本例类似,我们可以使用这些构造基于任意条件进行过滤。一般来说,记住这一点很有用
洛克
和
XS
专门用于基于标签的索引,而
查询
和
获取\级别\值
有助于构建通用条件掩码
用于过滤。
红利问题
如果我需要切一片
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
您需要对这四个习惯用法进行以下更改,以便使它们与列一起工作。
-
切成薄片
洛克
使用
df3.loc[:, ....] # Notice how we slice across the index with `:`.
或者,
df3.loc[:, pd.IndexSlice[...]]
-
使用
XS
如果合适,只需传递一个参数
轴=1
.
-
您可以直接使用
df.columns.get_level_values
. 然后你需要做一些像
df.loc[:, {condition}]
在哪里?
{condition}
表示使用
columns.get_level_values
.
-
使用
查询
,您唯一的选择是转置、查询索引并再次转置:
df3.T.query(...).T
不推荐使用其他3个选项之一。