pandas の iterrows は遅い:なぜループを避けるべきか

pandas で DataFrame を行ごとに処理したいとき、iterrows を使う人がいます。しかし、iterrows は非常に遅いので、できるだけ避けるべきです。

iterrows が遅い理由

iterrows は 1 行ずつ Python のループで処理します。NumPy のベクトル化された演算に比べると、桁違いに遅くなります。

import pandas as pd
import numpy as np
import time

df = pd.DataFrame({
    'a': np.random.rand(100000),
    'b': np.random.rand(100000)
})

# iterrows で計算(遅い)
start = time.time()
result = []
for idx, row in df.iterrows():
    result.append(row['a'] + row['b'])
df['c'] = result
print(f"iterrows: {time.time() - start:.2f}")

# ベクトル化で計算(速い)
start = time.time()
df['c'] = df['a'] + df['b']
print(f"ベクトル化: {time.time() - start:.4f}")

10 万行程度でも、iterrows は数秒かかりますが、ベクトル化なら一瞬で終わります。

なぜループを避けるべきか

pandas や NumPy の強みは、C 言語で実装された高速な配列演算です。Python のループを使うと、その恩恵を受けられません。

iterrows(ループ)

1 行ずつ Python オブジェクトに変換して処理。オーバーヘッドが大きい。

ベクトル化

配列全体を一度に処理。C 言語レベルの高速な演算。

代替手段 1:ベクトル化演算

算術演算や比較は、列に対して直接行えます。

# 加算
df['c'] = df['a'] + df['b']

# 条件分岐
df['flag'] = df['a'] > 0.5

# 複数条件
df['flag'] = (df['a'] > 0.5) & (df['b'] < 0.3)

代替手段 2:apply

行ごとに複雑な処理が必要な場合は apply を使います。iterrows よりは速いですが、ベクトル化には劣ります。

def process_row(row):
    if row['a'] > 0.5:
        return row['a'] * 2
    else:
        return row['b'] * 3

df['result'] = df.apply(process_row, axis=1)

代替手段 3:np.where

条件に応じて値を変える場合は np.where が高速です。

df['result'] = np.where(df['a'] > 0.5, df['a'] * 2, df['b'] * 3)

代替手段 4:np.select

複数の条件がある場合は np.select を使います。

conditions = [
    df['a'] > 0.7,
    df['a'] > 0.3,
]
choices = [
    'high',
    'medium',
]
df['category'] = np.select(conditions, choices, default='low')

iterrows を使ってもよい場面

とはいえ、iterrows が適切な場面もあります。

デバッグ時

少量のデータで処理内容を確認したいとき。

外部 API への送信

1 行ずつ外部サービスに送信する必要があるとき。ただし、バッチ処理が可能なら避けるべき。

パフォーマンスの目安

方法相対速度
ベクトル化1倍(最速)
np.where1〜2倍
apply10〜100倍遅い
iterrows100〜1000倍遅い

数値は目安ですが、データが大きくなるほど差は広がります。

pandas を使うときは「ループを書かずに済む方法はないか」を常に考える習慣をつけましょう。ほとんどの処理はベクトル化か組み込みメソッドで実現できます。