Pandas 2.2 中文官方教程和指南(二十三)(4)

简介: Pandas 2.2 中文官方教程和指南(二十三)

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 中执行。如果尝试对不是boolnp.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
  • listtuple字面值,例如,[1, 2](1, 2)
  • 属性访问,例如,df.a
  • 下标表达式,例如,df[0]
  • 简单的变量评估,例如,pd.eval("df")(这并不是很有用)
  • 数学函数:sincosexplogexpm1log1psqrtsinhcoshtanharcsinarccosarctanarccosharcsinharctanhabsarctan2log10

以下 Python 语法允许:

  • 表达式
  • 除数学函数外的函数调用。

  • is/is not操作

  • if表达式

  • lambda表达式

  • list/set/dict推导式

  • 字面dictset表达式

  • yield表达式

  • 生成器表达式

  • 仅由标量值组成的布尔表达式
  • 语句
  • 不允许简单或复合语句。这包括forwhileif

局部变量

你必须通过在名称前加上@字符来显式引用任何你想在表达式中使用的本地变量。这个机制对于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'解析器允许更直观地表达类似查询的操作(比较、连接和分离)。特别是,&|运算符的优先级被设置为与相应的布尔运算andor相同。

例如,上面的连接词可以不用括号写。或者,你可以使用'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 

这里的andor运算符具有与 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 

如果尝试对不是boolnp.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 评估。

相关文章
|
17天前
|
数据采集 存储 数据可视化
Pandas高级教程:数据清洗、转换与分析
Pandas是Python的数据分析库,提供Series和DataFrame数据结构及数据分析工具,便于数据清洗、转换和分析。本教程涵盖Pandas在数据清洗(如缺失值、重复值和异常值处理)、转换(数据类型转换和重塑)和分析(如描述性统计、分组聚合和可视化)的应用。通过学习Pandas,用户能更高效地处理和理解数据,为数据分析任务打下基础。
41 3
|
1月前
|
索引 Python
Pandas 2.2 中文官方教程和指南(一)(4)
Pandas 2.2 中文官方教程和指南(一)
27 0
|
1月前
|
存储 SQL JSON
Pandas 2.2 中文官方教程和指南(一)(3)
Pandas 2.2 中文官方教程和指南(一)
31 0
|
1月前
|
XML 关系型数据库 PostgreSQL
Pandas 2.2 中文官方教程和指南(一)(2)
Pandas 2.2 中文官方教程和指南(一)
41 0
|
1月前
|
XML 关系型数据库 MySQL
Pandas 2.2 中文官方教程和指南(一)(1)
Pandas 2.2 中文官方教程和指南(一)
34 0
|
1月前
|
C++ 索引 Python
Pandas 2.2 中文官方教程和指南(五)(4)
Pandas 2.2 中文官方教程和指南(五)
25 0
|
1月前
|
索引 Python
Pandas 2.2 中文官方教程和指南(五)(3)
Pandas 2.2 中文官方教程和指南(五)
24 0
|
1月前
|
SQL API 数据格式
Pandas 2.2 中文官方教程和指南(五)(2)
Pandas 2.2 中文官方教程和指南(五)
23 0
|
1月前
|
SQL API 数据格式
Pandas 2.2 中文官方教程和指南(五)(1)
Pandas 2.2 中文官方教程和指南(五)
32 0
|
1月前
|
索引 Python
Pandas 2.2 中文官方教程和指南(四)(4)
Pandas 2.2 中文官方教程和指南(四)
21 0