给出两个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'))
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的原因是我想自动化许多工作流程,因此动态创建它们对我很有用.
我试图更好地理解引擎和解析器参数,以确定如何最好地解决我的问题.我已经完成了documentation,但差异并没有让我明白.
>应该使用哪些参数来确保我的代码以最高性能运行?
>有没有办法将表达式的结果分配回df2?
>另外,为了使事情变得更复杂,如何将x作为参数传递到字符串表达式中?
解决方法:
这个答案深入研究了pd.eval
,df.query
和df.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
– “失踪手册”
Note
Of the three functions being discussed,pd.eval
is the most important.df.eval
anddf.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可以评估可以包含变量和/或文字的算术表达式.这些表达式必须作为字符串传递.所以,要回答上述问题,你可以做到
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")
可以在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)')
解析器选择:解析器= …参数
在解析表达式字符串以生成语法树时,pd.eval支持两种不同的解析器选项:pandas和python.两者之间的主要区别在于略微不同的优先规则.
使用默认的解析器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")
在这里,括号是必要的.为了做到这一点,传统上,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')
两种类型的解析器之间的另一个区别是具有列表和元组节点的==和!=运算符的语义,当使用“pandas”解析器时,它们分别具有与之相似的语义.例如,
pd.eval("df1 == [1, 2, 3]")
是有效的,并将以与之相同的语义运行
pd.eval("df1 in [1, 2, 3]")
OTOH,pd.eval(“df1 == [1,2,3]”,parser =’python’)将抛出NotImplementedError错误.
后端选择: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”.
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要慢得多.希望这应该为使用这些参数提供令人信服的论据.
目标(inplace)参数和Assignment表达式
这通常不是必需的,因为通常有更简单的方法,但您可以将pd.eval的结果分配给实现__getitem__的对象,例如dicts和(您猜对了)DataFrames.
考虑问题中的示例
060017
要为df2指定一列“D”,我们这样做
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.
虽然目标论点很有趣,但你很少需要使用它.
如果你想用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]]
但是,问题是长度大于10的列表:
pd.eval(["[1]"] * 100, engine='python') # Works
pd.eval(["[1]"] * 101, engine='python')
AttributeError: 'PandasExprVisitor' object has no attribute 'visit_Ellipsis'
DataFrame.eval
– 与pandas.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 _eval(expr, inplace=inplace, **kwargs)
eval创建参数,进行一些验证,并将参数传递给pd.eval.
有关更多信息,请参阅:when to use DataFrame.eval() versus pandas.eval() or python eval()
用法差异
使用DataFrames v / s系列表达式的表达式
对于与整个DataFrame关联的动态查询,您应该更喜欢pd.eval.例如,当您调用df1.eval或df2.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”指的是列名.
您还可以使用索引引用索引和列(除非命名索引,在这种情况下您将使用该名称).
df1.eval("A + index")
或者,更一般地,对于索引具有1个或更多级别的任何DataFrame,可以使用变量“ilevel_k”(表示“级别为k的索引”)来引用表达式中索引的第k级. IOW,上面的表达式可以写成df1.eval(“A ilevel_0”).
这些规则也适用于查询.
访问本地/全局命名空间中的变量
表达式内提供的变量必须以“@”符号开头,以避免与列名混淆.
A = 5
df1.eval("A > @A")
对于查询/同样如此
毫无疑问,您的列名必须遵循python中有效标识符命名的规则才能在eval中访问.有关命名标识符的规则列表,请参阅here.
多行查询和分配
一个鲜为人知的事实是eval支持处理赋值的多行表达式.例如,要根据某些列上的某些算术运算在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
eval v / s查询 – 最终单词
将df.query视为使用pd.eval作为子例程的函数是有帮助的.
通常,查询(顾名思义)用于评估条件表达式(即,导致True / False值的表达式)并返回与True结果对应的行.然后将表达式的结果传递给loc(在大多数情况下)以返回满足表达式的行.根据文件,
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.
就相似性而言,查询和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
要获得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
但是,对于查询,中间结果“m”直接传递给loc,因此使用查询,您只需要执行
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索引的所有行)
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
但不要.
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。