pandas tips データフレームのループを倍高速化する


 

目次

Pandasのデータ処理の概要

Pandasのループ処理改善

  1. 一般的なループ(Loop)
  2. iterrows()
  3. apply()
  4. Pandas ベクトル化(Pandas Vectorization)
  5. 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以上担当

理化学研究所にて研究員を拝命中 応用数理学会所属