Skip to content

懒加载 API

Polars 支持两种操作模式:惰性和急切。到目前为止的示例都使用了急切 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);

在这个例子中,我们使用eager API来:

  1. 读取鸢尾花 数据集
  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的信息

Eager API

在许多情况下,eager API 实际上是在底层调用 lazy 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
    [col("sepal_width").mean()] BY [col("species")] FROM
  simple π 3/3 ["sepal_width", "species", ... 1 other column]
    Csv SCAN [docs/assets/data/iris.csv]
    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")] FROM
  DF ["int_1", "int_2", "float_1", "float_2"]; PROJECT 3/5 COLUMNS