PandasからPolarsへ移行する際の実装パターンと性能比較

本記事では、データフレーム処理ライブラリのPolarsについて、Pandasユーザー向けの実践的な移行方法を解説します。Polarsの高速性を活かしながら、既存Pandasコードをいかに効率的に移行するかを、具体的なコード例を交えて学べます。

Polarsが注目される理由

Pandasは長年Pythonのデータ分析標準ツールでしたが、大規模データセット処理時のメモリ効率と速度に課題がありました。Polarsは、Rustで実装された次世代DataFrameライブラリで、以下のような特徴があります:

  • 処理速度:Pandasと比べて3〜100倍高速(データサイズと操作内容に依存)
  • メモリ効率:Lazy評価により不要な中間結果を生成しない
  • API設計:メソッドチェーン型で可読性が向上
  • 型安全性:明示的な型指定により予期しない型変換エラーを防止

ただし、エコシステム規模ではPandasが依然優位です。統計分析用途ではscipystatsmodelsとの連携が必要な場合、Pandasのままが現実的な選択肢となります。

インストールと基本的な環境構築

必要なパッケージのインストール

Polarsのインストールは以下のコマンドで実施します。動作環境はPython 3.8以上が必須です:

pip install polars pandas

本記事での動作確認環境:Ubuntu 22.04 / Python 3.11 / Polars 0.19.19 / Pandas 2.0.3

PandasコードをPolarsに移行する5つのステップ

ステップ1:データの読み込み

最も基本的な操作から始めましょう。CSVファイル読み込みの比較例です:

import pandas as pd
import polars as pl

# Pandasの従来の方法
df_pandas = pd.read_csv('data.csv')

# Polarsでの読み込み
df_polars = pl.read_csv('data.csv')

# Polarsでのメモリ効率的な読み込み(Lazy評価)
df_polars_lazy = pl.scan_csv('data.csv')

scan_csvを使うと、ファイルを読み込まずにクエリプランを構築します。collect()呼び出し時に初めて実行されるため、大規模ファイルの場合に有効です。

ステップ2:列の選択と抽出

import polars as pl

# サンプルデータ作成
df = pl.DataFrame({
    'name': ['Alice', 'Bob', 'Charlie'],
    'age': [25, 30, 35],
    'salary': [50000, 60000, 75000]
})

# 特定の列を選択(Pandasと類似)
result = df.select(['name', 'age'])

# メソッドチェーン形式(Polarsの推奨スタイル)
result = df.select([
    pl.col('name'),
    pl.col('age')
])

ステップ3:フィルタリング操作

# Pandasスタイル(参考用)
# result = df_pandas[df_pandas['age'] > 25]

# Polarsでのフィルタリング
result = df.filter(pl.col('age') > 25)

# 複数条件の組み合わせ
result = df.filter(
    (pl.col('age') > 25) & (pl.col('salary') > 55000)
)

Polarsではブール型のマスキングではなく、filter()メソッドを使用します。条件が明示的で、エラーが起きにくくなります。

ステップ4:集計操作

# グループ化と集計
result = df.groupby('department').agg([
    pl.col('salary').mean().alias('avg_salary'),
    pl.col('name').count().alias('employee_count'),
    pl.col('age').max().alias('max_age')
])

# 複数列でのグループ化
result = df.groupby(['department', 'gender']).agg(
    pl.col('salary').sum()
)

alias()で列名を明示的に指定することで、コードの可読性が向上します。

ステップ5:データ型の明示的指定

# スキーマを定義してから読み込み
schema = {
    'id': pl.Int32,
    'name': pl.Utf8,
    'created_at': pl.Date,
    'amount': pl.Float64
}

df = pl.read_csv('transactions.csv', dtypes=schema)

# または手動で型変換
df = df.with_columns([
    pl.col('created_at').str.strptime(pl.Date, '%Y-%m-%d'),
    pl.col('amount').cast(pl.Float64)
])

パフォーマンス改善のためのベストプラクティス

Lazy評価を活用した最適化

Polarsの大きな特徴がLazy評価です。複数の操作を組み合わせた場合、Polarsが内部的に最適なクエリプランを生成します:

import polars as pl

# Lazy評価でクエリを構築
query = (
    pl.scan_csv('large_data.csv')
    .filter(pl.col('age') > 25)
    .select(['name', 'age', 'salary'])
    .groupby('department')
    .agg(pl.col('salary').mean())
    .sort('salary', descending=True)
)

# 最後にcollect()で実行
result = query.collect()

# クエリプランを確認する(デバッグ用)
# print(query.explain())

よくあるハマりポイント:Pandasとの非互換性

Polarsへの移行時に遭遇しやすいエラーと対策をまとめました:

# ❌ エラー例1:ブール型インデックスは非サポート
# result = df[df['age'] > 25]  # PolarsではNG

# ✅ 正しい方法
result = df.filter(pl.col('age') > 25)

# ❌ エラー例2:行へのアクセス方法が異なる
# row = df.iloc[0]  # PolarsではNG

# ✅ 正しい方法
row = df[0]  # 最初の行を取得
rows = df.slice(0, 5)  # 複数行を取得

# ❌ エラー例3:NaN値の扱い
# result = df[df['value'].isna()]  # これはNG

# ✅ 正しい方法
result = df.filter(pl.col('value').is_null())

実践的な移行例:売上分析ダッシュボード

実際のビジネスユースケースで、PandasコードをPolarsに移行する完全な例を示します:

import polars as pl
from datetime import datetime

# データ準備
data = {
    'date': ['2024-01-01', '2024-01-02', '2024-01-03', '2024-01-04'],
    'product': ['A', 'B', 'A', 'B'],
    'sales': [10000, 15000, 12000, 18000],
    'units': [100, 150, 120, 180]
}

df = pl.DataFrame(data).with_columns(
    pl.col('date').str.strptime(pl.Date, '%Y-%m-%d')
)

# 分析クエリ:商品ごとの日別売上集計
analysis = (
    df
    .groupby(['date', 'product'])
    .agg([
        pl.col('sales').sum().alias('daily_sales'),
        pl.col('units').sum().alias('daily_units')
    ])
    .with_columns(
        pl.col('daily_sales').truediv(pl.col('daily_units')).alias('price_per_unit')
    )
    .sort(['date', 'daily_sales'], descending=[False, True])
)

print(analysis)

# 結果のエクスポート
analysis.write_csv('sales_report.csv')
analysis.write_parquet('sales_report.parquet')

PandasとPolarsの使い分けガイド

場面 推奨 理由
大規模データ処理(GB以上) Polars メモリ効率とSpeed が大幅に優位
統計分析(t検定、回帰など) Pandas scipy等との連携がスムーズ
時系列データ処理 Pandas DatetimeIndex が充実
データETL/クリーニング Polars Lazy評価で最適化される
既存プロジェクトの保守 Pandas チーム知見が蓄積されている

公式リソースと参考資料

Polars公式ドキュメント - User Guideでは、さらに詳細な使用方法とAPIリファレンスを参照できます。

よくある質問

A:いいえ。APIが異なるため、完全な互換性はありません。特にブール型インデックシングとインデックス操作が大きく異なります。ただし、基本的なデータ操作(読み込み、フィルタ、集計)はパターンが決まっているため、ステップバイステップで移行は容易です。

A:あります。小規模データではメモリ効率の優位性は目立ちませんが、以下の理由でPolarsを推奨します:①Lazy評価により将来のデータ拡張に対応しやすい、②API設計が一貫性を持つため保守性が向上、③複雑な変換処理で高速化の恩恵を受ける可能性があります。

A:多くのサードパーティライブラリはPolarsをネイティブサポートしていません。これが必須の場合は、処理結果をdf.to_pandas()で一時的にPandasに変換する折衷案があります。

まとめ

  • Polarsの活用場面:大規模データ処理、ETL・クリーニング作業、リアルタイム分析パイプラインに最適
  • 移行のコツ:API設計が異なるため、基本
K
AWS・Python・生成AIを専門とするソフトウェアエンジニア。AI・クラウド・開発ワークフローの実践ガイドを執筆しています。詳しく見る →