Pandas 2.2 中文官方教程和指南(二十三)(3)https://developer.aliyun.com/article/1508855
eval()
性能比较
pandas.eval()
在包含大型数组的表达式中表现良好。
In [58]: nrows, ncols = 20000, 100 In [59]: df1, df2, df3, df4 = [pd.DataFrame(np.random.randn(nrows, ncols)) for _ in range(4)]
DataFrame
算术:
In [60]: %timeit df1 + df2 + df3 + df4 7.34 ms +- 117 us per loop (mean +- std. dev. of 7 runs, 100 loops each)
In [61]: %timeit pd.eval("df1 + df2 + df3 + df4") 2.85 ms +- 58.8 us per loop (mean +- std. dev. of 7 runs, 100 loops each)
DataFrame
比较:
In [62]: %timeit (df1 > 0) & (df2 > 0) & (df3 > 0) & (df4 > 0) 5.98 ms +- 37 us per loop (mean +- std. dev. of 7 runs, 100 loops each)
In [63]: %timeit pd.eval("(df1 > 0) & (df2 > 0) & (df3 > 0) & (df4 > 0)") 9.38 ms +- 36.7 us per loop (mean +- std. dev. of 7 runs, 100 loops each)
DataFrame
在未对齐轴上的算术运算。
In [64]: s = pd.Series(np.random.randn(50)) In [65]: %timeit df1 + df2 + df3 + df4 + s 12.6 ms +- 105 us per loop (mean +- std. dev. of 7 runs, 100 loops each)
In [66]: %timeit pd.eval("df1 + df2 + df3 + df4 + s") 3.69 ms +- 62 us per loop (mean +- std. dev. of 7 runs, 100 loops each)
注意
诸如
1 and 2 # would parse to 1 & 2, but should evaluate to 2 3 or 4 # would parse to 3 | 4, but should evaluate to 3 ~1 # this is okay, but slower when using eval
应在 Python 中执行。如果尝试对不是bool
或np.bool_
类型的标量操作数执行任何布尔/位操作,将引发异常。
这里是一个显示pandas.eval()
运行时间与涉及计算的数据框大小的函数关系的图。两条线代表两种不同的引擎。
只有当您的DataFrame
的行数超过约 10 万行时,使用numexpr
引擎与pandas.eval()
才能看到性能优势。
此图是使用包含使用numpy.random.randn()
生成的浮点值的 3 列的DataFrame
创建的。
使用numexpr
的表达式评估限制
由于NaT
,会导致对象 dtype 或涉及日期时间操作的表达式必须在 Python 空间中评估,但表达式的一部分仍然可以使用numexpr
评估。例如:
In [67]: df = pd.DataFrame( ....: {"strings": np.repeat(list("cba"), 3), "nums": np.repeat(range(3), 3)} ....: ) ....: In [68]: df Out[68]: strings nums 0 c 0 1 c 0 2 c 0 3 b 1 4 b 1 5 b 1 6 a 2 7 a 2 8 a 2 In [69]: df.query("strings == 'a' and nums == 1") Out[69]: Empty DataFrame Columns: [strings, nums] Index: []
比较的数值部分(nums == 1
)将由numexpr
评估,比较的对象部分("strings == 'a'
)将由 Python 评估。
支持的语法
这些操作由pandas.eval()
支持:
- 除左移(
<<
)和右移(>>
)运算符外的算术运算,例如,df + 2 * pi / s ** 4 % 42 - the_golden_ratio
- 比较操作,包括链式比较,例如,
2 < df < df2
- 布尔运算,例如,
df < df2 and df3 < df4 or not df_bool
list
和tuple
字面值,例如,[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
和log10
。
以下 Python 语法不允许:
- 表达式
- 除数学函数外的函数调用。
is
/is not
操作if
表达式lambda
表达式list
/set
/dict
推导式- 字面
dict
和set
表达式yield
表达式- 生成器表达式
- 仅由标量值组成的布尔表达式
- 语句
- 不允许简单或复合语句。这包括
for
、while
和if
。
局部变量
你必须通过在名称前加上@
字符来显式引用任何你想在表达式中使用的本地变量。这个机制对于DataFrame.query()
和DataFrame.eval()
都是相同的。例如,
In [18]: df = pd.DataFrame(np.random.randn(5, 2), columns=list("ab")) In [19]: newcol = np.random.randn(len(df)) In [20]: df.eval("b + @newcol") Out[20]: 0 -0.206122 1 -1.029587 2 0.519726 3 -2.052589 4 1.453210 dtype: float64 In [21]: df.query("b < @newcol") Out[21]: a b 1 0.160268 -0.848896 3 0.333758 -1.180355 4 0.572182 0.439895
如果你不在本地变量前加上@
前缀,pandas 会引发一个异常,告诉你该变量未定义。
在使用DataFrame.eval()
和DataFrame.query()
时,这允许你在表达式中同时拥有一个本地变量和一个DataFrame
列具有相同的名称。
In [22]: a = np.random.randn() In [23]: df.query("@a < a") Out[23]: a b 0 0.473349 0.891236 1 0.160268 -0.848896 2 0.803311 1.662031 3 0.333758 -1.180355 4 0.572182 0.439895 In [24]: df.loc[a < df["a"]] # same as the previous expression Out[24]: a b 0 0.473349 0.891236 1 0.160268 -0.848896 2 0.803311 1.662031 3 0.333758 -1.180355 4 0.572182 0.439895
警告
如果你不能在上下文中使用@
前缀因为它没有被定义,pandas.eval()
会引发一个异常。
In [25]: a, b = 1, 2 In [26]: pd.eval("@a + b") Traceback (most recent call last): File ~/micromamba/envs/test/lib/python3.10/site-packages/IPython/core/interactiveshell.py:3577 in run_code exec(code_obj, self.user_global_ns, self.user_ns) Cell In[26], line 1 pd.eval("@a + b") File ~/work/pandas/pandas/pandas/core/computation/eval.py:325 in eval _check_for_locals(expr, level, parser) File ~/work/pandas/pandas/pandas/core/computation/eval.py:167 in _check_for_locals raise SyntaxError(msg) File <string> SyntaxError: The '@' prefix is not allowed in top-level eval calls. please refer to your variables by name without the '@' prefix.
在这种情况下,你应该像在标准 Python 中那样简单地引用变量。
In [27]: pd.eval("a + b") Out[27]: 3
pandas.eval()
解析器
有两种不同的表达式语法解析器。
默认的'pandas'
解析器允许更直观地表达类似查询的操作(比较、连接和分离)。特别是,&
和|
运算符的优先级被设置为与相应的布尔运算and
和or
相同。
例如,上面的连接词可以不用括号写。或者,你可以使用'python'
解析器来强制执行严格的 Python 语义。
In [28]: nrows, ncols = 20000, 100 In [29]: df1, df2, df3, df4 = [pd.DataFrame(np.random.randn(nrows, ncols)) for _ in range(4)] In [30]: expr = "(df1 > 0) & (df2 > 0) & (df3 > 0) & (df4 > 0)" In [31]: x = pd.eval(expr, parser="python") In [32]: expr_no_parens = "df1 > 0 & df2 > 0 & df3 > 0 & df4 > 0" In [33]: y = pd.eval(expr_no_parens, parser="pandas") In [34]: np.all(x == y) Out[34]: True
同样的表达式也可以用单词and
来“与”起来:
In [35]: expr = "(df1 > 0) & (df2 > 0) & (df3 > 0) & (df4 > 0)" In [36]: x = pd.eval(expr, parser="python") In [37]: expr_with_ands = "df1 > 0 and df2 > 0 and df3 > 0 and df4 > 0" In [38]: y = pd.eval(expr_with_ands, parser="pandas") In [39]: np.all(x == y) Out[39]: True
这里的and
和or
运算符具有与 Python 中相同的优先级。
pandas.eval()
引擎
有两种不同的表达式引擎。
'numexpr'
引擎是更高性能的引擎,可以相对于大型DataFrame
的标准 Python 语法带来性能改进。这个引擎需要安装可选依赖numexpr
。
'python'
引擎通常不有用,除了用于测试其他评估引擎。使用engine='python'
和eval()
不会带来任何性能优势,反而可能会降低性能。
In [40]: %timeit df1 + df2 + df3 + df4 7.42 ms +- 81.8 us per loop (mean +- std. dev. of 7 runs, 100 loops each)
In [41]: %timeit pd.eval("df1 + df2 + df3 + df4", engine="python") 8.11 ms +- 161 us per loop (mean +- std. dev. of 7 runs, 100 loops each)
DataFrame.eval()
方法
除了顶层pandas.eval()
函数外,您还可以在DataFrame
的“上下文”中评估表达式。
In [42]: df = pd.DataFrame(np.random.randn(5, 2), columns=["a", "b"]) In [43]: df.eval("a + b") Out[43]: 0 -0.161099 1 0.805452 2 0.747447 3 1.189042 4 -2.057490 dtype: float64
任何有效的pandas.eval()
表达式也是有效的DataFrame.eval()
表达式,额外的好处是您不必在感兴趣的列名之前加上DataFrame
的名称。
此外,您可以在表达式中执行列的赋值。这允许公式评估。赋值目标可以是新列名或现有列名,并且必须是有效的 Python 标识符。
In [44]: df = pd.DataFrame(dict(a=range(5), b=range(5, 10))) In [45]: df = df.eval("c = a + b") In [46]: df = df.eval("d = a + b + c") In [47]: df = df.eval("a = 1") In [48]: df Out[48]: a b c d 0 1 5 5 10 1 1 6 7 14 2 1 7 9 18 3 1 8 11 22 4 1 9 13 26
返回具有新列或修改列的DataFrame
的副本,原始框架保持不变。
In [49]: df Out[49]: a b c d 0 1 5 5 10 1 1 6 7 14 2 1 7 9 18 3 1 8 11 22 4 1 9 13 26 In [50]: df.eval("e = a - c") Out[50]: a b c d e 0 1 5 5 10 -4 1 1 6 7 14 -6 2 1 7 9 18 -8 3 1 8 11 22 -10 4 1 9 13 26 -12 In [51]: df Out[51]: a b c d 0 1 5 5 10 1 1 6 7 14 2 1 7 9 18 3 1 8 11 22 4 1 9 13 26
可以通过使用多行字符串执行多列赋值。
In [52]: df.eval( ....: """ ....: c = a + b ....: d = a + b + c ....: a = 1""", ....: ) ....: Out[52]: a b c d 0 1 5 6 12 1 1 6 7 14 2 1 7 8 16 3 1 8 9 18 4 1 9 10 20
标准 Python 中的等效操作是
In [53]: df = pd.DataFrame(dict(a=range(5), b=range(5, 10))) In [54]: df["c"] = df["a"] + df["b"] In [55]: df["d"] = df["a"] + df["b"] + df["c"] In [56]: df["a"] = 1 In [57]: df Out[57]: a b c d 0 1 5 5 10 1 1 6 7 14 2 1 7 9 18 3 1 8 11 22 4 1 9 13 26
eval()
性能比较
pandas.eval()
适用于包含大型数组的表达式。
In [58]: nrows, ncols = 20000, 100 In [59]: df1, df2, df3, df4 = [pd.DataFrame(np.random.randn(nrows, ncols)) for _ in range(4)]
DataFrame
算术:
In [60]: %timeit df1 + df2 + df3 + df4 7.34 ms +- 117 us per loop (mean +- std. dev. of 7 runs, 100 loops each)
In [61]: %timeit pd.eval("df1 + df2 + df3 + df4") 2.85 ms +- 58.8 us per loop (mean +- std. dev. of 7 runs, 100 loops each)
DataFrame
比较:
In [62]: %timeit (df1 > 0) & (df2 > 0) & (df3 > 0) & (df4 > 0) 5.98 ms +- 37 us per loop (mean +- std. dev. of 7 runs, 100 loops each)
In [63]: %timeit pd.eval("(df1 > 0) & (df2 > 0) & (df3 > 0) & (df4 > 0)") 9.38 ms +- 36.7 us per loop (mean +- std. dev. of 7 runs, 100 loops each)
具有不对齐轴的DataFrame
算术。
In [64]: s = pd.Series(np.random.randn(50)) In [65]: %timeit df1 + df2 + df3 + df4 + s 12.6 ms +- 105 us per loop (mean +- std. dev. of 7 runs, 100 loops each)
In [66]: %timeit pd.eval("df1 + df2 + df3 + df4 + s") 3.69 ms +- 62 us per loop (mean +- std. dev. of 7 runs, 100 loops each)
注意
在 Python 中应执行诸如
1 and 2 # would parse to 1 & 2, but should evaluate to 2 3 or 4 # would parse to 3 | 4, but should evaluate to 3 ~1 # this is okay, but slower when using eval
如果尝试对不是bool
或np.bool_
类型的标量操作执行任何布尔/位操作,将引发异常。
这里是一个显示pandas.eval()
运行时间与涉及计算的框架大小函数关系的图表。两条线代表两种不同的引擎。
只有当您的DataFrame
的行数超过约 100,000 行时,使用numexpr
引擎与pandas.eval()
才能看到性能优势。
此图是使用包含使用numpy.random.randn()
生成的浮点值的 3 列的DataFrame
创建的。
使用numexpr
的表达式评估限制
由于NaT
会导致结果为对象数据类型或涉及日期时间操作,因此表达式必须在 Python 空间中进行评估,但表达式的一部分仍然可以使用numexpr
进行评估。例如:
In [67]: df = pd.DataFrame( ....: {"strings": np.repeat(list("cba"), 3), "nums": np.repeat(range(3), 3)} ....: ) ....: In [68]: df Out[68]: strings nums 0 c 0 1 c 0 2 c 0 3 b 1 4 b 1 5 b 1 6 a 2 7 a 2 8 a 2 In [69]: df.query("strings == 'a' and nums == 1") Out[69]: Empty DataFrame Columns: [strings, nums] Index: []
比较的数值部分(nums == 1
)将由numexpr
评估,而比较的对象部分("strings == 'a'
)将由 Python 评估。