使用pd.eval()

时间:2018-12-14 12:39:22

标签: python pandas dataframe eval

客观动机

evalquery功能强大,但在熊猫API套件中却被低估了,它们的用法远未得到充分记录或理解。通过适当的照顾,queryeval可以大大简化代码,提高性能,并成为创建动态工作流的强大工具。

此规范QnA的目的是使用户更好地理解这些功能,并通过清晰易懂的示例讨论一些鲜为人知的功能,如何使用它们以及如何最好地使用它们。这篇文章将要解决的两个主要主题是

  1. 了解engine中的parsertargetpd.eval自变量,以及如何将其用于计算表达式
  2. 了解pd.evaldf.evaldf.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的原因是我想使许多工作流程自动化,所以动态创建它们对我很有用。

我试图更好地理解engineparser参数,以确定如何最好地解决我的问题。我经历过documentation,但对我而言,区别并不明显。

  1. 应使用哪些参数来确保我的代码以最高性能工作?
  2. 是否可以将表达式的结果分配回df2
  3. 此外,为了使事情变得更复杂,如何在字符串表达式内将x作为参数传递?

3 个答案:

答案 0 :(得分:44)

此答案将深入探讨pd.evaldf.querydf.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.evaldf.query通话   pd.eval在幕后。行为和用法或多或少   这三个功能保持一致,但语义有些许变化   变体,稍后将重点介绍。本节将   介绍这三个功能共有的功能-包括(但不限于)允许的语法,优先级规则关键字参数。

pd.eval可以计算算术表达式,该表达式可以包含变量和/或文字。这些表达式必须作为字符串传递。因此,按照说明回答问题,您可以

x = 5
pd.eval("df1.A + (df1.B * x)")  

这里要注意一些事情:

  1. 整个表达式是一个字符串
  2. df1df2x引用全局命名空间中的变量,它们在解析表达式时由eval拾取
  3. 使用属性访问器索引访问特定列。您也可以使用"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 < df4not df_bool   listtuple文字,例如[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在解析表达式字符串以生成语法树时支持两种不同的解析器选项:pandaspython。两者之间的主要区别在于优先级规则略有不同。

使用默认的解析器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=...(默认)和numexprpython选项使用为性能优化的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.evalv0.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()

用法差异

具有DataFrames v / s系列表达式的表达式

对于与整个DataFrame关联的动态查询,您应该首选pd.eval。例如,当您调用pd.evalpd.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

enter image description here

答案 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。