mcpField
P
pandas-performance

pandas-performance

github.com/tondevrelデータ・データベース2472 回インストール39 週間前に更新
リポジトリ

pandas-performance について

pandas-performance は、pandas の速度とメモリ使用量を改善するスキルで、大規模な DataFrame、遅い読み込み、処理のボトルネックに取り組むデータ担当者向けです。dtype の最適化、ベクトル化、Arrow バックエンド、チャンク処理、効率的な入出力を扱います。

主な機能

  1. 01ビット幅の小さい dtype でメモリ削減
  2. 02行単位の処理をベクトル化で置き換え
  3. 03PyArrow バックエンドでデータを読み込み
  4. 04Parquet の列と行を絞って読み込み
  5. 05大容量ファイルをチャンク処理
  6. 06検索、結合、重複排除を最適化

活用シーン

  1. 01大規模 DataFrame のメモリ負荷を軽減
  2. 02大きな CSV の読み込みを高速化
  3. 03数百万行の計算や検索を最適化
  4. 04機械学習向けデータを効率よく準備

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/tondevrel/scientific-agent-skills --skill pandas-performance

インストール後、Claude Code で/pandas-performanceを入力して読み込まれたか確認します。

詳細

pandas - パフォーマンスとメモリ管理

標準的な pandas コードは、メモリを大量に消費し、処理も遅くなりがちです。このサブスキルでは、内部アーキテクチャ(BlockManager と Arrow バックエンド)を理解することで、pandas を 10 倍高速化し、RAM 使用量を 5 分の 1 に抑える手法を紹介します。

使用する場面

  • DataFrame が 1GB を超え、RAM に負荷がかかっている場合。
  • pd.read_csv によるデータの読み込みに時間がかかりすぎる場合。
  • 行単位の処理(apply、iterrows)がボトルネックになっている場合。
  • 数百万行に対して複雑な結合や検索を実行する必要がある場合。
  • 高性能な ML モデル向けにデータを準備する場合。

参考ドキュメント

基本原則

RAM がボトルネックです

pandas は通常、処理中にデータのコピーを作成します。大規模データを扱うには、コピーを最小限に抑え、データ型に最も適したビット幅を使う必要があります。

ベクトル化とループ
  • レベル 1(最良):NumPy/pandas の組み込みベクトル化関数。
  • レベル 2(良好):複雑な計算には df.eval() または df.query()。
  • レベル 3(平均的):np.vectorize または df.apply()(ロジックが複雑な場合のみ)。
  • レベル 4(最低):iterrows() または itertuples()。

メモリ最適化パターン

1. 「ダウンキャスト」ワークフロー

標準の整数列と浮動小数点数列は、デフォルトで 64 ビットを使用します。科学データの大半は 16 ビットまたは 32 ビットで十分です。

import pandas as pd
import numpy as np

def optimize_memory(df):
    start_mem = df.memory_usage().sum() / 1024**2
    
    for col in df.columns:
        col_type = df[col].dtype
        
        if col_type != object:
            c_min = df[col].min()
            c_max = df[col].max()
            if str(col_type)[:3] == 'int':
                if c_min > np.iinfo(np.int8).min and c_max < np.iinfo(np.int8).max:
                    df[col] = df[col].astype(np.int8)
                elif c_min > np.iinfo(np.int16).min and c_max < np.iinfo(np.int16).max:
                    df[col] = df[col].astype(np.int16)
            else:
                if c_min > np.finfo(np.float32).min and c_max < np.finfo(np.float32).max:
                    df[col] = df[col].astype(np.float32)
        else:
            # Convert low-cardinality strings to Categorical
            num_unique = df[col].nunique()
            if num_unique / len(df) < 0.5:
                df[col] = df[col].astype('category')
                
    end_mem = df.memory_usage().sum() / 1024**2
    print(f'Memory reduced by {100 * (start_mem - end_mem) / start_mem:.1f}%')
    return df
2. 最新の PyArrow バックエンド(Pandas 2.0 以降)

文字列操作を大幅に高速化し、読み込みも速くするには、Arrow バックエンドを使用します。

# Load with Arrow engine for 2-3x speedup
df = pd.read_csv("data.csv", engine="pyarrow", dtype_backend="pyarrow")

速度最適化パターン

1. 複雑な「if-else」のベクトル化(.apply の代わり)

各行に対して Python 関数を呼び出す代わりに、次のようにします。

# ❌ SLOW:
# df['status'] = df.apply(lambda x: 'High' if x['val'] > 100 else 'Low', axis=1)

# ✅ FAST:
df['status'] = np.where(df['val'] > 100, 'High', 'Low')

# ✅ FAST (Multiple conditions):
conditions = [
    (df['val'] > 100),
    (df['val'] > 50) & (df['val'] <= 100),
    (df['val'] <= 50)
]
choices = ['High', 'Medium', 'Low']
df['status'] = np.select(conditions, choices, default='Unknown')
2. 高速な検索

辞書や別のテーブルから値を数百万回にわたって対応付ける必要がある場合:

# ❌ SLOW: df.merge() or df['id'].map(large_dict)
# ✅ FAST: Use a Series as a lookup table with index
lookup_table = pd.Series(data=values, index=keys)
result = lookup_table.reindex(df['target_ids']).values

効率的な I/O

1. フィルタリング付き Parquet(述語プッシュダウン)

大規模データの保存に CSV は使わないでください。Parquet を使用します。

# Save as partitioned parquet
df.to_parquet('data_dir', partition_cols=['year', 'month'])

# Load only specific columns and rows (Fast)
df_subset = pd.read_parquet('data_dir', columns=['price', 'id'], 
                            filters=[('year', '==', 2023)])
2. メモリが限られたシステム向けのチャンク処理

ファイルが 50GB で、RAM が 16GB の場合:

# Process in chunks of 100k rows
chunk_size = 100_000
for chunk in pd.read_csv("massive.csv", chunksize=chunk_size):
    # Perform aggregation
    summary = chunk.groupby('id')['value'].sum()
    # Save or update a running total

パフォーマンスに関する重要なルール

✅ 推奨事項
  • インプレース操作は控えめに使う - 一般に信じられていることとは異なり、inplace=True でも内部的にコピーが作られることがよくあります。代わりにデータ型に注目してください。
  • スライスする前にインデックスをソートする - 大きな DataFrame をインデックスでスライスする場合は、次のようにインデックスをソートしてください:df.sort_index(inplace=True)。これにより、O(N) の処理が O(log N) になります。
  • format を指定して pd.to_datetime を使う - 書式(%Y-%m-%d)を指定すると、自動解析よりも大幅に高速になります。
  • .eval() を活用する - (A + B) / (C * D) のような複雑な算術処理では、numexpr を使用する df.eval() のほうが高速でメモリ効率も優れています。
❌ 非推奨事項
  • iterrows() で反復処理しない - 各行を Series オブジェクトに変換するため、非常に遅くなります。
  • object 型は避ける - object 型の列(通常は文字列)は Python オブジェクトへのポインターであり、多くのメモリを消費します。category または string[pyarrow] を使用してください。
  • ループ内で append() を使わない - 呼び出すたびに DataFrame 全体のコピーが作成されます。データをリストに収集してから pd.concat() を使用してください。

アンチパターン(絶対に避ける)

# ❌ BAD: Growing a DataFrame row by row
df = pd.DataFrame()
for data in large_source:
    df = pd.concat([df, pd.DataFrame([data])]) # ❌ Disaster for performance!

# ✅ GOOD: List of dicts to DataFrame
data_list = []
for data in large_source:
    data_list.append(data)
df = pd.DataFrame(data_list)

# ❌ BAD: Manual string formatting
# df['name'].apply(lambda x: f"USER_{x}")

# ✅ GOOD: Vectorized string accessor
df['name'] = "USER_" + df['name'].astype(str)

実践的なワークフロー

1. メモリを大量に消費する列を特定する
# Get detailed memory breakdown (including object overhead)
print(df.memory_usage(deep=True))

# Identify columns with too many unique strings (bad for 'category')
for col in df.select_dtypes(include=['object']):
    print(f"{col}: {df[col].nunique() / len(df):.2%}")
2. 1,000 万行以上を高速に重複排除する
# Using sorting + shift is often faster than drop_duplicates
df = df.sort_values(['id', 'timestamp'])
mask = (df['id'] != df['id'].shift())
df_unique = df[mask]
3. マルチインデックスで結合する
# If you join on multiple columns, setting them as an index 
# and using join() can be 5x faster than merge()
df1.set_index(['key1', 'key2'], inplace=True)
df2.set_index(['key1', 'key2'], inplace=True)
result = df1.join(df2, how='inner')

このサブスキルにより、pandas はプロトタイピング用ツールから、産業規模の科学データを処理できる高性能エンジンへと変わります。

コメント