Pandasのデータフレームで、1行ごとや1列ごとに処理を実行したい場面ではapply()メソッドを使用します。
この記事では、apply()の基本的な使い方から、for文との処理速度の比較まで解説します。
Pandasバージョン
本記事は、Pandas 2.2.3の情報を基に執筆しています。
import pandas as pd
print(pd.__version__)
>>
2.2.3pd.DataFrame.apply()
apply()はデータフレームの行や列に対して関数を適用するためのメソッドです。
DataFrame.apply(func, axis=0, raw=False, result_type=None, args=(), by_row='compat', engine='python', engine_kwargs=None, **kwargs)基本的な使い方
以下のデータフレームを例に説明します。
df = pd.DataFrame(
data={"A":[1,2,3,4,5],
"B":[10,20,30,40,50],
"C":[100,200,300,400,500]})
>>
A B C
0 1 36 121
1 4 49 144
2 9 64 169
3 16 81 196
4 25 100 225ユニバーサル関数の適用
NumPyのユニバーサル関数を指定すると、各要素に対して関数が適用されます。以下はnp.sqrtで平方根を求める例です。
import numpy as np
df.apply(np.sqrt)
>>
A B C
0 1.0 6.0 11.0
1 2.0 7.0 12.0
2 3.0 8.0 13.0
3 4.0 9.0 14.0
4 5.0 10.0 15.0補足: この処理は
np.sqrt(df)と同じ結果になります。ユニバーサル関数の場合は直接適用する方が簡潔です。
集約関数の適用
sum(和)やmean(平均)などの集約関数を使用する場合は、axisで処理方向を指定します。
# 各列ごとの処理(axis=0)
df.apply(np.sum, axis=0)
>>
A 55
B 330
C 855# 各行ごとの処理(axis=1)
df.apply(np.sum, axis=1)
>>
0 158
1 197
2 242
3 293
4 350lambda式・ユーザー定義関数の適用
lambda式や独自に定義した関数を適用することもできます。
df.apply(lambda x: x*2, axis=1)
>>
A B C
0 2 72 242
1 8 98 288
2 18 128 338
3 32 162 392
4 50 200 450def multiply(x):
return x*2
df.apply(multiply, axis=1)
>>
A B C
0 2 72 242
1 8 98 288
2 18 128 338
3 32 162 392
4 50 200 450引数一覧
| 引数 | 必須 | 説明 |
|---|---|---|
| func | ○ | 適用する関数 |
| axis | 0または'index'で列方向の処理、1または'columns'で行方向の処理 | |
| raw | False: 各行をSeriesとして関数に渡す。True: 各行をndarrayとして関数に渡す | |
| result_type | axis=1の場合のみ有効。関数の戻り値の形式を指定する。デフォルトはNone | |
| args | 関数に渡す追加の位置引数をタプル形式で指定する | |
| by_row | funcがリストまたは辞書形式の場合に有効。関数の適用方法を制御する | |
| engine | 'python'または'numba'のいずれかを指定する | |
| engine_kwargs | engine='numba'の場合に使用する追加引数を辞書形式で指定する |
for文とのパフォーマンス比較
apply()とfor文は似たような処理を記述できますが、大量のデータに対する処理速度に大きな差があります。
検証データ
以下のようなウォルマートの株価データフレームを使用します。
import pandas as pd
df = pd.read_csv("../data/walmart_stock.csv").set_index("Date").head(5)
>>
Date Close/Last Volume Open High Low
09/22/2020 138.31 11034020 137.07 138.9900 136.57
09/21/2020 137.07 15758500 136.50 137.2400 135.29
09/18/2020 135.29 18236350 136.44 137.5300 134.75
09/17/2020 136.69 12415030 135.43 137.5700 135.02
09/16/2020 136.26 9292649 138.41 138.6825 136.05処理内容は、Open列が136.5以上の行について、新規列Diffを作成しHighとLowの差分を格納するものです。
# 処理後の結果。Diff列が追加される。
>>
Date Close/Last Diff High Low Open Volume
09/22/2020 138.31 2.4200 138.9900 136.57 137.07 11034020.0
09/21/2020 137.07 1.9500 137.2400 135.29 136.50 15758500.0
09/18/2020 135.29 NaN 137.5300 134.75 136.44 18236350.0
09/17/2020 136.69 NaN 137.5700 135.02 135.43 12415030.0
09/16/2020 136.26 2.6325 138.6825 136.05 138.41 9292649.0for文での実装
for i, row in df.iterrows():
if row["Open"] >= 136.5:
df.loc[i, "Diff"] = row["High"] - row["Low"]apply()での実装
def SetDiff(row):
if row["Open"] >= 136.5:
row["Diff"] = row["High"] - row["Low"]
return row
# 1行ごとにSetDiff()を適用
df = df.apply(SetDiff, axis=1)パフォーマンス比較結果
データフレームを複製し、10万件・100万件のデータで処理時間を比較しました。
| for文 | apply() | |
|---|---|---|
| 10万件 | 37.5秒 | 10秒 |
| 100万件 | 3,152.3秒 | 324.6秒 |
100万件の場合、処理時間に約10倍の差が生じます。大量データを扱う場合はapply()の使用を強く推奨します。
補足: さらなる高速化が必要な場合は、ベクトル演算(NumPyの配列操作)やPandasの
eval()/query()の活用を検討してください。
まとめ
今回は、apply()の使い方とfor文とのパフォーマンス比較を紹介しました。
apply()はデータフレームの行・列に対して関数を適用するメソッドaxisパラメータで行方向(axis=1)と列方向(axis=0)を切り替えられる- lambda式やユーザー定義関数を柔軟に適用できる
- for文と比較して、大量データ処理時に約10倍の速度差がある
大量のデータを処理する際は、for文ではなくapply()を活用することで、処理時間を大幅に短縮できます。