目次
Pandasのデータ処理の概要
Pandasのループ処理改善
- 一般的なループ(Loop)
- iterrows()
- apply()
- Pandas ベクトル化(Pandas Vectorization)
- Numpy ベクトル化(Numpy Vectorization)
Pandasのループ処理改善の比較
Pandasのデータ処理の概要
pandasで大量データを扱う場合、処理速度が遅く困る事があります。公式ドキュメントではパフォーマンス向上のために Cython や Numba を使う方法を記載しています。今回の記事はデータフレームのループを倍高速化する方法を解説と実験したいと思います。
最初はベンチマークとして一般的ループを実行します。
Pandasのループ処理改善
実験の環境:
環境:Colab(Webブラウザからプログラミング言語Python)
データセット:2019年シーズンのサッカープレミアリーグの試合データ
データ処理:特定のチームの試合が引き分けかどうか新しい列作成
評価:データ処理の速さ
data.worldからデータを読み込みます。
サッカーの結果かなら、特定のチームの試合で引き分けかどうか計算します。
import pandas as pd df = pd.read_csv(‘https://query.data.world/s/dz7yh6j26rhswrdlbx23eyebqpisvn’) team = ‘Tottenham’ df |
1. Ilocを用いた一般的なループ(Loop)
ループを使用する場合は、オブジェクト全体を反復処理します。 Pythonはメリットをいかせず非常に遅いです。
def soc_loop(df, TEAM): df[‘Draws’] = 99999 for row in range(0, len(df)): if ((df[‘HomeTeam’].iloc[row] == TEAM) & (df[‘FTR’].iloc[row] == ‘D’)) | \ ((df[‘AwayTeam’].iloc[row] == TEAM) & (df[‘FTR’].iloc[row] == ‘D’)): df[‘Draws’].iloc[row] = ‘Draw’ elif ((df[‘HomeTeam’].iloc[row] == TEAM) & (df[‘FTR’].iloc[row] != ‘D’)) | \ ((df[‘AwayTeam’].iloc[row] == TEAM) & (df[‘FTR’].iloc[row] != ‘D’)): df[‘Draws’].iloc[row] = ‘No_Draw’ else: df[‘Draws’].iloc[row] = ‘No_Game’ |
DataFrameでプレミアリーグからすべての試合を確認する必要があり、if文の分岐で彼らがホームチームかアウェイチームかを確認する必要があります。Loopは86.5 ミリ秒かかりました。
%%timeit soc_loop(df, team) |
10 loops, best of 5: 86.5 ms per loop
2. iterrows()
iterrows()はパンダ内蔵機能で、各行のSeriesを返すため、DataFrameをインデックスのペアとして繰り返し、対象の列をSeriesとして引き渡します。 これにより、標準のループよりも高速になります。
def soc_iter(TEAM,home,away,ftr): #team, row[‘HomeTeam’], row[‘AwayTeam’], row[‘FTR’] if [((home == TEAM) & (ftr == ‘D’)) | ((away == TEAM) & (ftr == ‘D’))]: result = ‘Draw’ elif [((home == TEAM) & (ftr != ‘D’)) | ((away == TEAM) & (ftr != ‘D’))]: result = ‘No_Draw’ else: result = ‘No_Game’ return result |
iterrows()は33.9ミリ秒かかりました。ループに比べると、2.5倍高速化しました。
%%timeit draw_series = [] for index, row in df.iterrows(): draw_series.append(soc_iter(team, row[‘HomeTeam’], row[‘AwayTeam’], row[‘FTR’])) df[‘Draws’] = draw_series |
10 loops, best of 5: 33.9 ms per loop
3. apply()
apply() 自体は高速ではありませんが、DataFrameと組み合わせて使用すると高速です。 ただし、apply式の内容によって異なります。 Cythonベースで実行できる場合、適用ははるかに高速です。
apply()は6.87ミリ秒かかりました。ループに比べると、12倍高速化しました。
%%timeit df[‘Draws’] = df.apply(lambda row: soc_iter(team, row[‘HomeTeam’], row[‘AwayTeam’], row[‘FTR’]), axis=1) |
100 loops, best of 5: 6.87 ms per loop
4. Pandas ベクトル化(Pandas Vectorization)
ベクトル化の利点を利用して、非常に高速なコードを作成します。 重要なのは、前の例のようにPythonレベルのループを回避し、メモリをはるかに効率的に使用する最適化されたCコードを使用しています。
def soc_iter(TEAM,home,away,ftr): df[‘Draws’] = ‘No_Game’ df.loc[((home == TEAM) & (ftr == ‘D’)) | ((away == TEAM) & (ftr == ‘D’)), ‘Draws’] = ‘Draw’ df.loc[((home == TEAM) & (ftr != ‘D’)) | ((away == TEAM) & (ftr != ‘D’)), ‘Draws’] = ‘No_Draw’ |
ベクトル化は2.64ミリ秒かかりました。ループに比べると、32倍高速化しました。
%%timeit df[‘Draws’] = soc_iter(team, df[‘HomeTeam’], df[‘AwayTeam’], df[‘FTR’]) |
5. Numpy ベクトル化(Numpy Vectorization)
前の例では、Pandasシリーズを関数で処理しました。 .values()を追加すると、Numpy配列で処理できます。
ベクトル化は1.08ミリ秒かかりました。ループに比べると、48倍高速化です。
%%timeit df[‘Draws’] = soc_iter(team, df[‘HomeTeam’], df[‘AwayTeam’].values, df[‘FTR’].values) |
100 loops, best of 5: 1.80 ms per loop
Pandasのループ処理改善の比較
チームの試合がドローしたかどうか新しい列作成の処理を比較しました。ループ処理に比べてベクトル化はかなり高速化できました。
担当者:HM
香川県高松市出身 データ分析にて、博士(理学)を取得後、自動車メーカー会社にてデータ分析に関わる。その後コンサルティングファームでデータ分析プロジェクトを歴任後独立 気が付けばデータ分析プロジェクトだけで50以上担当
理化学研究所にて研究員を拝命中 応用数理学会所属