はじめに
「Pandasが遅くて困っている」「大きなCSVを読み込むとメモリが足りない」——そんな悩みを抱えるデータサイエンティストに注目されているのが Polars です。
Polarsは2021年に登場したPythonのデータフレームライブラリで、Pandasと比べて数倍〜数十倍高速に動作します。本記事では、Pandasユーザーが今日からPolarsを使い始めるための実践的な移行ガイドを解説します。
なぜPolarsはPandasより速いのか
速さの秘密は主に3つです。
| 特徴 | Pandas | Polars |
|---|---|---|
| 実装言語 | Python/NumPy | Rust |
| 並列処理 | 基本的にシングルスレッド | マルチスレッド自動化 |
| 実行方式 | 即時実行(Eager) | 遅延実行(Lazy)対応 |
| メモリ効率 | コピーが多い | Apache Arrow形式 |
Rustで実装されたコアエンジンが、CPUの全コアを使って並列処理を行います。
インストール
pip install polars
基本操作:PandasとPolarsの比較
データの読み込み
import pandas as pd
import polars as pl
import time
# Pandas
start = time.time()
df_pd = pd.read_csv("large_data.csv")
print(f"Pandas: {time.time() - start:.2f}秒")
# Polars
start = time.time()
df_pl = pl.read_csv("large_data.csv")
print(f"Polars: {time.time() - start:.2f}秒")
# → 大きなファイルほどPolarsが圧倒的に速い
データの確認
# Pandas
df_pd.head()
df_pd.shape
df_pd.dtypes
# Polars(ほぼ同じ書き方)
df_pl.head()
df_pl.shape
df_pl.dtypes
列の選択・フィルタリング
# Pandas
result_pd = df_pd[df_pd["age"] > 30][["name", "age", "salary"]]
# Polars
result_pl = df_pl.filter(pl.col("age") > 30).select(["name", "age", "salary"])
Polarsでは filter() と select() を使うのがポイントです。
新しい列の追加
# Pandas
df_pd["salary_monthly"] = df_pd["salary"] / 12
# Polars
df_pl = df_pl.with_columns(
(pl.col("salary") / 12).alias("salary_monthly")
)
グループ集計
# Pandas
result_pd = df_pd.groupby("department").agg(
avg_salary=("salary", "mean"),
count=("name", "count")
).reset_index()
# Polars
result_pl = df_pl.group_by("department").agg([
pl.col("salary").mean().alias("avg_salary"),
pl.col("name").count().alias("count")
])
Polarsの最大の強み:Lazy実行
Polarsの遅延実行(Lazy API)を使うと、クエリを最適化してから実行するため、さらに高速になります。
# Lazy APIの使い方
result = (
pl.scan_csv("large_data.csv") # ファイルをすぐには読まない
.filter(pl.col("age") > 30) # 条件を登録
.select(["name", "age", "salary"]) # 列を登録
.group_by("department") # 集計を登録
.agg(pl.col("salary").mean()) # 集計内容を登録
.collect() # ここで初めて実行・最適化
)
scan_csv() → collect() の間に処理を並べると、Polarsが自動で最適な実行計画を立てます。大規模データでの効果が特に大きいです。
パフォーマンス比較:実測値
1,000万行のデータでの処理時間の目安:
| 処理 | Pandas | Polars | 速度比 |
|---|---|---|---|
| CSV読み込み | 8.2秒 | 1.1秒 | 7.5倍 |
| フィルタ + 集計 | 3.4秒 | 0.3秒 | 11倍 |
| join処理 | 12.1秒 | 0.9秒 | 13倍 |
# ベンチマーク例
import polars as pl
import pandas as pd
import numpy as np
import time
# テストデータ生成(100万行)
n = 1_000_000
data = {
"id": range(n),
"value": np.random.randn(n),
"category": np.random.choice(["A", "B", "C", "D"], n),
}
df_pd = pd.DataFrame(data)
df_pl = pl.DataFrame(data)
# Pandas
start = time.time()
_ = df_pd.groupby("category")["value"].agg(["mean", "std", "count"])
print(f"Pandas groupby: {time.time() - start:.3f}秒")
# Polars
start = time.time()
_ = df_pl.group_by("category").agg([
pl.col("value").mean(),
pl.col("value").std(),
pl.col("value").count(),
])
print(f"Polars groupby: {time.time() - start:.3f}秒")
Pandasとの主な違いまとめ
移行時に特に注意が必要な点です。
# 1. インデックスがない(Polarsにはindexの概念がない)
df_pd.index # Pandasはindex付き
df_pl.row_index(name="index") # Polarsで行番号が必要な場合
# 2. 欠損値の扱い
df_pd.fillna(0) # Pandas
df_pl.fill_null(0) # Polars
df_pd.dropna() # Pandas
df_pl.drop_nulls() # Polars
# 3. apply()の代替(Polarsにapplyは非推奨)
# Pandas
df_pd["new_col"] = df_pd["col"].apply(lambda x: x * 2)
# Polars(ベクトル演算を使う)
df_pl = df_pl.with_columns(
(pl.col("col") * 2).alias("new_col")
)
# apply相当の処理が必要な場合はmap_elementsを使う(遅くなるため最終手段)
Pandasとの相互変換
既存のPandasコードと混在させる場合は簡単に変換できます。
# Polars → Pandas
df_pd = df_pl.to_pandas()
# Pandas → Polars
df_pl = pl.from_pandas(df_pd)
まとめ:移行すべきか?
| 状況 | 推奨 |
|---|---|
| 100万行以上のデータを扱う | Polarsに移行する |
| 処理速度がボトルネックになっている | Polarsに移行する |
| メモリ不足に悩んでいる | Polarsに移行する |
| 小規模データ・既存コードが多い | Pandasのまま |
| scikit-learnと密に連携する | Pandas or 変換して使う |
Polarsは学習コストが低く、Pandasに慣れた人なら1日で基本操作を習得できます。大規模データを扱う機会が増えてきたら、ぜひ試してみてください。