Dynamic Expression Evaluation in pandas using pd.eval()
给出两个DataFrame
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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 |
我想使用
1 2 | x = 5 df2['D'] = df1['A'] + (df1['B'] * x) |
...使用
我想更好地理解
这个答案深入研究了
建立
示例将涉及这些DataFrame(除非另有说明)。
1 2 3 4 5 | 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')) |
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.Ok.
1 2 | x = 5 pd.eval("df1.A + (df1.B * x)") |
有些事情需要注意:
我将在解释下面
1 2 | pd.eval("df1.A + df2.A") # Valid, returns a pd.Series object pd.eval("abs(df1) ** .5") # Valid, returns a pd.DataFrame object |
...等等。条件表达式也以相同的方式受支持。以下语句都是有效的表达式,将由引擎进行评估。
1 2 3 4 5 | 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") |
可以在文档中找到详细说明所有支持的功能和语法的列表。综上所述,
Arithmetic operations except for the left shift ( << ) and right shift (>> ) operators, e.g.,df + 2 * pi / s ** 4 % 42 - the_golden_ratioComparison 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.Ok.
本文档的这一部分还指定了不受支持的语法规则,包括
从列表中可以看出,您还可以传递涉及索引的表达式,例如
1 | pd.eval('df1.A * (df1.index > 1)') |
解析器选择:
解析表达式字符串以生成语法树时,
使用默认解析器
1 | pd.eval("(df1 > df2) & (df3 < df4)") |
将是一样的
1 2 | pd.eval("df1 > df2 & df3 < df4") # pd.eval("df1 > df2 & df3 < df4", parser='pandas') |
而且也一样
1 | pd.eval("df1 > df2 and df3 < df4") |
在这里,括号是必要的。为了做到这一点,传统上,parens将被要求覆盖按位运算符的更高优先级:
1 | (df1 > df2) & (df3 < df4) |
没有它,我们最终会
1 2 3 | 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的实际运算符优先级规则的一致性,请使用
1 | pd.eval("(df1 > df2) & (df3 < df4)", parser='python') |
两种类型的解析器之间的另一个区别是具有列表和元组节点的
1 | pd.eval("df1 == [1, 2, 3]") |
是有效的,并将以与之相同的语义运行
1 | pd.eval("df1 in [1, 2, 3]") |
OTOH,
后端选择:
有两个选项 -
使用
1 2 3 4 5 6 7 | 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 |
遗憾的是,这种方法与
有时,为表达式中使用的变量提供值,但当前未在命名空间中定义的值很有用。您可以将字典传递给
例如,
1 2 3 | pd.eval("df1 > thresh") UndefinedVariableError: name 'thresh' is not defined |
这会失败,因为未定义
1 | pd.eval("df1 > x", local_dict={'thresh': 10}) |
当您从字典中提供变量时,这非常有用。或者,使用
1 2 3 4 | mydict = {'thresh': 5} # Dictionary values with *string* keys cannot be accessed without # using the 'python' engine. pd.eval('df1 > mydict["thresh"]', engine='python') |
但这可能比使用
这通常不是必需的,因为通常有更简单的方法,但您可以将
考虑问题中的示例
1
2 x = 5
df2['D'] = df1['A'] + (df1['B'] * x)
要将列"D"分配给
1 2 3 4 5 6 7 8 | 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 |
这不是
1 2 3 4 5 6 7 8 | pd.eval('df1.A + df2.A') 0 10 1 11 2 7 3 16 4 10 dtype: int32 |
如果您想(例如)将其分配回DataFrame,您可以使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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 |
如果要在
1 2 3 4 5 6 7 8 9 10 11 | 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 |
如果设置
虽然
如果您想使用
1 2 3 4 5 6 7 8 9 10 | 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 |
注意
1 2 | pd.eval("[1, 2, 3]") array([1, 2, 3], dtype=object) |
它还可以使用
1 2 | pd.eval("[[1, 2, 3], [4, 5], [10]]", engine='python') [[1, 2, 3], [4, 5], [10]] |
和字符串列表:
1 2 | pd.eval(["[1, 2, 3]","[4, 5]","[10]"], engine='python') [[1, 2, 3], [4, 5], [10]] |
但是,问题是长度大于10的列表:
1 2 3 4 | pd.eval(["[1]"] * 100, engine='python') # Works pd.eval(["[1]"] * 101, engine='python') AttributeError: 'PandasExprVisitor' object has no attribute 'visit_Ellipsis' |
可以在此处找到更多信息,此错误,原因,修复和解决方法。
如上所述,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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) |
有关更多信息,您可以阅读:何时使用DataFrame.eval()与pandas.eval()或python eval()
用法差异
使用DataFrames v / s系列表达式的表达式
对于与整个DataFrame相关联的动态查询,您应该更喜欢
指定列名称
另一个主要区别是如何访问列。例如,要在
1 | pd.eval("df1.A + df1.B") |
使用df.eval,您只需提供列名:
1 | df1.eval("A + B") |
因为,在
您还可以使用
1 | df1.eval("A + index") |
或者,更一般地,对于索引具有1个或更多级别的任何DataFrame,可以使用变量"ilevel_k"(表示"级别为k的索引")来引用表达式中索引的第k级。 IOW,上面的表达式可以写成
这些规则也适用于
访问本地/全局命名空间中的变量
表达式内提供的变量必须以"@"符号开头,以避免与列名混淆。
1 2 | A = 5 df1.eval("A > @A") |
毫无疑问,您的列名必须遵循python中有效标识符命名的规则才能在
多行查询和分配
一个鲜为人知的事实是
1 2 3 4 5 6 7 8 9 10 11 12 | 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 |
...俏皮!但请注意,
将
通常,
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__() .Ok.
This method uses the top-level
pandas.eval() function to evaluate the
passed query.Ok.
就相似性而言,
如上所述,这两者之间的关键区别在于它们如何处理表达式结果。当您通过这两个函数实际运行表达式时,这一点就很明显了。例如,考虑一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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 |
要获取
1 2 3 4 5 6 7 8 | m = df1.eval("A >= B") m 0 True 1 False 2 False 3 True 4 True dtype: bool |
1 2 3 4 5 6 7 | df1[m] # df1.loc[m] A B C D 0 5 0 3 3 3 8 8 1 6 4 7 7 8 1 |
但是,使用
1 2 3 4 5 6 | df1.query("A >= B") A B C D 0 5 0 3 3 3 8 8 1 6 4 7 7 8 1 |
性能方面,它完全相同。
1 2 3 4 5 6 7 | 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) |
但后者更简洁,并且只需一步即可表达相同的操作。
请注意,您也可以像这样使用
1 2 3 4 5 6 7 8 9 | 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 |
但不要。
底线:根据条件表达式查询或过滤行时,请使用
好。
已经很好的教程了,但请记住,在通过其更简单的语法吸引使用
在这种情况下,只需使用
参考:https://pandas.pydata.org/pandas-docs/version/0.22/enhancingperf.html#enhancingperf-eval