Skip to content

Lazy / eager API

Polars は 2 つの動作モードをサポートしています: lazy と eager です。eager API では、クエリが即座に実行されますが、lazy API では、クエリが「必要」とされるまで評価されません。最後の瞬間まで実行を遅らせることで、大幅なパフォーマンスの向上が期待できるため、ほとんどの場合 Lazy API が好ましいです。例を使って説明します:

read_csv

df = pl.read_csv("docs/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 · Available on feature csv

let df = CsvReader::from_path("docs/data/iris.csv")
    .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. iris dataset を読み込む
  2. データセットをsepal length でフィルタリングする
  3. 種ごとのsepal width の平均を計算する

各ステップが即座に実行され、中間結果が返されます。しかし、使われていないデータを読み込んだり、不要な処理を行ったりするなど、無駄が生じる可能性があります。代わりに lazy API を使い、すべてのステップが定義されてから実行するようにすれば、クエリプランナーが最適化を行うことができます:

  • Predicate pushdown: データセットの読み込み時に可能な限り早くフィルタを適用し、sepal length が 5 より大きい行のみ読み込む
  • Projection pushdown: 必要なカラム(sepal width)のみ読み込み、不要なカラム(petal length & petal width)は読み込まない

scan_csv

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

df = q.collect()

LazyCsvReader · Available on feature csv

let q = LazyCsvReader::new("docs/data/iris.csv")
    .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 を呼び出して実行を指示します。Lazy API の実装については、後の章で詳しく説明します。

Eager API

多くの場合、eager API は内部で lazy API を呼び出し、即座に結果を収集しています。これにより、クエリ内部での最適化も行われます。

使い分け

一般的に、lazy API を使うことをお勧めします。ただし、中間結果に興味がある場合や、探索的な作業を行っていて、クエリの形が確定していない場合は、eager API を使うことも検討してください。