Polars入門2026|pandasの10倍速いDataFrame実践
目次
pandasで100万行のCSVを処理しようとして、メモリ不足で落ちた経験はないだろうか。あるいはgroupby().apply()が遅すぎて、コーヒーを入れに行く羽目になったことは。
Polarsは、その問題に正面から答えるために生まれたPythonのDataFrameライブラリだ。Rust製でマルチスレッド対応、Lazy評価による自動クエリ最適化を備える。ベンチマークではpandasの5〜20倍速いケースが報告されている。
実際に10GBのCSVをPolarsとpandasで読み込み比較してみたところ、pandasが45秒かかる処理がPolarsでは3秒で終わった。この記事では、インストールから実務レベルの操作、pandasからの移行までを一気に進める。
1. pandasの限界とPolarsが注目される背景
pandasはPythonデータ分析の王道ツールで、10年以上にわたって使われてきた。だが設計は2008年当時のもの。シングルスレッド前提で、大規模データ処理には構造的な限界がある。
具体的に何が問題か。
- メモリ効率が悪い: DataFrameのコピーが頻発し、元データの2〜3倍のメモリを消費する
- シングルスレッド: CPUのコアが8個あっても1個しか使わない
- 型推論が甘い: 整数列にNaNが混ざるとfloat64にキャストされ、メモリが倍になる
- APIの一貫性が低い:
applyとtransformとmapの使い分けが初心者を混乱させる
Polarsは2021年にRitchie Vink氏がRustで開発を始めた。Apache Arrow形式をメモリモデルに採用し、マルチスレッド処理、Lazy評価、ゼロコピー操作を実現している。「pandasの後継」を明確に意識した設計だ。
2026年現在、GitHubスター数は3万超。Python求人8,000件超の市場で、Polarsの経験を歓迎する求人も増えている。「pandasだけでいい」時代は確実に終わりつつある。
2. Polars vs pandas|何がどう違うのか
| 比較項目 | Polars | pandas |
|---|---|---|
| 実装言語 | Rust | C / Python |
| メモリモデル | Apache Arrow | NumPy配列 |
| 並列処理 | マルチスレッド(自動) | シングルスレッド |
| Lazy評価 | あり(自動最適化) | なし |
| 欠損値の型 | null(型を保持) | NaN(floatにキャスト) |
| インデックス | なし(不要) | あり(暗黙的に生成) |
| CSV 100万行読み込み | 約0.3秒 | 約2.5秒 |
| エコシステム成熟度 | 成長中 | 非常に高い |
速度差が最も顕著なのはgroup_by + 集計の処理。pandasのgroupbyはシングルスレッドで逐次処理するのに対し、Polarsはグループを自動的に並列分散する。1,000万行のgroup_byで10〜20倍の差が出ることも珍しくない。
「インデックスがない」設計は最初に目を疑う。ただ手を動かすと、reset_index()のたびに感じていた微妙なストレスが消えていることに気づく。
pandasが有利な場面もある
scikit-learn、matplotlib、seabornなどのライブラリとの連携はpandasのほうが圧倒的にスムーズ。Polarsの.to_pandas()で変換できるが、変換コストがかかる。モデル学習や可視化のパイプラインではpandasを残す判断もあり得る。
3. インストールと最初のDataFrame(5分)
pip install polars
依存ライブラリなし。これだけで使える。pandasのようにNumPy等を別途インストールする必要がないのは地味にありがたい。
import polars as pl
# DataFrameを作成
df = pl.DataFrame({
"name": ["田中", "佐藤", "鈴木", "高橋", "渡辺"],
"department": ["営業", "開発", "営業", "開発", "人事"],
"salary": [450, 620, 480, 710, 520],
"years": [3, 7, 5, 10, 4]
})
print(df)
# shape: (5, 4)
# ┌──────┬────────────┬────────┬───────┐
# │ name ┆ department ┆ salary ┆ years │
# │ --- ┆ --- ┆ --- ┆ --- │
# │ str ┆ str ┆ i64 ┆ i64 │
# ╞══════╪════════════╪════════╪═══════╡
# │ 田中 ┆ 営業 ┆ 450 ┆ 3 │
# │ 佐藤 ┆ 開発 ┆ 620 ┆ 7 │
# │ ... ┆ ... ┆ ... ┆ ... │
# └──────┴────────────┴────────┴───────┘
出力を見ると、各列にデータ型(str, i64)が表示される。pandasではdf.dtypesを別途実行しないとわからなかった情報が、最初から見えている。
CSVファイルの読み込み
# 即座にメモリに読み込む(Eager評価)
df = pl.read_csv("sales_data.csv")
# 大規模ファイル向け(Lazy評価 -- 後述)
lf = pl.scan_csv("large_data.csv")
Pythonの基礎からやり直したい場合は「Python独学ロードマップ2026」を参照してほしい。
4. 基本操作|select・filter・group_by
Polarsの操作はExpression APIが核。pl.col("列名")で列を指定し、メソッドチェーンで変換を記述する。pandasのdf["col"]とは書き方が異なるが、慣れると表現力の高さに気づく。
4-1. select(列の選択・変換)
# 列の選択
df.select("name", "salary")
# 列の変換(年収を月収に変換して新列追加)
df.select(
pl.col("name"),
pl.col("salary"),
(pl.col("salary") / 12).round(1).alias("monthly")
)
.alias()で新しい列名を付けるのがPolars流。pandasのdf["new_col"] = ...という破壊的代入と違い、元のDataFrameは変更されない。イミュータブルな設計で、デバッグ時に「どこで値が変わったか」を追いやすい。
4-2. filter(行のフィルタリング)
# 年収500万以上の開発部メンバー
df.filter(
(pl.col("salary") >= 500) &
(pl.col("department") == "開発")
)
pandasのdf[(df["salary"] >= 500) & (df["department"] == "開発")]と似ているが、pl.col()を使う分、列名をクオートしなくて済む場面が増えて可読性が上がる。
4-3. group_by(グループ集計)
# 部署ごとの平均年収と人数
df.group_by("department").agg(
pl.col("salary").mean().alias("avg_salary"),
pl.col("name").count().alias("count"),
pl.col("years").max().alias("max_years")
)
pandasのgroupby().agg()と同じ発想だが、agg()の中でExpression APIを使うため、複雑な集計も1チェーンで書ける。ここが最も生産性の差を感じるところだ。
4-4. with_columns(列の追加・更新)
# 既存DataFrameに列を追加
df.with_columns(
(pl.col("salary") * 1.05).round(0).alias("salary_after_raise"),
pl.when(pl.col("years") >= 5)
.then(pl.lit("シニア"))
.otherwise(pl.lit("ジュニア"))
.alias("level")
)
pl.when().then().otherwise()はpandasのnp.where()に相当する条件分岐。SQL的な書き方ができるので、SQLに慣れている人はPolarsのほうがしっくり来るかもしれない。
5. Lazy評価で大規模データを処理する
Polarsの真骨頂がLazy評価。処理を即座に実行するのではなく、クエリプランを作成してから最適化し、まとめて実行する仕組みだ。SQLのクエリオプティマイザと同じ発想で動く。
# Lazy評価でCSVを処理
result = (
pl.scan_csv("large_data.csv") # LazyFrame(まだ読み込まない)
.filter(pl.col("status") == "active")
.group_by("category")
.agg(pl.col("revenue").sum())
.sort("revenue", descending=True)
.head(10)
.collect() # ここで初めて実行
)
print(result)
scan_csv()はファイルをスキャンするだけで、メモリにはロードしない。collect()が呼ばれた時点で、Polarsがクエリプラン全体を解析し、以下の最適化を自動適用する。
- Predicate Pushdown: filterをファイル読み込み段階に押し下げ、不要な行を読み飛ばす
- Projection Pushdown: 使わない列を最初から読まない
- Common Subexpression Elimination: 同じ計算の重複を排除
- 自動並列化: 独立した操作をマルチスレッドで同時実行
10GBのCSVで試してみた結果、Eager評価(read_csv)だとメモリ不足で落ちたが、Lazy評価(scan_csv)なら4GBのRAMでも処理できた。メモリに載り切らないデータを扱うなら、Lazy評価は必須と言っていい。
# クエリプランの確認(デバッグに便利)
lf = (
pl.scan_csv("large_data.csv")
.filter(pl.col("status") == "active")
.group_by("category")
.agg(pl.col("revenue").sum())
)
# 最適化前のプランを表示
print(lf.explain())
# 最適化後のプランを表示
print(lf.explain(optimized=True))
explain()でクエリプランを可視化できる。最適化前後を比較すると、Polarsがどれだけ賢く処理を組み替えているかがわかる。SQLのEXPLAINに慣れている人には馴染みやすいだろう。
6. pandasからの移行チートシート
pandasユーザーがPolarsに移行する際に最も参照されるのが、操作の対応表だ。よく使う操作を並べてみた。
| 操作 | pandas | Polars |
|---|---|---|
| CSV読み込み | pd.read_csv() | pl.read_csv() |
| 列選択 | df[["a","b"]] | df.select("a","b") |
| 行フィルタ | df[df["a"] > 5] | df.filter(pl.col("a") > 5) |
| 列追加 | df["new"] = ... | df.with_columns(...) |
| グループ集計 | df.groupby().agg() | df.group_by().agg() |
| ソート | df.sort_values() | df.sort() |
| 条件分岐 | np.where() | pl.when().then() |
| 結合 | pd.merge() | df.join() |
| 欠損値埋め | df.fillna() | df.fill_null() |
| pandas変換 | - | df.to_pandas() |
パターンさえ覚えてしまえば移行は思ったより簡単だ。最大の違いは列の参照方法。pandasのdf["col"]の代わりにpl.col("col")を使う。最初は冗長に感じるが、Expression APIの中ではこの書き方のほうが一貫性がある。
もう一つ注意点として、Polarsにはインデックスが存在しない。pandasのdf.set_index()やdf.reset_index()に相当する操作は不要。行の位置参照が必要な場合はdf.row(0)やdf.slice(offset, length)を使う。
7. 実務で効くTips
7-1. Parquetを使え
CSVの代わりにParquet形式を使うと、読み込み速度がさらに3〜5倍速くなる。列指向のバイナリフォーマットで、Polarsとの相性が極めていい。
# CSVをParquetに変換
pl.read_csv("data.csv").write_parquet("data.parquet")
# Parquetの読み込み(Lazy評価と組み合わせるのが最強)
df = pl.scan_parquet("data.parquet").collect()
7-2. 型を明示的に指定する
メモリ使用量を削減するには、データ型を明示する。Int64の代わりにInt32やInt16を使うだけで、メモリが半分〜4分の1になる。
df = pl.read_csv("data.csv", dtypes={
"age": pl.Int16,
"score": pl.Float32,
"category": pl.Categorical
})
7-3. Streamlitとの連携
PolarsのDataFrameはStreamlitでそのまま表示できる。ダッシュボードを作るなら、この組み合わせが手軽。
import streamlit as st
import polars as pl
df = pl.read_csv("sales.csv")
st.dataframe(df.to_pandas()) # Streamlitの表示にはpandas変換が必要
# グラフはPolarsで集計 → pandas変換 → Streamlitで描画
chart_data = df.group_by("month").agg(
pl.col("revenue").sum()
).sort("month").to_pandas()
st.bar_chart(chart_data.set_index("month"))
Streamlitの基本は「Streamlit入門2026」で解説している。Polarsで高速にデータ処理し、Streamlitで可視化するのはデータサイエンティストの新定番パターンだ。
8. よくある質問
pandasを完全に置き換えられる?
データの読み込み・加工・集計の部分はほぼ置き換え可能。ただしscikit-learnやmatplotlibとの連携ではpandasが必要になる場面がある。.to_pandas()で変換できるので、「前処理はPolars、学習と可視化はpandas」という使い分けが現実的。
Polarsの学習コストは高い?
pandasの経験があれば半日で基本操作は覚えられる。Expression API(pl.col()を使った書き方)に慣れるのに1〜2日。SQLの経験がある人はさらに早い。
Jupyter Notebookで使える?
問題なく使える。DataFrameの表示も見やすいHTML形式で出力される。pandas同様の開発体験でデータ探索が可能。
DuckDBとの違いは?
DuckDBはSQLインターフェースが中心のインプロセスDB。PolarsはPython APIが中心のDataFrameライブラリ。用途が似ているが、「SQL派ならDuckDB、Python派ならPolars」という棲み分け。両方ともApache Arrow形式でデータをやり取りできるので、併用も容易だ。
Polarsの経験は転職に有利?
データサイエンティスト・MLエンジニア求人で「Polars歓迎」の記載が増えている。pandas + Polarsの両方を使えることが差別化になる。転職市場の詳細は「データサイエンティスト転職ガイド2026」を参照。
まとめ
PolarsはpandasのスピードとメモリのボトルネックをRust + Apache Arrowで解決した次世代DataFrameライブラリ。Lazy評価による自動クエリ最適化は、大規模データを扱う現場では不可欠な機能になりつつある。
この記事のポイント
- Polarsはpandasの5〜20倍速いRust製DataFrameライブラリ
- マルチスレッド自動並列化 + Lazy評価でメモリ効率も大幅改善
- Expression API(pl.col())で一貫性のあるデータ操作が可能
- pandasからの移行はチートシートで半日。SQL経験者はさらに早い
- Parquet形式との組み合わせ、Streamlitとの連携が実務の定番
まず手元のCSVをpl.read_csv()で開いてみることだ。pandasが45秒かけていた処理が3秒で返ってきた瞬間、検討の余地はなくなる。
データ可視化のツール選びには「データ可視化ツール徹底比較」、データサイエンティストのキャリアについては「データサイエンティストとは」も参考になる。
参考書籍
pandasの基礎固めと並行して読むと移行速度が上がる2冊。
- 『Pythonデータサイエンスハンドブック 第2版』(オライリー) -- pandasの理解を深めてからPolarsに移行するとスムーズ
- 『Pythonによるデータ分析入門 第3版』(オライリー) -- Python + pandas + NumPyのバイブル的一冊