惰性 API
Polars 支持两种操作模式:惰性(lazy)和即时(eager)。到目前为止的示例都使用了即时API,其中查询会立即执行。而在惰性API中,查询只有在被*收集*后才会被评估。将执行推迟到最后一刻可以带来显著的性能优势,这也是为什么在大多数情况下首选惰性API的原因。让我们通过一个例子来演示这一点
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)
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进行操作,以实现以下目的:
- 读取 iris 数据集。
- 根据萼片长度筛选数据集。
- 计算每个物种的萼片宽度平均值。
每个步骤都立即执行并返回中间结果。这可能会非常浪费,因为我们可能会执行未使用的操作或加载额外的数据。如果我们转而使用惰性API,并等到所有步骤都定义后再执行,那么查询规划器就可以执行各种优化。在这种情况下:
- 谓词下推:在读取数据集时尽可能早地应用筛选器,从而只读取萼片长度大于5的行。
- 投影下推:在读取数据集时只选择所需的列,从而无需加载额外的列(例如,花瓣长度和花瓣宽度)。
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()
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`
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` 来查看此表达式将如何针对任意模式进行评估
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