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

2024-12-25 08:51:00
admin
原创
121
摘要:问题描述:我想使用 对一个或多个数据框列执行算术运算pd.eval。具体来说,我想移植以下用于评估公式的代码:x = 5 df2['D'] = df1['A'] + (df1['B'] * x) ...使用 进行编码pd.eval。使用 的原因pd.eval是我想自动化许多工作流程,因此动态创建它们对我很有用...

问题描述:

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

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

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

我的两个输入 DataFrames 是:

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.evalengine论据parser,以确定如何最好地解决我的问题。我已阅读文档,但差异并没有向我说明清楚。

  1. 应该使用什么参数来确保我的代码以最佳性能运行?

  2. 有没有办法将表达式的结果赋回给df2

  3. 此外,为了使事情变得更加复杂,我如何x在字符串表达式中传递参数?


解决方案 1:

您可以使用 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'))

1)pandas.eval

这是 pandas doc 应该包含的“缺失手册”。
注意:在讨论的三个函数中,pd.eval是最重要的。df.eval并在后台df.query调用
pd.eval。这三个函数的行为和用法大致一致,但有一些细微的语义差异,稍后将重点介绍。本节将介绍这三个函数共有的功能 - 包括(但不限于)允许的语法、优先规则关键字参数。

pd.eval可以计算由变量和/或文字组成的算术表达式。这些表达式必须以字符串形式传递。因此,要回答上述问题,您可以这样做

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

这里需要注意以下几点:

  1. 整个表达式是一个字符串

  2. df1df2和引用全局命名空间中的变量,这些变量在解析表达式时x被获取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 < 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、arccosh、arcsinh、arctanh、abs 和 arctan2。

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

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

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

1a)解析器选择:parser=...论点

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

使用默认解析器pandas,重载的按位运算符&和(使用 pandas 对象实现矢量化的 AND 和 OR 运算)将具有与and|相同的运算符优先级。因此,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().

parser='python'如果您想在评估字符串时与 python 的实际运算符优先规则保持一致,请使用。

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

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

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

有效,并将以与以下相同的语义运行

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

另一方面,pd.eval("df1 == [1, 2, 3]", parser='python')会引发NotImplementedError错误。

1b)后端选择:engine=...论点

有两个选项 - numexpr(默认)和python。该numexpr选项使用针对性能进行了优化的numexpr后端。

使用 Python 后端,表达式的求值方式类似于将表达式传递给 Pythoneval函数。您可以灵活地在表达式内部执行更多操作,例如字符串操作。

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'除非您知道自己在做什么。

1c)local_dictglobal_dict论点

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

例如:

pd.eval("df1 > thresh")

UndefinedVariableError: name 'thresh' is not defined

thresh由于未定义,因此失败。但是,以下方法有效:

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_dict`global_dict

1d) target(+ inplace) 参数和赋值表达式

这通常不是一个要求,因为通常有更简单的方法可以做到这一点,但你可以将结果分配给实现诸如s 和(你猜对了)DataFrames 的pd.eval对象。__getitem__`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(但可以……继续阅读)。考虑另一个例子:

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非预期用途是以与非常相似的方式解析文字字符串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]]

以及字符串列表:

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'

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


2)DataFrame.eval

如上所述,在后台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()


2a)使用差异

2a1) DataFrames 表达式与系列表达式

对于与整个 DataFrames 相关的动态查询,您应该选择。例如,当您调用或 时,pd.eval没有简单的方法来指定 的等效项。pd.eval("df1 + df2")`df1.eval`df2.eval

2a2)指定列名

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

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

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

df1.eval("A + B")

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

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

df1.eval("A + index")

或者,更一般地,对于任何具有 1 个或多个级别的索引的 DataFrame,您可以在表达式中使用变量“ilevel_k”引用索引的第k 级,该变量代表“第k 级索引”。换句话说,上面的表达式可以写成。df1.eval("A + ilevel_0")

这些规则也适用于df.query

2a3)访问本地/全局命名空间中的变量

表达式内部提供的变量前面必须有“@”符号,以避免与列名混淆。

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

也一样query

毋庸置疑,您的列名必须遵循 Python 中有效标识符命名的规则,才能在 中访问eval。请参阅此处了解有关命名标识符的规则列表。

2a4)多行查询和赋值

一个鲜为人知的事实是,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

3)eval对比query

将其视为一个用作子程序df.query的函数会有所帮助。pd.eval

通常,query(顾名思义)用于评估条件表达式(即结果为 True/False 值的表达式)并返回与结果对应的行True。然后将表达式的结果传递给loc(大多数情况下)以返回满足表达式的行。根据文档,

该表达式的评估结果首先传递给
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

为了获取其中“A”> =“B”的所有行,我们可以这样df1使用: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)

但后者更简洁,只需一步即可表达相同的操作。

请注意,您还可以这样做奇怪的事情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

但不要这样。

底线:请query在根据条件表达式查询或过滤行时使用。

解决方案 2:

目前已经有了很好的教程,但是请记住,在疯狂地使用它之前eval/query,如果它的语法更简单,那么如果你的数据集少于 15,000 行,它会出现严重的性能问题。

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

参考:通过 eval() 进行表达式求值

在此处输入图片描述

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用