結論はタイトルの通りです。公式ドキュメントにも書かれています。
同僚が「pandasのassignメソッドは内部でデータフレームのコピーを作っているから、メモリ食ってしまって遅くて困っているんだよ」という話をしました。
「Python/pandasのデータ処理で再帰代入撲滅委員会」を読み、統計のバッチ処理をメソッドチェーンを使ってきれいに書くことにハマっていました。
ところが、実際のpandasのコードを見ると、
# コメントや他のメソッドは略
class DataFrame(NDFrame):
def insert(self, loc, column, value, allow_duplicates=False):
data = self.copy()
# do all calculations first...
results = {}
for k, v in kwargs.items():
results[k] = com._apply_if_callable(v, data)
# ... and then assign
for k, v in sorted(results.items()):
data[k] = v
return data
となっており、「あれ?Pythonのcopyメソッドって、辞書や配列ではshallow copyだよね?」と思っていたのですが、
そのため、辞書や配列でcopyメソッドを使うときは、中のオブジェクトは同一で、中身のオブジェクトのコピーによってメモリを食いつぶすようなことは無いのですが、
a = {'a': [1, 2, 3]}
b = a.copy()
# aとbの中身は同一
assert a['a'] is b['a']
# 破壊的変更が波及している!
a['a'].append(4)
print(b)
# => {'a': [1, 2, 3, 4]}
pandasのcopyメソッド(や、それを利用したassignメソッド等)は、巨大なデータフレームを扱う際には、メモリの心配をしたほうが良さそうです。
import pandas as pd
df_a = pd.DataFrame({'a': [1, 2, 3]})
df_b = df_a.copy()
# aとbの中身は同一ではない!
assert df_a['a'] is not df_b['a']
冗談半分で、もしこれがHaskellだったらshallow copyでも破壊的変更が加わらないから全く問題ないのにね、と同僚と話しました。
同僚のコメントです。わかりやすさとメモリ節約を兼ね備えた書き方があれば知りたいです。
【pandasの再帰代入撲滅の注意】
assignやpipeを使って再帰代入しないようにしていることも多いかと思うけど、assignはdf自体をcopyしてるので大分遅くなるので注意。一方、pipeはcopyしていないので大丈夫assign
https://github.com/pandas-dev/pandas/blob/v0.20.3/pandas/core/frame.py#L2492
pipe
https://github.com/pandas-dev/pandas/blob/v0.19.2/pandas/core/generic.py#L2698-L2708ただ、assignはuiとして読みやすいのも事実だと思うので、
- できるだけassignの回数を減らす(一回のassignで複数のカラムを追加してもcopyは発生していないので大丈夫)
- もう振り切って再帰代入する
ことがせいぜいできることかな…
ちなみにassignに渡す前にカラムを絞って、帰ってきたdataframeを元のdataframeにconcatも試してみたけど、逆にかなり遅くなったのでこれもあまりよくない