データエンジニアの @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.
リリースノート的には以下の部分ですね。
メッセージやドキュメントだけ見れば、appendをconcatに置き換えればいい、という話になりますが、ちょっと違和感を感じてIssueを追いかけてみました。
appendがdeprecatedされた経緯
ドキュメントから参照されている以下のIssueを見れば、appendがdeprecatedされた経緯がわかります。
長いIssueなのですが、要するに list.appendをもじってappendメソッド作ってるけど、作りも良くないしパフォーマンスも悪い。ドキュメントのappendのページもめっちゃ見られてるけど、代替手段を使うほうが良くね?
という話です。データ構造の問題で、結局内部的にはconcatと同じことをやっていて、そもそもappendと呼んで良い操作ではない、みたいな話もあります。
Issueの方ではやはり賛否両論という感じです。そもそもパフォーマンスが問題にならないユースケースであればappendは単純に便利で直感的なメソッドです。議論の詳細はIssueの方を読んでもらうとして、結果的にはappendはdeprecatedして、代わりにconcatを勧めようという話になったようです。
代替手段はconcatで良いのか?
ただ、パフォーマンスやデータ構造の問題であるなら、単純にappendをconcatに置き換えても解決しないのでわ?という気がしますよね。特に、appendやconcatをループの中で呼び出してるような実装をしてる場合は尚更そうです。
代替手段はいろいろありますが、appendを使うのではなくDataFrame.from_dictを使うのがわりと知られた方法のようです。
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.append
とSeries.append
は1.4でDeprecatedされた。 - Deprecatedの経緯はデータ構造的な話とパフォーマンスの話がある。
- warningメッセージ従ってappendを単にconcatに置き換えるだけだと不適切な場合がある。
- 特にループ内でappendやconcatを使う場合は、DataFrame.from_dictなどへの置き換えを検討しよう。
ライブラリの仕様が変わることは大なり小なりよくありますが、ある程度背景を把握したほうが色々勉強になるので、ぜひリリースノートや関連するIssueを掘る習慣をつけていきましょう。
@kazasiki
デジタルテクノロジー統括部 デジタルソリューション部 Webアプリエンジニアグループ リードエンジニア
バックエンドエンジニア。VRゲームとダンスミュージックが好き。都内のクラブによく行く。
※2022年5月現在の情報です。