pandasのappendが1.4でDeprecatedされた件

pandasのappendが1.4でDeprecatedされた件

データエンジニアの @kazasiki です。

今回はデータ分析やAIなどをやってる人はお世話になってるだろうpandasについての細かい話をします。

pandasは2022/01/04にバージョンが1.4.0になりました。それに伴って色々変わったんですが、この間pandas使って実装してたら以下のwarningが出てきました。

FutureWarning: The frame.append method is deprecated and will be removed from pandas in a future version. Use pandas.concat instead.

リリースノート的には以下の部分ですね。

pandas.pydata.org

メッセージやドキュメントだけ見れば、appendをconcatに置き換えればいい、という話になりますが、ちょっと違和感を感じてIssueを追いかけてみました。

appendがdeprecatedされた経緯

ドキュメントから参照されている以下のIssueを見れば、appendがdeprecatedされた経緯がわかります。

github.com

長いIssueなのですが、要するに list.appendをもじってappendメソッド作ってるけど、作りも良くないしパフォーマンスも悪い。ドキュメントのappendのページもめっちゃ見られてるけど、代替手段を使うほうが良くね? という話です。データ構造の問題で、結局内部的にはconcatと同じことをやっていて、そもそもappendと呼んで良い操作ではない、みたいな話もあります。

Issueの方ではやはり賛否両論という感じです。そもそもパフォーマンスが問題にならないユースケースであればappendは単純に便利で直感的なメソッドです。議論の詳細はIssueの方を読んでもらうとして、結果的にはappendはdeprecatedして、代わりにconcatを勧めようという話になったようです。

代替手段はconcatで良いのか?

ただ、パフォーマンスやデータ構造の問題であるなら、単純にappendをconcatに置き換えても解決しないのでわ?という気がしますよね。特に、appendやconcatをループの中で呼び出してるような実装をしてる場合は尚更そうです。

代替手段はいろいろありますが、appendを使うのではなくDataFrame.from_dictを使うのがわりと知られた方法のようです。

github.com

DataFrameのインスタンスを繰り返し弄るよりも、Pythonのプリミティブなlistやdictでデータの集合を扱って、最後にDataFrameに変換して、必要ならconcatするなりすればよいという考え方です。

要するに、メッセージの通りappendを単純にconcatに置き換えるだけでは、パフォーマンスの観点的にはあんまりよくないってことです。

pandasのappendやconcatメソッドが遅くなる理由をちゃんと書こうとすると長大な内容になりますし、自分も正確に説明できるほど理解してないので割愛します。。。

簡単なベンチマーク

簡単に append concat listを変換してconcat の3つを比較してみました。

import time
import pandas as pd  # 1.4.0


# 全てappendで行う
def func_append(ss, _):
    s = pd.DataFrame([0])
    for other in ss:
        s = s.append(other, ignore_index=True)
    return s


# 全てconcatで行う
def func_concat(ss, _):
    s = pd.DataFrame([0])
    for other in ss:
        s = pd.concat([s, other], ignore_index=True)
    return s


# append操作は単なるlistで行って、最後に変換してconcatする
def func_list(_, n):
    s = pd.DataFrame([0])
    xs = []
    for i in range(n):
        xs.append(i)
    return pd.concat([s, pd.DataFrame(xs)])


fs = {"append": func_append, "concat": func_concat, "list": func_list}

for n in [1000, 3000, 10000]:
    # インスタンス生成の時間を計測に含めないために予め作っておく
    ss = [pd.DataFrame([i]) for i in range(n - 1)]
    for key, f in fs.items():
        t1 = time.time()
        res = f(ss, n - 1)
        t2 = time.time()
        print(len(res), key, t2 - t1)
要素数 メソッド time
1000 append 3.5408639907836914
1000 concat 0.11452603340148926
1000 list 0.0006008148193359375
3000 append 10.30685305595398
3000 concat 0.32955002784729004
3000 list 0.0013060569763183594
10000 append 34.366724729537964
10000 concat 1.076340913772583
10000 list 0.003818035125732422

計測方法が正しいかは自信がないですが、予想したとおり実行時間は append > concat > list です。正直、appendとconcatでここまで差が出るのは予想外でした。誰か詳しい人いたら理由を教えて下さい。

ただ、基本的にはDataFrame.from_dictを使ったり、listからDataFrameを生成してconcatするのが適切なようです。

まとめ

  • Pandasの DataFrame.appendSeries.append は1.4でDeprecatedされた。
  • Deprecatedの経緯はデータ構造的な話とパフォーマンスの話がある。
  • warningメッセージ従ってappendを単にconcatに置き換えるだけだと不適切な場合がある。
  • 特にループ内でappendやconcatを使う場合は、DataFrame.from_dictなどへの置き換えを検討しよう。

ライブラリの仕様が変わることは大なり小なりよくありますが、ある程度背景を把握したほうが色々勉強になるので、ぜひリリースノートや関連するIssueを掘る習慣をつけていきましょう。

alt

@kazasiki

デジタルテクノロジー統括部 デジタルソリューション部 Webアプリエンジニアグループ リードエンジニア

バックエンドエンジニア。VRゲームとダンスミュージックが好き。都内のクラブによく行く。

※2022年5月現在の情報です。