pd.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
eval进行编码。使用
eval
的原因是我想自动化许多工作流程,因此动态创建它们对我来说将是有用的。我试图更好地理解
engine
和parser
参数,以确定如何最好地解决我的问题。我已经阅读了文档,但是区别并不清楚。 应使用哪些参数来确保我的代码以最高性能工作?
是否可以将表达式的结果分配回
df2
?此外,为了使事情更复杂,如何在字符串表达式中将
x
作为参数传递? #1 楼
此答案将深入研究pd.eval
,df.query
和df.eval
提供的各种功能。Setup
示例将涉及这些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")
在文档中可以找到详细列出所有受支持的功能和语法的列表。总之,
除左移运算符(
<<
)和右移运算符(>>
)外的算术运算,例如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, arccosh,arcsinh,arcttanh,abs和
arctan2。
本文档的这一部分还指定了不支持的语法规则,包括
set
/ dict
文字, if-else语句,循环和理解以及生成器表达式。从列表中可以明显看出,您还可以传递expre涉及索引的部分,例如
pd.eval('df1.A * (df1.index > 1)')
解析器选择:
parser=...
参数pd.eval
在将表达式字符串解析为生成语法树:pandas
和python
。两者之间的主要区别是通过稍有不同的优先级规则来突出显示。使用默认解析器
pandas
,使用pandas对象实现矢量化AND和OR运算符的重载按位运算符&
和|
将具有相同的运算符优先级分别为and
和or
。因此,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'
。带列表和元组节点的==
和!=
运算符的语义,使用in
解析器时分别具有与not in
和'pandas'
相似的语义。例如,pd.eval("(df1 > df2) & (df3 < df4)", parser='python')
有效,并且将以与
pd.eval("df1 == [1, 2, 3]")
OTOH相同的语义运行,
pd.eval("df1 == [1, 2, 3]", parser='python')
将引发NotImplementedError
错误。后端选择:
engine=...
参数有两个选项-
numexpr
(默认)和python
。 numexpr
选项使用针对性能优化的numexpr后端。 使用
'python'
后端,您的表达式的求值类似于将表达式传递给python的eval
函数。您可以灵活地执行更多内部表达式(例如,字符串操作)。pd.eval("df1 in [1, 2, 3]")
不幸的是,与
numexpr
引擎相比,此方法没有性能上的好处,并且很少有安全措施可确保不评估危险的表达,因此请自担风险使用!除非您知道自己在做什么,否则通常不建议将此选项更改为'python'
。 local_dict
和global_dict
参数有时,为表达式内部使用但当前未在名称空间中定义的变量提供值很有用。您可以将字典传递给local_dict
,例如,
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
此操作失败,因为未定义
thresh
。但是,这可以起作用:pd.eval("df1 > thresh")
UndefinedVariableError: name 'thresh' is not defined
当您有要从字典提供的变量时,这很有用。另外,使用
'python'
引擎,您可以简单地执行以下操作:pd.eval("df1 > thresh", local_dict={'thresh': 10})
但这可能比使用
'numexpr'
引擎并将字典传递给local_dict
或global_dict
慢得多。希望这应该成为使用这些参数的令人信服的参数。target
(+ inplace
)参数和赋值表达式这不是经常需要的,因为通常是更简单的方法,但是您可以将
pd.eval
的结果分配给实现__getitem__
的对象,例如dict
,以及(您猜对了)DataFrames。 考虑问题中的示例
mydict = {'thresh': 5}
# Dictionary values with *string* keys cannot be accessed without
# using the 'python' engine.
pd.eval('df1 > mydict["thresh"]', engine='python')
要为
df2
分配列“ D”,我们做x = 5
df2['D'] = df1['A'] + (df1['B'] * x)
这不是对
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
如果要(例如)将其分配回DataFrame,则可以使用
target
参数,如下所示:pd.eval('df1.A + df2.A')
0 10
1 11
2 7
3 16
4 10
dtype: int32
如果要对
df
执行就地突变,请设置inplace=True
。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
如果
inplace
设置为不带一个目标,会引发一个ValueError
。尽管
target
参数很有趣,但是您几乎不需要使用它。如果您想使用
df.eval
,您将使用涉及赋值的表达式: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
注意
pd.eval
的非预期用途之一是解析字面字符串,其方式与ast.literal_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
它也可以使用
'python'
引擎解析嵌套列表:pd.eval("[1, 2, 3]")
array([1, 2, 3], dtype=object)
以及列表字符串:
pd.eval("[[1, 2, 3], [4, 5], [10]]", engine='python')
[[1, 2, 3], [4, 5], [10]]
但是,问题在于长度大于100的列表:
pd.eval(["[1, 2, 3]", "[4, 5]", "[10]"], engine='python')
[[1, 2, 3], [4, 5], [10]]
更多信息可以在这里找到此错误,原因,修复和解决方法。
DataFrame.eval
-与并置pandas.eval
如上所述,
df.eval
在后台调用pd.eval
。 v0.23源代码显示以下内容:pd.eval(["[1]"] * 100, engine='python') # Works
pd.eval(["[1]"] * 101, engine='python')
AttributeError: 'PandasExprVisitor' object has no attribute 'visit_Ellipsis'
eval
创建参数,进行一些验证,然后将参数传递给pd.eval
。更多内容,您可以继续阅读:何时使用DataFrame.eval()和pandas.eval()或python eval()
用法差异
DataFrames v的表达式/ s系列表达式
对于与整个DataFrame相关的动态查询,您应该首选
pd.eval
。例如,当您调用pd.eval("df1 + df2")
或df1.eval
时,没有简单的方法来指定df2.eval
的等效项。指定列名
另一个其他主要区别是如何访问列。例如,要在
df1
中添加两列“ A”和“ B”,您将使用以下表达式调用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)
使用df.eval,您需要仅提供列名称:
pd.eval("df1.A + df1.B")
因为在
df1
的上下文中,很明显“ A”和“ B”是指列名。 还可以使用
index
引用索引和列(除非命名索引,在这种情况下,您将使用名称)。 df1.eval("A + B")
或者,更一般而言,对于具有1个或多个级别索引的任何DataFrame,您可以使用变量在表达式中引用索引的第k级“ ilevel_k”代表“级别k的索引”。 IOW,上面的表达式可以写成
df1.eval("A + ilevel_0")
。这些规则也适用于
query
。访问本地/全局命名空间中的变量
变量内部表达式中必须使用“ @”符号开头,以避免与列名混淆。
df1.eval("A + index")
query
也是如此。毋庸置疑,您的列名必须遵循python中有效标识符命名的规则,才能在
eval
内部访问。有关命名标识符的规则列表,请参见此处。多行查询和赋值
一个鲜为人知的事实是
eval
支持处理赋值的多行表达式。例如,要基于某些列上的某些算术运算在df1中创建两个新列“ E”和“ F”,并基于先前创建的“ E”和“ F”来创建第三列“ G”,我们可以A = 5
df1.eval("A > @A")
...漂亮!但是,请注意,
query
不支持此功能。eval
v / s query
-最终词将
df.query
视为使用pd.eval
作为子例程的函数。 通常,
query
(顾名思义)用于评估条件表达式(即产生True / False值的表达式)并返回与True
结果相对应的行。然后将表达式的结果传递给loc
(在大多数情况下)以返回满足表达式的行。根据文档,该表达式的求值结果首先传递给
DataFrame.loc
,如果由于多维键而失败,则(例如,一个DataFrame),然后将结果传递给
DataFrame.__getitem__()
。此方法使用顶级
pandas.eval()
函数评估传递的查询。
就相似性而言,
query
和df.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
要获取
df1
中“ A”> =“ B”的所有行,我们将使用eval
,如下所示:df1.A
0 5
1 7
2 2
3 8
4 7
Name: A, dtype: int32
df1.B
0 9
1 3
2 0
3 1
4 7
Name: B, dtype: int32
m
表示通过评估表达式“ A> = B”生成的中间结果。然后,我们使用掩码过滤df1
:m = df1.eval("A >= B")
m
0 True
1 False
2 False
3 True
4 True
dtype: bool
但是,对于
query
来说,中间结果“ m”直接传递给loc
,所以对于query
来说,您只需需要执行df1[m]
# df1.loc[m]
A B C D
0 5 0 3 3
3 8 8 1 6
4 7 7 8 1
性能明智,它是完全相同的。
df1.query("A >= B")
A B C D
0 5 0 3 3
3 8 8 1 6
4 7 7 8 1
但是后者更为简洁,可以一步完成相同的操作。
请注意,您也可以像这样用
query
做一些奇怪的事情(例如,返回由df1.index索引的所有行)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)
但是不要。
底线:根据条件表达式查询或过滤行时,请使用
query
。评论
您是否会进一步了解性能,因为这是OP中提出的问题之一?
–user3471881
18/12/18在21:42
@ user3471881我还没有非常深入地研究性能,因为它取决于每个人的用例,但是我对哪些后端和解析器选项通常最适合最大化性能进行了评论。
–cs95
18/12/19在3:41
我们已经从文档中知道numexpr通常可以提高速度,我只是希望对此进行更深入的分析,因为OP中的第一个问题专门提出了该问题,并表示无法在文档中找到答案。另一种选择是稍微修改OP问题以适合答案?
–user3471881
18/12/19在7:36
还是至少分析三种情况的性能:数据大小,查询复杂性和分配?顺便说一句-不用说,我喜欢这篇文章,非常感谢您在这里和整个论坛中的工作。否则我不会问这个:D
–user3471881
18/12/19在7:44
@ user3471881您的观点是正确的,我将考虑采取最佳措施。对我来说,最简单的方法是从OP中删除关于“绩效”的问题,但是为了对职位公正,请让我深入研究一下,看看是否可以更适当地解决这些问题。感谢您的反馈,非常感谢:-)
–cs95
18/12/19在7:46
#2 楼
不错的教程,但是请记住,如果您的数据集少于15,000行,那么在被其简单的语法吸引而广泛使用eval/query
之前,它就存在严重的性能问题。在这种情况下,请使用
df.loc[mask1, mask2]
。参考:https://pandas.pydata.org/pandas-docs/version/0.22/enhancingperf.html#enhancingperf-eval
评论
我对问题和答案都表示赞同,因为我认为这是有用的原始材料。但是,作为可搜索的官方文档的一部分,它会更加有用(您可以通过请求请求进行更新)。您能为pandas.MultiIndex编写其中之一吗?
@tel抱歉,由于几个问题,我不得不撤下并重新发布。 MultiIndex过滤规范的最终版本在这里。 :-)