eval
和query
功能强大,但在熊猫API套件中却被低估了,它们的用法远未得到充分记录或理解。通过适当的照顾,query
和eval
可以大大简化代码,提高性能,并成为创建动态工作流的强大工具。
此规范QnA的目的是使用户更好地理解这些功能,并通过清晰易懂的示例讨论一些鲜为人知的功能,如何使用它们以及如何最好地使用它们。这篇文章将要解决的两个主要主题是
engine
中的parser
,target
和pd.eval
自变量,以及如何将其用于计算表达式pd.eval
,df.eval
和df.query
之间的区别,以及每个函数适合用于动态执行的时间。这篇文章不能代替文档(答案中的链接),所以也请仔细阅读!
我将以这样一种方式来提出问题,即开始讨论eval
支持的各种功能。
给出两个数据框
np.random.seed(0)
df1 = pd.DataFrame(np.random.choice(10, (5, 4)), columns=list('ABCD'))
df2 = pd.DataFrame(np.random.choice(10, (5, 4)), columns=list('ABCD'))
df1
A B C D
0 5 0 3 3
1 7 9 3 5
2 2 4 7 6
3 8 8 1 6
4 7 7 8 1
df2
A B C D
0 5 9 8 9
1 4 3 0 3
2 5 0 2 3
3 8 1 3 3
4 3 7 0 1
我想使用pd.eval
对一列或多列进行算术运算。具体来说,我想移植以下代码:
x = 5
df2['D'] = df1['A'] + (df1['B'] * x)
...使用eval
进行编码。使用eval
的原因是我想使许多工作流程自动化,所以动态创建它们对我很有用。
我试图更好地理解engine
和parser
参数,以确定如何最好地解决我的问题。我经历过documentation,但对我而言,区别并不明显。
df2
?x
作为参数传递? 答案 0 :(得分:44)
此答案将深入探讨pd.eval
,df.query
和df.eval
提供的各种功能。
设置
示例将涉及这些DataFrame(除非另有说明)。
np.random.seed(0)
df1 = pd.DataFrame(np.random.choice(10, (5, 4)), columns=list('ABCD'))
df2 = pd.DataFrame(np.random.choice(10, (5, 4)), columns=list('ABCD'))
df3 = pd.DataFrame(np.random.choice(10, (5, 4)), columns=list('ABCD'))
df4 = pd.DataFrame(np.random.choice(10, (5, 4)), columns=list('ABCD'))
pandas.eval
-“缺少手册” 注意
在讨论的三个功能中,pd.eval
是最重要的。df.eval
和df.query
通话pd.eval
在幕后。行为和用法或多或少 这三个功能保持一致,但语义有些许变化 变体,稍后将重点介绍。本节将 介绍这三个功能共有的功能-包括(但不限于)允许的语法,优先级规则和关键字参数。
pd.eval
可以计算算术表达式,该表达式可以包含变量和/或文字。这些表达式必须作为字符串传递。因此,按照说明回答问题,您可以
x = 5
pd.eval("df1.A + (df1.B * x)")
这里要注意一些事情:
df1
,df2
和x
引用全局命名空间中的变量,它们在解析表达式时由eval
拾取"df1['A'] + (df1['B'] * x)"
来达到相同的效果。我将在下面解释target=...
属性的部分中讨论重新分配的特定问题。但目前,这里是pd.eval
的有效操作的更简单示例:
pd.eval("df1.A + df2.A") # Valid, returns a pd.Series object
pd.eval("abs(df1) ** .5") # Valid, returns a pd.DataFrame object
...等等。条件表达式也以相同的方式受支持。以下语句都是有效表达式,将由引擎进行评估。
pd.eval("df1 > df2")
pd.eval("df1 > 5")
pd.eval("df1 < df2 and df3 < df4")
pd.eval("df1 in [1, 2, 3]")
pd.eval("1 < 2 < 3")
在documentation中可以找到详细列出所有受支持的功能和语法的列表。总之,
- 除左移(
<<
和右移(>>
)运算符外的算术运算,例如df + 2 * pi / s ** 4 % 42
-the_golden_ratio- 比较操作,包括链式比较,例如
2 < df < df2
- 布尔运算,例如
df < df2 and df3 < df4
或not df_bool
list
和tuple
文字,例如[1, 2]
或(1, 2)
- 属性访问,例如
df.a
- 下标表达式,例如
df[0]
- 简单的变量求值,例如
pd.eval('df')
(这不是很有用)- 数学函数:sin,cos,exp,log,expm1,log1p,sqrt,sinh,cosh,tanh,arcsin,arccos,arctan,arcosh,arcsinh,arctanh,abs和 arctan2。
文档的此部分还指定了不支持的语法规则,包括set
/ dict
文字,if-else语句,循环和理解以及生成器表达式。
从列表中可以明显看出,您还可以传递涉及索引的表达式,例如
pd.eval('df1.A * (df1.index > 1)')
parser=...
参数 pd.eval
在解析表达式字符串以生成语法树时支持两种不同的解析器选项:pandas
和python
。两者之间的主要区别在于优先级规则略有不同。
使用默认的解析器pandas
,使用pandas对象实现矢量化AND和OR运算的重载按位运算符&
和|
的运算符优先级与and
相同和`或。因此,
pd.eval("(df1 > df2) & (df3 < df4)")
将与
相同pd.eval("df1 > df2 & df3 < df4")
# pd.eval("df1 > df2 & df3 < df4", parser='pandas')
也和
相同pd.eval("df1 > df2 and df3 < df4")
在这里,括号是必需的。按照常规方式,需要使用parens来覆盖按位运算符的更高优先级:
(df1 > df2) & (df3 < df4)
否则,我们最终会得到
df1 > df2 & df3 < df4
ValueError: The truth value of a DataFrame is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().
如果要在评估字符串时保持与python实际运算符优先级规则的一致性,请使用parser='python'
。
pd.eval("(df1 > df2) & (df3 < df4)", parser='python')
两种类型的解析器之间的另一个区别是==
和!=
运算符的语义以及列表和元组节点,它们的语义与in
和{{1 }},分别使用not in
解析器时。例如,
'pandas'
有效,并且将以与
相同的语义运行pd.eval("df1 == [1, 2, 3]")
OTOH,pd.eval("df1 in [1, 2, 3]")
将引发pd.eval("df1 == [1, 2, 3]", parser='python')
错误。
NotImplementedError
参数有两个选项-engine=...
(默认)和numexpr
。 python
选项使用为性能优化的numexpr后端。
通过numexpr
后端,您的表达式的求值类似于将表达式传递给python的'python'
函数。您可以灵活地执行更多内部表达式,例如字符串操作。
eval
不幸的是,与df = pd.DataFrame({'A': ['abc', 'def', 'abacus']})
pd.eval('df.A.str.contains("ab")', engine='python')
0 True
1 False
2 True
Name: A, dtype: bool
引擎相比,此方法没有提供 no 性能优势,并且几乎没有安全措施可确保不评估危险的表达式,因此请随时使用自己承担风险!除非您知道自己在做什么,否则通常不建议将此选项更改为numexpr
。
'python'
和local_dict
自变量有时候,为表达式中使用的变量提供值很有用,但当前尚未在名称空间中定义。您可以将字典传递给global_dict
例如,
local_dict
此操作失败,因为未定义pd.eval("df1 > thresh")
UndefinedVariableError: name 'thresh' is not defined
。但是,这可行:
thresh
当您需要从字典中提供变量时,这很有用。或者,使用pd.eval("df1 > x", local_dict={'thresh': 10})
引擎,您可以简单地执行以下操作:
'python'
但是,这可能会比使用mydict = {'thresh': 5}
# Dictionary values with *string* keys cannot be accessed without
# using the 'python' engine.
pd.eval('df1 > mydict["thresh"]', engine='python')
引擎并将字典传递给'numexpr'
或local_dict
的速度慢得多。希望这应该为使用这些参数提供令人信服的论据。
global_dict
(+ target
)参数和赋值表达式这通常不是必需的,因为这样做通常更简单,但是您可以将inplace
的结果分配给实现pd.eval
的对象,例如__getitem__
,和(您猜对了)DataFrames。
考虑问题中的示例
dict
要为x = 5
df2['D'] = df1['A'] + (df1['B'] * x)
分配一列“ D”,请执行
df2
这不是对pd.eval('D = df1.A + (df1.B * x)', target=df2)
A B C D
0 5 9 8 5
1 4 3 0 52
2 5 0 2 22
3 8 1 3 48
4 3 7 0 42
的就地修改(但可以...继续阅读)。请考虑另一个示例:
df2
例如,如果您想将此分配回DataFrame,则可以使用pd.eval('df1.A + df2.A')
0 10
1 11
2 7
3 16
4 10
dtype: int32
参数,如下所示:
target
如果您想对df = pd.DataFrame(columns=list('FBGH'), index=df1.index)
df
F B G H
0 NaN NaN NaN NaN
1 NaN NaN NaN NaN
2 NaN NaN NaN NaN
3 NaN NaN NaN NaN
4 NaN NaN NaN NaN
df = pd.eval('B = df1.A + df2.A', target=df)
# Similar to
# df = df.assign(B=pd.eval('df1.A + df2.A'))
df
F B G H
0 NaN 10 NaN NaN
1 NaN 11 NaN NaN
2 NaN 7 NaN NaN
3 NaN 16 NaN NaN
4 NaN 10 NaN NaN
进行就地突变,请设置df
。
inplace=True
如果将pd.eval('B = df1.A + df2.A', target=df, inplace=True)
# Similar to
# df['B'] = pd.eval('df1.A + df2.A')
df
F B G H
0 NaN 10 NaN NaN
1 NaN 11 NaN NaN
2 NaN 7 NaN NaN
3 NaN 16 NaN NaN
4 NaN 10 NaN NaN
设置为没有目标,则会引发inplace
。
虽然ValueError
参数很有趣,但是您几乎不需要使用它。
如果您想使用target
进行此操作,则可以使用一个涉及赋值的表达式:
df.eval
注意
df = df.eval("B = @df1.A + @df2.A")
# df.eval("B = @df1.A + @df2.A", inplace=True)
df
F B G H
0 NaN 10 NaN NaN
1 NaN 11 NaN NaN
2 NaN 7 NaN NaN
3 NaN 16 NaN NaN
4 NaN 10 NaN NaN
的一种意外用途是以与pd.eval
非常相似的方式解析文字字符串:
ast.literal_eval
它也可以使用pd.eval("[1, 2, 3]")
array([1, 2, 3], dtype=object)
引擎解析嵌套列表:
'python'
以及字符串列表:
pd.eval("[[1, 2, 3], [4, 5], [10]]", engine='python')
[[1, 2, 3], [4, 5], [10]]
但是,问题在于长度大于10的列表:
pd.eval(["[1, 2, 3]", "[4, 5]", "[10]"], engine='python')
[[1, 2, 3], [4, 5], [10]]
可以找到更多有关此错误,原因,修复和解决方法的信息,here。
DataFrame.eval
-与pd.eval(["[1]"] * 100, engine='python') # Works
pd.eval(["[1]"] * 101, engine='python')
AttributeError: 'PandasExprVisitor' object has no attribute 'visit_Ellipsis'
并置如上所述,pandas.eval
在后台调用df.eval
。 v0.23 source code显示如下:
pd.eval
def eval(self, expr, inplace=False, **kwargs):
from pandas.core.computation.eval import eval as _eval
inplace = validate_bool_kwarg(inplace, 'inplace')
resolvers = kwargs.pop('resolvers', None)
kwargs['level'] = kwargs.pop('level', 0) + 1
if resolvers is None:
index_resolvers = self._get_index_resolvers()
resolvers = dict(self.iteritems()), index_resolvers
if 'target' not in kwargs:
kwargs['target'] = self
kwargs['resolvers'] = kwargs.get('resolvers', ()) + tuple(resolvers)
return _eval(expr, inplace=inplace, **kwargs)
创建自变量,进行一些验证,然后将自变量传递到eval
。
有关更多信息,您可以阅读:when to use DataFrame.eval() versus pandas.eval() or python eval()
对于与整个DataFrame关联的动态查询,您应该首选pd.eval
。例如,当您调用pd.eval
或pd.eval("df1 + df2")
时,没有简单的方法可以指定df1.eval
的等效项。
另一个主要区别是如何访问列。例如,要在df2.eval
中添加两列“ A”和“ B”,则可以使用以下表达式调用df1
:
pd.eval
使用df.eval,只需提供列名称:
pd.eval("df1.A + df1.B")
因为,在df1.eval("A + B")
的上下文中,很明显“ A”和“ B”是指列名。
您还可以使用df1
来引用索引和列(除非索引被命名,在这种情况下,您将使用名称)。
index
或更笼统地说,对于具有1个或多个级别索引的任何DataFrame,您可以使用变量“ ilevel_k”在表达式中引用索引的第k 级代表“ k级的 i ndex”。 IOW,上面的表达式可以写成df1.eval("A + index")
。
这些规则也适用于df1.eval("A + ilevel_0")
。
表达式中提供的变量必须以“ @”符号开头,以避免与列名混淆。
query
A = 5
df1.eval("A > @A")
/
毋庸置疑,您的列名必须遵循在query
内部可访问的python中有效标识符命名的规则。有关命名标识符的规则列表,请参见here。
一个鲜为人知的事实是eval
支持处理赋值的多行表达式。例如,要基于某些列上的某些算术运算在df1中创建两个新列“ E”和“ F”,并基于先前创建的“ E”和“ F”来创建第三列“ G”,我们可以< / p>
eval
...好漂亮!但是,请注意df1.eval("""
E = A + B
F = @df2.A + @df2.B
G = E >= F
""")
A B C D E F G
0 5 0 3 3 5 14 False
1 7 9 3 5 16 7 True
2 2 4 7 6 6 5 True
3 8 8 1 6 16 9 True
4 7 7 8 1 14 10 True
不支持此功能。
query
v / s eval
-最终词将query
视为使用df.query
作为子例程的函数会有所帮助。
通常,pd.eval
(顾名思义)用于评估条件表达式(即产生True / False值的表达式)并返回与query
结果相对应的行。然后将表达式的结果传递到True
(在大多数情况下)以返回满足表达式的行。根据文档,
该表达式的求值结果首先传递给
loc
,如果由于多维键而失败 (例如,DataFrame),则结果将传递到DataFrame.loc
。此方法使用顶级
DataFrame.__getitem__()
函数来评估 通过查询。
就相似性而言,pandas.eval()
和query
在访问列名和变量方面都是相似的。
如上所述,两者之间的主要区别在于它们如何处理表达式结果。当您实际上通过这两个函数运行表达式时,这一点变得显而易见。例如,考虑
df.eval
要获取df1.A
0 5
1 7
2 2
3 8
4 7
Name: A, dtype: int32
df2.B
0 9
1 3
2 0
3 1
4 7
Name: B, dtype: int32
中“ A”> =“ B”的所有行,我们将使用df1
,如下所示:
eval
m = df1.eval("A >= B")
m
0 True
1 False
2 False
3 True
4 True
dtype: bool
表示通过评估表达式“ A> = B”生成的中间结果。然后,我们使用掩码过滤m
:
df1
但是,对于df1[m]
# df1.loc[m]
A B C D
0 5 0 3 3
3 8 8 1 6
4 7 7 8 1
,中间结果“ m”直接传递给query
,因此对于loc
,您只需要做
query
明智的选择,完全相同。
df1.query("A >= B")
A B C D
0 5 0 3 3
3 8 8 1 6
4 7 7 8 1
但是后者更为简洁,并且只需一步即可表达相同的操作。
请注意,您还可以使用df1_big = pd.concat([df1] * 100000, ignore_index=True)
%timeit df1_big[df1_big.eval("A >= B")]
%timeit df1_big.query("A >= B")
14.7 ms ± 33.9 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
14.7 ms ± 24.3 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
做一些奇怪的事情(例如,返回由df1.index索引的所有行)
query
但是不要。
底线:根据条件表达式查询或过滤行时,请使用df1.query("index")
# Same as df1.loc[df1.index] # Pointless,... I know
A B C D
0 5 0 3 3
1 7 9 3 5
2 2 4 7 6
3 8 8 1 6
4 7 7 8 1
。
答案 1 :(得分:2)
本教程非常出色,但是请记住,在您的数据集少于15,000行的情况下,{@ {1}}的使用由于其更简单的语法而引人注目,在性能上存在严重的问题。
在这种情况下,只需使用eval/query
。
引用:https://pandas.pydata.org/pandas-docs/version/0.22/enhancingperf.html#enhancingperf-eval
答案 2 :(得分:2)
很棒的教程@cs95。我正在阅读 Jake VanderPlas 的书 (Python DataScience Handbook 其中有一个代码:
births['decade'] = 10 * (births['year'] // 10)
如果你看看上面的 floordiv() (//),我无法做到,使用 eval()。
births.eval("decade = 10 * (year // 10)")
这会出错。 但是,在阅读了您的文章后,我更改了引擎,并且运行良好。
births.eval("decade = 10 * (year // 10)", engine= "python")
谢谢@cs95。