惰性API

Polars支持两种操作模式: lazyeager, 之前的示例都是使用的 eager, 即查询会立即执行。 但在Lazy模式中, 查询只会在使用了collect后执行。将执行推迟到最后一刻可以带来显著的性能优势,这也是为什么在大多数情况下首选惰性执行 (lazy) API 的原因。

  • eager模式:立即执行查询,适合需要看中间数据的情况。

  • lazy模式:惰性查询,只会在最后才进行查询。

示例

数据准备

下载官方提供的数据集 官方数据文件格式和官方文档上已经有点不一样了,所以需要手动处理一下。将iris.data文件改为iris.csv,并且在最开始一行加入表头:sepal_length,sepal_width,height,width,species。主要是几个后续用到的列名称,其他列的名称无所谓。如下所示:

iris.csv
1sepal_length,sepal_width,height,width,species
25.1,3.5,1.4,0.2,Iris-setosa
34.9,3.0,1.4,0.2,Iris-setosa
44.7,3.2,1.3,0.2,Iris-setosa
54.6,3.1,1.5,0.2,Iris-setosa
6······
Python
1df = pl.read_csv("polars\iris\iris.csv")
2df_small = df.filter(pl.col("sepal_length") > 5)
3df_agg = df_small.group_by("species").agg(pl.col("sepal_width").mean())
4print(df_agg)
1shape: (3, 2)
2┌─────────────────┬─────────────┐
3│ species         ┆ sepal_width │
4│ ---             ┆ ---         │
5│ str             ┆ f64         │
6╞═════════════════╪═════════════╡
7│ Iris-versicolor ┆ 2.804255    │
8│ Iris-setosa     ┆ 3.713636    │
9│ Iris-virginica  ┆ 2.983673    │
10└─────────────────┴─────────────┘

在此示例中,使用 Eager API 来执行操作流程如下:

  1. 读取鸢尾花数据集。
  2. 根据萼片长度过滤数据集。
  3. 计算每个物种的萼片宽度的平均值。

每个步骤都会立即执行并返回中间结果。这可能非常消耗资源,因为可能会执行一些工作或加载一些未使用的额外数据。如果改用惰性 API,并等待所有步骤都定义完成后再执行,那么查询规划器就可以进行各种优化。在这种情况下:

  • 谓词下推:在读取数据集时尽早应用过滤器,从而仅读取萼片长度大于 5 的行。
  • 投影下推:读取数据集时仅选择所需的列,从而无需加载额外的列(例如花瓣长度和花瓣宽度)。
Python
1q = (
2    pl.scan_csv("polars\iris\iris.csv")
3    .filter(pl.col("sepal_length") > 5)
4    .group_by("species")
5    .agg(pl.col("sepal_width").mean())
6)
7df = q.collect()
8print(df)
1shape: (3, 2)
2┌─────────────────┬─────────────┐
3│ species         ┆ sepal_width │
4│ ---             ┆ ---         │
5│ str             ┆ f64         │
6╞═════════════════╪═════════════╡
7│ Iris-virginica  ┆ 2.983673    │
8│ Iris-setosa     ┆ 3.713636    │
9│ Iris-versicolor ┆ 2.804255    │
10└─────────────────┴─────────────┘

这些将显著降低内存和 CPU 的负载,从而能够在内存中容纳更大的数据集并更快地处理它们。定义查询后,可以调用collect来通知 Polars 要执行该查询。

预览查询计划

使用lazyapi时,可以使用函数explain要求Polars创建查询计划的描述, 该描述将在收集结果后执行。对于用来了解 Polars 对查询做了哪些类型的优化非常有用。用法如下:

Python
1print(q.explain())

然后输出结果如下:

1AGGREGATE[maintain_order: false]
2  [col("sepal_width").mean()] BY [col("species")]
3  FROM
4  Csv SCAN [polars\iris\iris.csv]
5  PROJECT 3/5 COLUMNS
6  SELECTION: [(col("sepal_length")) > (5.0)]

在结果中可以看见,Polars 确实应用了谓词下推,因为它只读取萼片长度大于 5 的行,并且它确实应用了投影下推,因为它只读取查询所需的列。

使用lazy模式

TIP
  • 以下只是简单示例如何使用, 具体是否使用lazy模式取决于数据集大小和查询
  • 数据集小时, 使用eager模式可能会更快, 因为生成查询计划需要时间

可以使用lazy()方法将DataFrame转换为LazyDataFrame.

最后使用collect()收集所有查询步骤并返回结果

Python
1import polars as pl
2
3df: pl.DataFrame = pl.DataFrame({
4    "name": ["Alice", "Bob", "Charlie", "David"],
5    "age": [25, 35, 30, 40],
6    "city": ["NY", "LA", "NY", "SF"]
7})
8
9lazy_df: pl.LazyFrame = df.lazy()
10
11query = (
12    lazy_df
13    .filter(pl.col("age") > 30)
14    .select(pl.col("name"),pl.col("age"))
15)
16print(query.explain())
17print(query.collect())
1FILTER [(col("age")) > (30)]
2FROM
3  DF ["name", "age", "city"]; PROJECT["name", "age"] 2/3 COLUMNS
4shape: (2, 2)
5┌───────┬─────┐
6│ name  ┆ age │
7│ ---   ┆ --- │
8│ str   ┆ i64 │
9╞═══════╪═════╡
10│ Bob   ┆ 35  │
11│ David ┆ 40  │
12└───────┴─────┘