从 Pandas 中的公式动态计算表达式
Dynamically evaluate an expression from a formula in Pandas
我想使用 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
的 engine
和 parser
参数以确定如何最好地解决我的问题。我已经经历了 the documentation,但我并不清楚其中的区别。
- 应该使用哪些参数来确保我的代码以最佳性能运行?
- 有没有办法将表达式的结果赋值回
df2
?
- 此外,为了让事情变得更复杂,我如何将
x
作为参数传递到字符串表达式中?
您可以使用 1) pd.eval()
, 2) df.query()
, or 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
This is the "Missing Manual" that pandas doc should contain.
Note: of the three functions being discussed, pd.eval
is the most important. df.eval
and df.query
call
pd.eval
under the hood. Behaviour and usage is more or less
consistent across the three functions, with some minor semantic
variations which will be highlighted later. This section will
introduce functionality that is common across all the three functions - this includes, (but not limited to) allowed syntax, precedence rules, and keyword arguments.
pd.eval
可以计算由变量 and/or 文字组成的算术表达式。这些表达式必须作为字符串传递。所以,要回答问题,你可以
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")
可以在 the documentation 中找到详细说明所有支持的功能和语法的列表。综上所述,
- Arithmetic operations except for the left shift (
<<
) and right shift (>>
) operators, e.g., df + 2 * pi / s ** 4 % 42
- the_golden_ratio
- Comparison operations, including chained comparisons, e.g.,
2 < df < df2
- Boolean operations, e.g.,
df < df2 and df3 < df4
or not df_bool
list
and tuple
literals, e.g., [1, 2]
or (1, 2)
- Attribute access, e.g.,
df.a
- Subscript expressions, e.g.,
df[0]
- Simple variable evaluation, e.g.,
pd.eval('df')
(this is not very useful)
- Math functions: sin, cos, exp, log, expm1, log1p, sqrt, sinh, cosh, tanh, arcsin, arccos, arctan, arccosh, arcsinh, arctanh, abs and
arctan2.
文档的这一部分还指定了不受支持的语法规则,包括 set
/dict
文字、if-else 语句、循环和理解以及生成器表达式。
从表中可以看出,也可以传递涉及索引的表达式,如
pd.eval('df1.A * (df1.index > 1)')
1a) 解析器选择: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")
这里,括号是必须的。按照惯例,需要括号来覆盖按位运算符的更高优先级:
(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
和 not in
分别在使用 'pandas'
解析器时。例如,
pd.eval("df1 == [1, 2, 3]")
有效,运行与
具有相同的语义
pd.eval("df1 in [1, 2, 3]")
OTOH,pd.eval("df1 == [1, 2, 3]", parser='python')
将引发 NotImplementedError
错误。
1b) 后端选择:engine=...
参数
有两个选项 - numexpr
(默认值)和 python
。 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
不幸的是,与 numexpr
引擎相比,此方法 没有 性能优势,并且几乎没有安全措施来确保不评估危险的表达式,因此 使用风险自负!通常不建议将此选项更改为 'python'
,除非您知道自己在做什么。
1c) local_dict
和 global_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
) 参数和赋值表达式
这通常不是必需的,因为通常有更简单的方法来做到这一点,但您可以将 pd.eval
的结果分配给实现 __getitem__
的对象,例如 dict
s,和(你猜对了)DataFrames。
考虑问题中的例子
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 source code 显示:
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 <b>_eval(expr, inplace=inplace, **kwargs)</b>
eval
创建参数,进行一些验证,然后将参数传递给 pd.eval
。
更多内容,您可以继续阅读:
2a) 用法差异
2a1) 数据框表达式与序列表达式
对于与整个 DataFrame 关联的动态查询,您应该更喜欢 pd.eval
。例如,当您调用 df1.eval
或 df2.eval
.
时,没有简单的方法来指定 pd.eval("df1 + df2")
的等价物
2a2) 指定列名
另一个主要区别是访问列的方式。例如,要在 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 个或多个级别的索引的任何 DataFrame,您可以使用变量 "ilevel_k" 代表“iindex at level k”。 IOW,上面的表达式可以写成 df1.eval("A + ilevel_0")
.
这些规则也适用于 df.query
。
2a3) 访问 Local/Global 命名空间中的变量
表达式内部提供的变量必须以“@”符号开头,以避免与列名混淆。
A = 5
df1.eval("A > @A")
query
也是如此。
不用说,您的列名必须遵循 Python 中有效标识符命名的规则才能在 eval
中访问。有关命名标识符的规则列表,请参阅 here。
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 值的表达式)和 return 对应于 True
结果。然后将表达式的结果传递给 loc
(在大多数情况下)到 return 满足该表达式的行。根据文档,
The result of the evaluation of this expression is first passed to
DataFrame.loc
and if that fails because of a multidimensional key
(e.g., a DataFrame) then the result will be passed to
DataFrame.__getitem__()
.
This method uses the top-level pandas.eval()
function to evaluate the
passed query.
就相似性而言,query
和 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
要获取 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)
但后者更简洁,一步表达相同的操作
请注意,您也可以像这样用 query
做一些奇怪的事情(比如,return 所有由 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
。
已经有很棒的教程,但请记住,在疯狂使用 eval/query
之前,如果您的数据集少于 15,000 行,它会因语法更简单而出现严重的性能问题。
在那种情况下,只需使用 df.loc[mask1, mask2]
。
参考:Expression Evaluation via eval()
我想使用 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
的 engine
和 parser
参数以确定如何最好地解决我的问题。我已经经历了 the documentation,但我并不清楚其中的区别。
- 应该使用哪些参数来确保我的代码以最佳性能运行?
- 有没有办法将表达式的结果赋值回
df2
? - 此外,为了让事情变得更复杂,我如何将
x
作为参数传递到字符串表达式中?
您可以使用 1) pd.eval()
, 2) df.query()
, or 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
This is the "Missing Manual" that pandas doc should contain. Note: of the three functions being discussed,
pd.eval
is the most important.df.eval
anddf.query
callpd.eval
under the hood. Behaviour and usage is more or less consistent across the three functions, with some minor semantic variations which will be highlighted later. This section will introduce functionality that is common across all the three functions - this includes, (but not limited to) allowed syntax, precedence rules, and keyword arguments.
pd.eval
可以计算由变量 and/or 文字组成的算术表达式。这些表达式必须作为字符串传递。所以,要回答问题,你可以
x = 5
pd.eval("df1.A + (df1.B * x)")
这里有几点需要注意:
- 整个表达式是一个字符串
df1
、df2
和x
引用全局命名空间中的变量,这些在解析表达式 时由- 使用属性访问器索引访问特定列。您也可以使用
"df1['A'] + (df1['B'] * x)"
来达到同样的效果。
eval
选取
我将在下面解释 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")
可以在 the documentation 中找到详细说明所有支持的功能和语法的列表。综上所述,
- Arithmetic operations except for the left shift (
<<
) and right shift (>>
) operators, e.g.,df + 2 * pi / s ** 4 % 42
- the_golden_ratio- Comparison operations, including chained comparisons, e.g.,
2 < df < df2
- Boolean operations, e.g.,
df < df2 and df3 < df4
ornot df_bool
list
andtuple
literals, e.g.,[1, 2]
or(1, 2)
- Attribute access, e.g.,
df.a
- Subscript expressions, e.g.,
df[0]
- Simple variable evaluation, e.g.,
pd.eval('df')
(this is not very useful)- Math functions: sin, cos, exp, log, expm1, log1p, sqrt, sinh, cosh, tanh, arcsin, arccos, arctan, arccosh, arcsinh, arctanh, abs and arctan2.
文档的这一部分还指定了不受支持的语法规则,包括 set
/dict
文字、if-else 语句、循环和理解以及生成器表达式。
从表中可以看出,也可以传递涉及索引的表达式,如
pd.eval('df1.A * (df1.index > 1)')
1a) 解析器选择: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")
这里,括号是必须的。按照惯例,需要括号来覆盖按位运算符的更高优先级:
(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
和 not in
分别在使用 'pandas'
解析器时。例如,
pd.eval("df1 == [1, 2, 3]")
有效,运行与
具有相同的语义pd.eval("df1 in [1, 2, 3]")
OTOH,pd.eval("df1 == [1, 2, 3]", parser='python')
将引发 NotImplementedError
错误。
1b) 后端选择:engine=...
参数
有两个选项 - numexpr
(默认值)和 python
。 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
不幸的是,与 numexpr
引擎相比,此方法 没有 性能优势,并且几乎没有安全措施来确保不评估危险的表达式,因此 使用风险自负!通常不建议将此选项更改为 'python'
,除非您知道自己在做什么。
1c) local_dict
和 global_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
) 参数和赋值表达式
这通常不是必需的,因为通常有更简单的方法来做到这一点,但您可以将 pd.eval
的结果分配给实现 __getitem__
的对象,例如 dict
s,和(你猜对了)DataFrames。
考虑问题中的例子
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 source code 显示:
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 <b>_eval(expr, inplace=inplace, **kwargs)</b>
eval
创建参数,进行一些验证,然后将参数传递给 pd.eval
。
更多内容,您可以继续阅读:
2a) 用法差异
2a1) 数据框表达式与序列表达式
对于与整个 DataFrame 关联的动态查询,您应该更喜欢 pd.eval
。例如,当您调用 df1.eval
或 df2.eval
.
pd.eval("df1 + df2")
的等价物
2a2) 指定列名
另一个主要区别是访问列的方式。例如,要在 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 个或多个级别的索引的任何 DataFrame,您可以使用变量 "ilevel_k" 代表“iindex at level k”。 IOW,上面的表达式可以写成 df1.eval("A + ilevel_0")
.
这些规则也适用于 df.query
。
2a3) 访问 Local/Global 命名空间中的变量
表达式内部提供的变量必须以“@”符号开头,以避免与列名混淆。
A = 5
df1.eval("A > @A")
query
也是如此。
不用说,您的列名必须遵循 Python 中有效标识符命名的规则才能在 eval
中访问。有关命名标识符的规则列表,请参阅 here。
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 值的表达式)和 return 对应于 True
结果。然后将表达式的结果传递给 loc
(在大多数情况下)到 return 满足该表达式的行。根据文档,
The result of the evaluation of this expression is first passed to
DataFrame.loc
and if that fails because of a multidimensional key (e.g., a DataFrame) then the result will be passed toDataFrame.__getitem__()
.This method uses the top-level
pandas.eval()
function to evaluate the passed query.
就相似性而言,query
和 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
要获取 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)
但后者更简洁,一步表达相同的操作
请注意,您也可以像这样用 query
做一些奇怪的事情(比如,return 所有由 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
。
已经有很棒的教程,但请记住,在疯狂使用 eval/query
之前,如果您的数据集少于 15,000 行,它会因语法更简单而出现严重的性能问题。
在那种情况下,只需使用 df.loc[mask1, mask2]
。
参考:Expression Evaluation via eval()