pandasの全行に対してなんか処理したい

pandasはまとめて処理が基本

まずpandasは全行まとめての計算が可能なので、できるだけそうするべき。

例えば、”身長","体重"のカラムを持ったデータフレームに”BMI”のカラムを付け加えたいならこう書くだけ

df["BMI"] = df["体重"] / (df["身長"]*df["身長"])

これですべての行でのBMIを計算して列を追加してくれる。間違ってもループさせて計算なんてしてはいけない。

どうしても1行ずつ処理したいとき

しかしのっぴきならない事情で全行ループする必要があるのだ、と。そんなこともあるかもしれない。

それなら仕方ないが、やり方は気をつけたい。

for row in df.iterrows():
    # なんか処理

こんなコードをときどき見るのだけど、でかいデータで気安くiterrows()を使ってはいけない。

iterrows()はDataFrameの各行を(Index, Series)という形のタプルで返してくれるので行データをあれこれするには使いやすいのだが、いかんせん遅い。巨大なデータでこんなことをしようもんなら壮大な待ちぼうけを食らうことになる。

データ参照ならitertuples()

iterrows()のようにデータ参照目的でループするならitertuples()を使ったほうがいい。

for row in pd.itertuples():
    # なんか処理

itertulples()は行データを(index, 1列目データ, 2列目データ, …)という形のタプルで返してくれる。

タプルと言っても、Named Tupleのため、列名でのアクセスも可能。

ここでのrowに入るのは行のコピーなので、rowを変更しても元データフレームを変更することはできないのはiterrows()同様。

iterrowsに比べて10倍以上高速になるので、よっぽどの理由がなければこっちを使ったほうがいい。というか逆にiterrows()を使うケースが思い浮かばない。

ちなみにDataFrameじゃなくてpythonのListのループのが速いんじゃないかと思って、df.values.tolist()してみても、itertuplesと同じくらいの速度だったので、きっとやってることは似たようなもんなんだろう。

全列不要なら必要な列だけ取り出してループ

全行ループしたいと言っても、すべての列データが必要なことは少ない。ならば必要な列だけ抜き出してループすればいい。

    for row in zip(df.index, df["A"], df["B"]):
        # なんか処理

インデックスとA列B列のみをループしている。ここではrowは単なるタプルになり、row[2]でB列データにアクセスすることになる。上記に比べて少しわかりにくくはなるけれど、こっちのがitertuplesよりも高速。

必要なデータが少ないなら展開して受ければいい。

    for index, col_a, col_b in zip(df.index, df["A"], df["B"]):
        # なんか処理

基本的にデータ参照するだけならこれでよさそう。

ちなみにすべての列取り出してもitertuplesよりも少しだけ速かった・・・なぜ?

データフレームを編集するならapply()

データを参照するのではなく、それぞれの行に同じ処理をしてデータフレーム自体を書き換えたいという場合はapplyメソッドを使うのが一般的だと思う。

def func(row):
    # なんか行に対する処理

# axis指定しないと列に対する処理になる。axis=1で各行に対する処理
df.apply(func, axis=1)

これで全行に同じ関数を効かせることができる。コードとしてもforループと比べわかりやすい。

ただ残念なことに思ったほどスピードは出ないようで、絶対にループよりこっちのがいいとも言い切れない。データ参照だけのときは単純ループのほうがいいかもしれない。

(てっきりこっちのが速いと思ってたのだけど、試してみたらitertupleのほうが速かった・・・処理内容で変わるかもしれない)