Pandasのデータフレームで、1行ごとや1列ごとに処理を実行したい場面ではapply()を使用します。
今回は、apply()の使い方をまとめていこうと思います。
apply()を使うことで、1行ごとに複雑な処理を加えることができる
for文で1行ずつ処理するのと違いがあるんです?
そのあたりも後ほど紹介しよう。
Pandasバージョン
本記事は、Pandas2.2.3の情報を基に執筆しています。
import pandas as pd
print(pd.__version__)
>>
2.2.3
pd.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のユニバーサル関数1を指定すると、各要素に対し関数処理が適用されます。
例では、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を指定し、行方向の処理か列方向の処理かを指定します。
#各列ごとの処理
df.apply(np.sum, axis=0)
>>
A 55
B 330
C 855
#各行ごとの処理
df.apply(np.sum, axis=1)
>>
0 158
1 197
2 242
3 293
4 350
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 450
def 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として関数(func)に渡す True:各行をndarrayとして関数(func)に渡す | |
result_type | axis=1の場合のみ有効。 関数の戻り値の形式を指定する。デフォルトはNone。 | |
args | 関数に渡す引数をタプル形式で指定する。 (処理対象の行/列データ以外) | |
by_row | funcがリストまたは辞書形式の場合にのみ有効。 関数の適用方法を制御する。 | |
engine | ‘python’または‘numba’のいずれかを指定する。 | |
engine_kwargs | engine=‘numba’の場合のみ使用する。 追加の引数が必要な場合、辞書形式で指定する。 |
for文とのパフォーマンス差
for文もapply()も処理の書き方自体はあまり変わらないのに、なぜわざわざapply()を使うのでしょうか。
その秘密は処理速度にあります。
例のような数件のデータであれば差はほとんどありませんが、数万、数十万件を処理する場合では両者に大きな差が出てきます。パフォーマンス差を実際に検証してみます。
前提
以下のようなデータフレームを準備します。ウォルマートの株価データです。
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.0
検証
for文
for文を使う場合の処理は以下です。
for i, row in df.iterrows():
if row["Open"] >= 136.5:
df.loc[i, "Diff"] = row["High"] - row["Low"]
apply()
続いて同じ処理をapply()を用いて実行してみます。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)
関数SetDiff()を作成し、apply()で1行ごとにSetDiff()を呼び出します。axis=1とすることで1行ずつの処理となります。
結果
データフレームを複製し、10万件、100万件データで両者の処理時間を比較してみます。
For文 | apply() | |
---|---|---|
10万件 | 37.5s | 10s |
100万件 | 3152.3s | 324.6s |
100万件の場合だと処理時間が10倍程度異なります。この点からもapply()を使うことのメリットは大きと言えます。
まとめ
pandas.apply()の使い方と、for文処理とのパフォーマンス差を紹介しました。
For文でも同様の処理は実装できますが、処理時間の観点からapply()を使用することをオススメします。
大量データの処理時にはapply()を検討ください。
処理時間がfor文と10倍変わるのなら、なるべくapply()を使ったほうがいいのは明らかですね。
そうだな。大量データを扱うほど効果が出てくるから、事前に処理方法を検討することをおすすめする。では今回はここまでにしよう。
ありがとうございました~~♪
- NumPyの多次元配列( numpy.ndarray )で、全ての要素に対し演算を行い、結果も同一サイズになる関数 ↩︎