懒加载 API
Polars 支持两种操作模式:惰性和急切。到目前为止的示例都使用了急切 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);
在这个例子中,我们使用eager API来:
- 读取鸢尾花 数据集。
- 根据花萼长度筛选数据集。
- 计算每个物种的萼片宽度的平均值。
每一步都会立即执行并返回中间结果。这可能会非常浪费,因为我们可能会做一些工作或加载未使用的额外数据。如果我们使用惰性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的信息。
Eager API
在许多情况下,eager API 实际上是在底层调用 lazy API 并立即收集结果。这样做的好处是,在查询本身中,查询计划器所做的优化仍然可以发生。
何时使用哪个
一般来说,除非你对中间结果感兴趣,或者正在进行探索性工作且尚未确定查询的具体形式,否则应优先使用惰性API。
预览查询计划
使用惰性API时,您可以使用函数explain来请求Polars创建查询计划的描述,该计划将在您收集结果时执行。如果您想查看Polars对您的查询执行了哪些类型的优化,这可能很有用。我们可以请求Polars解释我们上面定义的查询q:
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来查看这个表达式如何针对任意模式进行评估:
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