当前位置: 首页 > 知识库问答 >
问题:

在Pandas中动态评估公式中的表达式

米浩穰
2023-03-14

我想使用pd.eval对一个或多个数据帧列执行算术。具体来说,我想移植以下计算公式的代码:

x = 5
df2['D'] = df1['A'] + (df1['B'] * x)

…使用pd.eval进行编码。使用pd.eval的原因是我想自动化许多工作流,因此动态创建它们对我很有用。

我的两个输入数据帧是:

import pandas as pd
import numpy as np

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引擎解析器参数,以确定如何最好地解决我的问题。我已经阅读了文档,但没有向我说明其中的区别。

  1. 应该使用什么参数来确保我的代码以最大的性能工作?
  2. 是否有办法将表达式的结果赋值回df2
  3. 另外,为了使事情更复杂,我如何在字符串表达式中传递x作为参数?

共有2个答案

徐阳炎
2023-03-14

已经有很多很好的教程,但是请记住,在被其简单的语法吸引而疯狂使用eval/query之前,如果您的数据集的行数少于15000行,则会出现严重的性能问题。

在这种情况下,只需使用df.loc[mask1,mask2]

请参阅:通过eval()进行表达式计算

吕和风
2023-03-14

可以使用1)pd.eval(),2)df.query(),或3)df.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'))
df3 = pd.DataFrame(np.random.choice(10, (5, 4)), columns=list('ABCD'))
df4 = pd.DataFrame(np.random.choice(10, (5, 4)), columns=list('ABCD'))

这是熊猫文档应该包含的“失踪手册”。注意:在讨论的三个函数中,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")

详细列出所有支持的功能和语法的列表可以在留档中找到。总之,

  • 除左移以外的算术运算(

本节文档还指定了不受支持的语法规则,包括set/dict文字、if-else语句、循环和理解以及生成器表达式。

从列表中,很明显,您还可以传递涉及索引的表达式,例如

pd.eval('df1.A * (df1.index > 1)')

pd.eval在解析表达式字符串以生成语法树时支持两种不同的解析器选项:pandaspython。两者之间的主要区别在于优先级规则略有不同。

使用默认解析器pandas,重载的位运算符

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")

这里,括号是必要的。按照惯例,需要括号来覆盖位运算符的更高优先级:

(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().

使用parser='python',如果您想在计算字符串时与python的实际操作员优先级规则保持一致。

pd.eval("(df1 > df2) & (df3 < df4)", parser='python')

这两种解析器之间的另一个区别是==的语义= 'pandas'解析器时,带有列表和元组节点的code>运算符,其语义分别与中的中的相似。例如

pd.eval("df1 == [1, 2, 3]")

是有效的,并将使用与相同的语义运行

pd.eval("df1 in [1, 2, 3]")

OTOH,pd.eval("df1==[1,2,3]", parser='python')将抛出一个Not实现错误错误。

有两个选项-numexpr(默认值)和pythonnumexpr选项使用针对性能优化的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

不幸的是,这种方法在Numexpr引擎上没有性能优势,并且很少有安全措施来确保危险表达式不被计算,所以使用时请自担风险!通常不建议将此选项更改为'python',除非您知道自己在做什么。

有时,为表达式中使用但当前未在命名空间中定义的变量提供值很有用。您可以将词典传递给local\u dict

例如:

pd.eval("df1 > thresh")

UndefinedVariableError: name 'thresh' is not defined

此操作失败,因为未定义阈值。然而,这是可行的:

pd.eval("df1 > thresh", 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_dictglobal_dict要慢得多。希望这将为使用这些参数提供令人信服的论据。

这通常不是一个要求,因为通常有更简单的方法,但是您可以将pd.eval的结果分配给实现\uuuu getitem\uuuuu的对象,例如dicts和(您猜到的)数据帧。

考虑问题中的例子

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的就地修改(但可以…继续阅读)。再举一个例子:

pd.eval('df1.A + df2.A')

0    10
1    11
2     7
3    16
4    10
dtype: int32

如果您想(例如)将其分配回DataFrame,您可以使用以下参数:

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\u 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]]

和字符串列表:

pd.eval(["[1, 2, 3]", "[4, 5]", "[10]"], engine='python')
[[1, 2, 3], [4, 5], [10]]

但是,对于长度大于100的列表,问题是:

pd.eval(["[1]"] * 100, engine='python') # Works
pd.eval(["[1]"] * 101, engine='python')

AttributeError: 'PandasExprVisitor' object has no attribute 'visit_Ellipsis'

可以在此处找到此错误的更多信息、原因、修复和解决方法。

如上所述,df.eval在引擎盖下调用pd.eval,并有一些并列的参数。v0.23源代码显示了以下内容:

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创建参数,进行少量验证,并将参数传递给pd.eval

有关更多信息,请阅读:何时使用DataFrame.eval()与pandas.eval()或Python eval()比较

对于与整个DataFrames关联的动态查询,您应该首选pd.eval。例如,当调用df1.evaldf2.eval时,没有简单的方法来指定pd.eval("df1 df2")的等价物。

另一个主要区别是如何访问列。例如,要在df1中添加两列“A”和“B”,可以使用以下表达式调用pd.eval

pd.eval("df1.A + df1.B")

使用df.eval,只需提供列名:

df1.eval("A + B")

因为,在df1的上下文中,很明显"A"和"B"指的是列名。

您还可以使用index引用索引和列(除非索引已命名,在这种情况下,您将使用名称)。

df1.eval("A + index")

或者,更一般地,对于索引具有1个或多个级别的任何数据帧,您可以使用变量“ilevel_k”在表达式中引用索引的kth级别,该变量表示“index at level k”。照此,上面的表达式可以写成df1.eval(“A ilevel_0”)

这些规则也适用于df.query

表达式中提供的变量必须在“@”符号之前,以避免与列名混淆。

A = 5
df1.eval("A > @A")

这同样适用于查询

不用说,列名必须遵循Python中有效标识符命名的规则,才能在eval中访问。有关命名标识符的规则列表,请参见此处。

一个鲜为人知的事实是,eval支持处理赋值的多行表达式(而query不支持)。例如,要在df1中基于对某些列的一些算术运算创建两个新列“E”和“F”,并基于先前创建的“E”和“F”创建第三列“G”,我们可以这样做

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

df.query视为使用pd.eval作为子例程的函数是有帮助的。

通常,查询(顾名思义)用于计算条件表达式(即,导致True/False值的表达式),并返回对应于True结果的行。然后将表达式的结果传递给loc(在大多数情况下),以返回满足表达式的行。根据留档,

此表达式的计算结果首先传递给DataFrame.loc,如果由于多维键(例如,DataFrame)而失败,则结果将传递给DataFrame.\uuu getitem\uuuuu()

此方法使用顶级pandas.eval()函数来计算传递的查询。

就相似性而言,查询df.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

获取"A"所在的所有行

m = df1.eval("A >= B")
m
0     True
1    False
2    False
3     True
4     True
dtype: bool

m表示通过计算表达式“A”生成的中间结果

df1[m]
# df1.loc[m]

   A  B  C  D
0  5  0  3  3
3  8  8  1  6
4  7  7  8  1

但是,使用query,中间结果“m”直接传递到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)

但是后者更加简洁,在一个步骤中表达了相同的操作。

请注意,您还可以像这样使用query执行一些奇怪的操作(例如,返回由df1.index索引的所有行)

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

但是不要。

底线:查询或筛选基于条件表达式的行时,请使用查询

 类似资料:
  • ...使用进行编码。使用的原因是我想要自动化许多工作流,因此动态地创建它们对我会很有用。 我的两个输入数据流是: 我试图更好地理解的和参数,以确定如何最好地解决我的问题。我已经看过了文件,但我并不清楚其中的区别。 应使用哪些参数来确保代码以最大性能工作? 是否有方法将表达式的结果赋回到? 另外,为了使事情更加复杂,如何将作为字符串表达式中的参数传递?

  • 问题内容: 在我们的项目中,我们需要在没有任何数据库服务器的情况下评估SQL语句。您能否建议一个免费的Java库,该库能够评估基于数学的SQL语句并返回结果? 例如; 输入 输出 可能会被称为 问题答案: 如下面的代码所示,可以使用ZQL来实现。但是我严重建议您选择一个简单的嵌入式数据库,例如H2(此处为示例),而应使用它(项目运行状况要高得多)。 使用H2: 输出: 要使用它,请将其添加到您的:

  • 问题内容: 对象在Python中评估的真值是多少? 相关问题 Python中对象的布尔值:有关覆盖对象评估方式的讨论 问题答案: 可以测试任何对象的真值,以在if或while条件中使用或用作以下布尔运算的操作数。以下值为“假”: 没有 假 任何数值类型的零,例如,,,。 任何空序列,例如,,。 任何空映射,例如。 用户定义的类的实例,如果该类定义了或方法,则该方法返回整数0或bool value时

  • 问题内容: 我正在寻找一种相对简单的方法(与编写解析器相比)来评估Java中的布尔表达式,并且我不想使用JEP库。 我有一个String表达式,例如:我的目标是用值替换变量。 有没有一种方法可以评估此​​表达式? 请记住,这可以是任何深度,因此编写解析器将非常复杂。 问题答案: 您可以使用Java6中的脚本引擎,并选择任何流行的脚本语言,例如Scala,Ruby,Python,Groovy和Jav

  • 问题内容: 什么是实现将采用字符串并根据运算符优先级输出结果的python程序的最佳方法(例如:“ 4 + 3 * 5”将输出19)。我在谷歌上寻找解决这个问题的方法,但是它们都太复杂了,我正在寻找一个(相对)简单的方法。 澄清:我需要比eval()稍微先进的东西-我希望能够添加其他运算符(例如,最大运算符-4 $ 2 = 4),或者,我对此在学术上比对专业更感兴趣-我想知道 该怎么 做。 问题答

  • 本文向大家介绍评估后缀表达式,包括了评估后缀表达式的使用技巧和注意事项,需要的朋友参考一下 为了求解数学表达式,我们需要前缀或后缀形式。将中缀转换为后缀后,我们需要后缀评估算法来找到正确的答案。 在这里,我们还必须使用堆栈数据结构来解决后缀表达式。 从后缀表达式中,找到一些操作数后,将它们压入堆栈。找到某个运算符后,将从堆栈中弹出两个项目,并按正确的顺序执行操作。之后,结果也被压入堆栈中以备将来使