ITや趣味など気軽に投稿しています。

【Pandas】データフレームの1行/1列ごとに複雑な処理を実行する

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(funcaxis=0raw=Falseresult_type=Noneargs=()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適用する関数
axis0または‘index’で行方向の処理
1または‘columns’で列方向の処理
rawFalse:各行をSeriesとして関数(func)に渡す
True:各行をndarrayとして関数(func)に渡す
result_typeaxis=1の場合のみ有効。
関数の戻り値の形式を指定する。デフォルトはNone。
args関数に渡す引数をタプル形式で指定する。
(処理対象の行/列データ以外)
by_rowfuncがリストまたは辞書形式の場合にのみ有効。
関数の適用方法を制御する。
engine‘python’または‘numba’のいずれかを指定する。
engine_kwargsengine=‘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.5s10s
100万件3152.3s324.6s

100万件の場合だと処理時間が10倍程度異なります。この点からもapply()を使うことのメリットは大きと言えます。

まとめ

pandas.apply()の使い方と、for文処理とのパフォーマンス差を紹介しました。

For文でも同様の処理は実装できますが、処理時間の観点からapply()を使用することをオススメします。

大量データの処理時にはapply()を検討ください。

処理時間がfor文と10倍変わるのなら、なるべくapply()を使ったほうがいいのは明らかですね。

そうだな。大量データを扱うほど効果が出てくるから、事前に処理方法を検討することをおすすめする。では今回はここまでにしよう。

ありがとうございました~~♪

  1. NumPyの多次元配列( numpy.ndarray )で、全ての要素に対し演算を行い、結果も同一サイズになる関数 ↩︎