跳到内容

惰性 API

Polars 支持两种操作模式:惰性(lazy)和即时(eager)。到目前为止的示例都使用了即时API,其中查询会立即执行。而在惰性API中,查询只有在被*收集*后才会被评估。将执行推迟到最后一刻可以带来显著的性能优势,这也是为什么在大多数情况下首选惰性API的原因。让我们通过一个例子来演示这一点

read_csv

df = pl.read_csv("docs/assets/data/iris.csv")
df_small = df.filter(pl.col("sepal_length") > 5)
df_agg = df_small.group_by("species").agg(pl.col("sepal_width").mean())
print(df_agg)

CsvReader · 在功能 `csv` 上可用

let df = CsvReadOptions::default()
    .try_into_reader_with_file_path(Some("docs/assets/data/iris.csv".into()))
    .unwrap()
    .finish()
    .unwrap();
let mask = df.column("sepal_length")?.f64()?.gt(5.0);
let df_small = df.filter(&mask)?;
#[allow(deprecated)]
let df_agg = df_small
    .group_by(["species"])?
    .select(["sepal_width"])
    .mean()?;
println!("{df_agg}");

在此示例中,我们使用即时API进行操作,以实现以下目的:

  1. 读取 iris 数据集
  2. 根据萼片长度筛选数据集。
  3. 计算每个物种的萼片宽度平均值。

每个步骤都立即执行并返回中间结果。这可能会非常浪费,因为我们可能会执行未使用的操作或加载额外的数据。如果我们转而使用惰性API,并等到所有步骤都定义后再执行,那么查询规划器就可以执行各种优化。在这种情况下:

  • 谓词下推:在读取数据集时尽可能早地应用筛选器,从而只读取萼片长度大于5的行。
  • 投影下推:在读取数据集时只选择所需的列,从而无需加载额外的列(例如,花瓣长度和花瓣宽度)。

scan_csv

q = (
    pl.scan_csv("docs/assets/data/iris.csv")
    .filter(pl.col("sepal_length") > 5)
    .group_by("species")
    .agg(pl.col("sepal_width").mean())
)

df = q.collect()

LazyCsvReader · 在功能 `csv` 上可用

let q = LazyCsvReader::new("docs/assets/data/iris.csv")
    .with_has_header(true)
    .finish()?
    .filter(col("sepal_length").gt(lit(5)))
    .group_by(vec![col("species")])
    .agg([col("sepal_width").mean()]);
let df = q.collect()?;
println!("{df}");

这些优化将显著降低内存和CPU的负载,从而允许您在内存中处理更大的数据集,并更快地处理它们。一旦查询定义完成,您就可以调用 `collect` 来通知 Polars 您希望执行它。您可以在其专用章节中了解更多关于惰性API的信息

即时API

在许多情况下,即时API实际上在底层调用了惰性API,并立即收集结果。这样做的好处是,查询规划器所做的优化仍然可以在查询本身内部进行。

何时使用哪种

通常,应优先使用惰性API,除非您对中间结果感兴趣,或者正在进行探索性工作且尚未确定查询的最终形态。

预览查询计划

使用惰性API时,您可以使用 `explain` 函数要求 Polars 创建一份查询计划描述,该计划将在您收集结果时执行。如果您想查看 Polars 对您的查询执行了哪些类型的优化,这会很有用。我们可以要求 Polars 解释我们上面定义的查询 `q`

explain

print(q.explain())

explain

let q = LazyCsvReader::new("docs/assets/data/iris.csv")
    .with_has_header(true)
    .finish()?
    .filter(col("sepal_length").gt(lit(5)))
    .group_by(vec![col("species")])
    .agg([col("sepal_width").mean()]);
println!("{}", q.explain(true)?);

AGGREGATE[maintain_order: false]
  [col("sepal_width").mean()] BY [col("species")]
  FROM
  Csv SCAN [docs/assets/data/iris.csv] [id: 140375700657408]
  PROJECT 3/5 COLUMNS
  SELECTION: [(col("sepal_length")) > (5.0)]

通过解释,我们可以立即看到 Polars 确实应用了谓词下推,因为它只读取萼片长度大于5的行;并且它也应用了投影下推,因为它只读取查询所需的列。

`explain` 函数也可以用来查看表达式展开在给定模式上下文中的展开方式。请考虑表达式展开部分中的示例表达式

(pl.col(pl.Float64) * 1.1).name.suffix("*1.1")

我们可以使用 `explain` 来查看此表达式将如何针对任意模式进行评估

explain

schema = pl.Schema(
    {
        "int_1": pl.Int16,
        "int_2": pl.Int32,
        "float_1": pl.Float64,
        "float_2": pl.Float64,
        "float_3": pl.Float64,
    }
)

print(
    pl.LazyFrame(schema=schema)
    .select((pl.col(pl.Float64) * 1.1).name.suffix("*1.1"))
    .explain()
)
SELECT [[(col("float_1")) * (1.1)].alias("float_1*1.1"), [(col("float_2")) * (1.1)].alias("float_2*1.1"), [(col("float_3")) * (1.1)].alias("float_3*1.1")]
  DF ["int_1", "int_2", "float_1", "float_2", ...]; PROJECT["float_1", "float_2", "float_3"] 3/5 COLUMNS