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

动态评估熊猫的公式表达?

祁权
2023-03-14
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作为字符串表达式中的参数传递?

共有1个答案

苏昊英
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'))

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

详细说明所有支持的特性和语法的列表可以在文档中找到。总之,

  • 除左移(<<)和右移(>)运算符以外的算术运算,例如df+2*pi/s**4%42-the_golden_ratio
  • 比较操作,包括链接比较,例如2
  • 布尔运算,例如df not df_bool 列表元组文字,例如 [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,arctanh,abs和arctan2。

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

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

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

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

使用默认解析器pandas,使用pandas对象实现向量化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")
(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')

这两种类型的解析器之间的另一个区别是带有列表和元组节点的==!=运算符的语义,当使用'pandas'解析器时,它们分别具有与innot in相似的语义。例如,

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

是有效的,并将以与

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

OTOH,pd.eval(“df1==[1,2,3]”,parser='python')将引发NOTIMPLENTEDERROR错误。

有两个选项-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_dict

例如:

pd.eval("df1 > thresh")

UndefinedVariableError: name 'thresh' is not defined
pd.eval("df1 > thresh", local_dict={'thresh': 10})
    
mydict = {'thresh': 5}
# Dictionary values with *string* keys cannot be accessed without 
# using the 'python' engine.
pd.eval('df1 > mydict["thresh"]', engine='python')

考虑问题中的例子

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,可以使用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("[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]]

和字符串列表:

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

对于与整个数据流关联的动态查询,您应该首选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")

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

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

对于query也是如此。

不言而喻,列名必须遵循python中有效标识符命名的规则,才能在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

该表达式的求值结果首先传递给DataFrame.loc,如果由于多维键(例如DataFrame)而失败,则结果将传递给DataFrame.__GetItem__()

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

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

要获取DF1中“a”>=“b”的所有行,我们将使用eval如下所示:

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

m表示通过对表达式“a>=b”求值生成的中间结果。然后使用掩码筛选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

但是,对于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)
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

 类似资料:
  • 我想使用对一个或多个数据帧列执行算术。具体来说,我想移植以下计算公式的代码: …使用进行编码。使用的原因是我想自动化许多工作流,因此动态创建它们对我很有用。 我的两个输入数据帧是: 我试图更好地理解的和参数,以确定如何最好地解决我的问题。我已经阅读了文档,但没有向我说明其中的区别。 应该使用什么参数来确保我的代码以最大的性能工作? 是否有办法将表达式的结果赋值回? 另外,为了使事情更复杂,我如何在

  • 问题内容: 给定两个数据框 我想使用对一列或多列进行算术运算pd.eval。具体来说,我想移植以下代码: …使用进行编码eval。使用的原因eval是我想自动执行许多工作流程,因此动态创建它们对我很有用。 我试图更好地理解和参数,以确定如何最好地解决我的问题。我已经浏览了文档,但是对我而言,区别并不明显。 应该使用什么参数来确保我的代码以最高性能工作? 有没有一种方法可以将表达式的结果赋值给df2

  • 问题内容: 我引用的数据框如下(是列名): 但是我不想被硬编码,我想要一个变量来使其动态。怎么做? TIA 问题答案: 您可以使用方括号对列进行索引: 因此,当您接受输入为a时,您可以执行以下操作: 此外,将列作为属性访问可能导致模棱两可的行为。如具有列命名,并尝试做这可能要列不同的值,或者如果你有一个名为一样像任何有效的方法DF柱或那么这将导致语法错误。 因此,我强烈建议您使用方括号来选择列。

  • 我正在使用NPOI从Excel2003文件中读取数据。这些文件包含如下公式('1:2'!$C$17)。NPOI识别像SUM('1'!$C$17)这样的公式(W/O表2),并计算无效的结果。我使用的是NPOI示例中的常规代码,比如

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

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