Pythonデータ分析 #7 polars体験 — pandasが遅くなったときの次の一手
シリーズ最終回です。ここまで 6 回にわたって、pandas ひとつで読み込みから可視化まで一周してきました。最終回では、視野を一段広げてみます。pandas が苦しくなる瞬間はいつなのか、そのとき取り出せる次の一手である polars がどんなツールなのかを、コード比較を中心に体験します。新しい文法を覚える記事ではなく、「こういう選択肢がある」という地図を手に入れる記事です。
pandasが苦しくなる瞬間 #
pandas は、数十万行まではほとんど悩みなくよく動きます。問題はデータが大きくなったときです。だいたい 3 つの壁にぶつかります。
- 行数: 数百万〜数千万行になると、
groupbyやmerge1 回に数十秒かかり始めます。 - メモリ: pandas はファイル全体をメモリに載せます。2GB の CSV がメモリ上ではその数倍に膨らみ、ノート PC で
MemoryErrorに出会うことになります。 - シングルコア: pandas の演算の大半は、CPU コアを 1 つしか使いません。8 コアのマシンでも 7 つは遊んでいます。
小さいデータではまったく問題にならなかった設計が、データが大きくなった瞬間、一斉にコストとして返ってきます。
polars: Rustで作り直されたDataFrame #
polars は Rust で書かれた DataFrame ライブラリです。Python から import polars でそのまま使え、設計自体が上の 3 つの壁を狙っています。
- Rust ベース: 中核の演算が、コンパイル済みのネイティブコードで動きます。
- マルチコア: 特別な設定なしに、すべてのコアを使って並列に演算します。
- Arrow メモリフォーマット: Apache Arrow という列指向の標準フォーマットを使うため、メモリ効率が良く、他のツールとデータをコピーなしでやり取りできます。
インストールは 1 行です。
uv add polars同じ作業を並べて比較 #
文法がどれだけ似ていて、どこが違うのか。シリーズを通して使ってきた販売データ sales.csv(city、category、price、quantity の列)で、同じ作業を両方にやらせてみます。
まずは読み込みです。
import pandas as pd
df = pd.read_csv("sales.csv")import polars as pl
df = pl.read_csv("sales.csv")ここまではほぼ同じです。polars の read_csv はマルチコアでパースするため、同じファイルでも体感速度が違います。次はフィルタです。
expensive = df[df["price"] > 10000]expensive = df.filter(pl.col("price") > 10000)pandas はブールマスクを角括弧に入れましたが、polars は pl.col("price") > 10000 という 式(expression) を filter メソッドに渡します。この違いが polars 文法の中心です。最後に groupby 集計です。
result = (
df[df["price"] > 10000]
.groupby("city")["price"]
.mean()
.sort_values(ascending=False)
)result = (
df.filter(pl.col("price") > 10000)
.group_by("city")
.agg(pl.col("price").mean())
.sort("price", descending=True)
)読んでみると、やっていることは同じです。価格が 10,000 を超える取引だけを残し、都市ごとの平均価格を求め、降順に並べ替えます。
式APIという考え方 #
上のコードに現れた違いを一文にまとめるとこうなります。pandas は「DataFrame オブジェクトをメソッドで加工し続ける」、polars は「何を計算するかを式として組み立て、まとめて渡す」です。
pl.col("price").mean() は、それ自体では何も計算しません。「price 列の平均」という計算の計画にすぎず、agg() のような場所にはめ込まれたとき、初めて実行されます。式が独立した部品なので、組み合わせは自由です。
result = df.group_by("city").agg(
pl.col("price").mean().alias("avg_price"),
pl.col("price").max().alias("max_price"),
pl.col("quantity").sum().alias("total_qty"),
)pandas で agg({"price": ["mean", "max"], ...}) のように辞書で書いていたものが、polars では式のリストとしてフラットに並びます。そして polars は、こうして受け取った式を内部で並列に実行します。
lazyモード: 計画を立てて一度に実行 #
polars の本当の武器は lazy モード です。ここまでのコードは、1 行ずつ即時に実行される eager 方式でした。lazy モードは違います。scan_csv で始めると演算をすぐには実行せず計画だけを積み上げ、最後に collect() を呼んだ瞬間に、全体の計画を一度に実行します。
result = (
pl.scan_csv("sales.csv")
.filter(pl.col("price") > 10000)
.group_by("city")
.agg(pl.col("price").mean())
.collect()
)買い物にたとえるとこうです。eager 方式は、「牛乳を買ってきて」「卵を買ってきて」と言われるたびに、スーパーへ 1 回ずつ行くやり方です。lazy 方式は、リストを最後まで書き取ってから、動線を考えてスーパーへ 1 回だけ 行くやり方です。
collect() の直前に、polars のクエリオプティマイザが計画を検討して組み直します。上の例なら「どうせ使うのは city と price の 2 列だけだから、残りの列は読まない」「price > 10000 のフィルタをファイル読み込みの段階に前倒しして、必要な行だけ載せる」といった最適化が自動で適用されます。メモリに載りきらなかったファイルが lazy モードでは処理できる、ということが珍しくない理由です。
どちらを使うか #
2 つから 1 つを選ぶ問題というより、状況に合わせたデフォルトを決める問題です。
| 基準 | pandas | polars |
|---|---|---|
| エコシステム・検索情報 | 圧倒的に多い | 急速に成長中 |
| 可視化・ライブラリ連携 | ほとんどが pandas 基準 | 変換を挟めば可能 |
| 速度・マルチコア | シングルコア | 標準で並列 |
| メモリより大きいデータ | 難しい | lazy モードで可能 |
数十万行以下の探索作業、そして他のライブラリと組み合わせる作業なら、pandas が依然として楽な選択です。数百万行以上、あるいは同じパイプラインを繰り返し実行するなら、polars が時間を大きく節約してくれます。そして 2 つは敵同士ではありません。変換 1 行で行き来できるため、重い加工は polars でやり、最後に pandas に変換して可視化する、といった組み合わせが実務でよく使われます。
df_pl = pl.from_pandas(df_pd) # pandas -> polars
df_pd = df_pl.to_pandas() # polars -> pandasより大きなデータへの地図 #
polars でも足りなくなる規模が来たら、その次のキーワードを 2 つだけ覚えておけば大丈夫です。Parquet は CSV を置き換える列指向のファイルフォーマットで、同じデータをはるかに小さく保存し、必要な列だけを読めます。DuckDB はファイルの上で直接 SQL を実行する分析用データベースで、メモリより大きいデータをノート PC 1 台で扱うときの標準のように使われています。どちらも Arrow フォーマットで polars と自然につながるので、このシリーズで学んだ考え方がそのまま活きます。
シリーズを終えて #
全 7 回を 1 行ずつ振り返ります。
- #1 pandas入門: データ分析とは何か、環境をどう準備するかを整理しました。
- #2 読み込みと探索:
read_csvでデータを開き、head、info、describeで第一印象をつかみました。 - #3 選択とフィルタ:
locとブールマスクで、欲しい行と列だけを取り出しました。 - #4 変形と欠損値: 新しい列を作り、空いている値を扱う基準を立てました。
- #5 グループ・集計・結合:
groupbyでまとめて要約し、mergeで表を結合しました。 - #6 可視化: 数値の要約をグラフに変えて、パターンを目で確認しました。
- #7 polars体験: pandas の限界と、その先のツールを見てきました。
ツールの話を長くしましたが、分析の本質はツールではなく問いです。「どの都市の売上が落ちているのか」「この変化は偶然なのか」という問いが先にあり、pandas も polars も、その問いに答えを持ってくる手段にすぎません。このシリーズで身につけた一周、つまり読み込んで → 眺めて → 選び出して → 整えて → 要約して → 描いてみるという流れは、ツールが変わってもそのまま残ります。
次の学習としては 2 つの道をおすすめします。繰り返しのデータ作業をコードとして固めたいなら Python自動化 シリーズが自然につながり、Python という言語そのものの筋力を鍛えたいなら モダンPython中級 が次のステップです。良い問いに出会ったとき、答えを求めるツールはもう手元にあります。