Polars は Rust で実装された列指向のデータフレームライブラリで、近年 pandas の代替候補として広く名前を聞くようになりました。大量データで速いだけでなく、API の設計思想も pandas と異なるため、「どんな場面で嬉しいのか」「pandas のコードをどう書き換えるか」を理解しておくと選択肢が広がります。
本記事では、Polars の特徴を概観し、株価データを使って pandas との書き方の違いを並べて確認します。
目次
- インストール
- Polars の 3 つの特徴
- サンプルデータ
- 列の選択 — 式が中心
- 行の絞り込み
- 列の追加 — with_columns
- groupby
- 並べ替え・トップ N
- 遅延評価(LazyFrame)
- NULL の扱い
- pandas との相互変換
- いつ Polars を選ぶか
インストール
pip install polars pyarrowpyarrow は CSV・Parquet 入出力で内部的に使われます。Polars 単体でも動きますが、入れておくと選択肢が増えます。
検証バージョン: Python 3.12.5 / Polars 1.7.1
Polars の 3 つの特徴
Polars が pandas と違う点を、性質で整理します。
| 特徴 | 内容 |
|---|---|
| 列指向(columnar) | 列ごとに連続メモリで保持する。SIMD・キャッシュ効率が良い |
| 遅延評価(lazy) | クエリプランを作ってから一括実行できる。最適化の余地が大きい |
| マルチスレッド | groupby や filter が複数 CPU で並列に走る |
| Rust 実装 | Python のオーバーヘッドが少ない。型・所有権の安全性も継承 |
| 式(Expression)中心 API | pl.col("x") * 2 のように列を式として扱う |
実用上の効果として、数千万行レベルのデータでも groupby が数秒で終わる、ファイル読み込みが pandas の数倍速い、といった違いが体感できます。
サンプルデータ
pandas と並べて書き方を比較するために、共通の DataFrame を用意します。
import polars as pl
df = pl.DataFrame({ "Date": ["2026-04-01", "2026-04-02", "2026-04-03", "2026-04-01", "2026-04-02", "2026-04-03"], "Code": [7203, 7203, 7203, 9984, 9984, 9984], "C": [2900, 2925, 2880, 9800, 9750, 9900], "Vo": [11_000_000, 9_500_000, 12_500_000, 4_200_000, 5_100_000, 4_800_000],})print(df)shape: (6, 4)┌────────────┬──────┬───────┬──────────┐│ Date ┆ Code ┆ C ┆ Vo ││ --- ┆ --- ┆ --- ┆ --- ││ str ┆ i64 ┆ i64 ┆ i64 │╞════════════╪══════╪═══════╪══════════╡│ 2026-04-01 ┆ 7203 ┆ 2900 ┆ 11000000 ││ ... ┆ ... ┆ ... ┆ ... │└────────────┴──────┴───────┴──────────┘表の上部に shape と各列の データ型 が表示されるのが Polars の特徴です。型が常に明示されるため、object のような曖昧な型に悩まされることが減ります。
列の選択 — 式が中心
pandas は df["C"] で列を取り出しますが、Polars では 式(pl.col("C")) を使った組み立てが中心です。
# pandas:# df["C"] * 2
# Polars:df.select(pl.col("C") * 2)「列を 2 倍する処理を、まだ実行せずに記述だけしておく」のが式の発想です。select / with_columns / filter などのメソッドに渡すことで、はじめて値が確定します。
複数列の選択や型変換は次の通りです。
df.select([ pl.col("Date").str.to_date("%Y-%m-%d").alias("Date"), pl.col("Code"), pl.col("C").alias("close_price"), (pl.col("C") * pl.col("Vo")).alias("amount"),])alias は SQL の AS と同じで、式の結果に名前を付けます。
行の絞り込み
pandas の真偽値マスクと似ていますが、ここでも式を使います。
df.filter(pl.col("Code") == 7203)df.filter((pl.col("Code") == 7203) & (pl.col("Vo") > 10_000_000))カッコと & | の使い方は pandas と同じです。
列の追加 — with_columns
pandas の代入(df["x"] = ...)に当たるのが with_columns です。
df2 = df.with_columns([ pl.col("Date").str.to_date("%Y-%m-%d").alias("Date"), (pl.col("C") * pl.col("Vo")).alias("amount"),])print(df2)新しい列を追加した 新しい DataFrame が返ります。Polars では原則として元の DataFrame を破壊的に変更せず、メソッドチェーンで処理を組み上げます。
groupby
Polars では group_by です(groupby は古い名前)。pandas の集計と書き味を比較します。
# pandas:# df.groupby("Code").agg(# mean_close=("C", "mean"),# sum_volume=("Vo", "sum"),# )
# Polars:df.group_by("Code").agg([ pl.col("C").mean().alias("mean_close"), pl.col("Vo").sum().alias("sum_volume"),])集計関数は式に対するメソッドとして定義されており、.mean() / .sum() / .std() / .min() / .max() がそのまま使えます。
並べ替え・トップ N
並べ替えは sort、上位 N 件は head で書けます。
df.sort(["Code", "Date"])df.sort("C", descending=True).head(3)descending=True は pandas の ascending=False と同じ意味です。
遅延評価(LazyFrame)
Polars の真価は 遅延評価 モードです。scan_csv や lazy() で LazyFrame に変換し、すべての処理を collect() するまで実行を遅らせます。
import polars as pl
q = ( pl.scan_csv("prices.csv") .filter(pl.col("Code").is_in([7203, 9984, 8306])) .with_columns(pl.col("Date").str.to_date("%Y-%m-%d")) .group_by("Code") .agg([ pl.col("C").mean().alias("mean_close"), pl.col("C").std(ddof=1).alias("std_close"), ]))print(q.explain()) # クエリプランを表示result = q.collect() # ここで実行print(result)explain() で、Polars がどのような最適化を行うかを確認できます。scan_csv を使うと、必要な列だけを CSV から読み込むなど、クエリ全体を見渡した最適化が走ります。
NULL の扱い
Polars は欠損値を null として明示的に持ちます。pandas の NaN(浮動小数の特殊値) と異なり、整数列でも文字列列でも欠損を表現できます。
df_with_null = pl.DataFrame({"x": [1, None, 3]})df_with_null.select(pl.col("x").is_null())df_with_null.fill_null(0)集計関数(mean / sum など)はデフォルトで null をスキップします。pandas に近い感覚で扱えます。
pandas との相互変換
既存の pandas コード資産を活かしつつ Polars に移行できます。
import pandas as pd
# pandas → Polarspdf = pd.DataFrame({"a": [1, 2, 3]})pol = pl.from_pandas(pdf)
# Polars → pandaspdf2 = pol.to_pandas()NumPy / PyArrow との変換も用意されています。「読み込みと前処理を Polars で速くやり、可視化や ML は既存の pandas / scikit-learn に渡す」といった分業がしやすい設計です。
いつ Polars を選ぶか
選択基準を表にまとめます。
| 状況 | 候補 |
|---|---|
| 数十万行までの分析、可視化中心 | pandas |
| 数百万〜数千万行の集計・前処理 | Polars |
| 既存資産が pandas で多い | pandas(必要に応じて Polars に部分移行) |
| 列指向ファイル(Parquet)を高速に読みたい | Polars |
| 厳密な型管理を重視する | Polars |
実データでの差は#4-12「pandas vs Polars — どちらを使うか / ベンチマーク」 でベンチマークします。
生成AI へのプロンプト例
「pandas の処理を Polars に書き換える」依頼は、対比を明示すると安定します。
次の pandas コードを Polars 1.7 系で書き換えてください。
[pandas コード]df["return"] = df.groupby("Code")["C"].pct_change()result = ( df.dropna(subset=["return"]) .groupby("Code") .agg(mean=("return","mean"), std=("return","std")))
要件:- group_by + agg + alias を使う- 列の追加は with_columns で- DataFrame は遅延評価 (lazy) を使わず、通常の DataFrame で書くAPI 名 / バージョンを明記すると、新旧 API の混在を避けられます。
まとめ
- Polars は列指向・遅延評価・Rust 実装で、大量データでの高速化に強い
- API は 式中心(
pl.col(...))。select/with_columns/filterで組み立てる - groupby は
group_by(...).agg([...])の形 - LazyFrame(
scan_csv/lazy)を使うと、クエリ全体の最適化が走る - pandas / NumPy との相互変換が容易で、段階的な移行が可能