pandasのSettingWithCopyWarningで苦労したので本当に恥ずかしいけれど自戒のためにメモ。
SettingWithCopyWarningとは
偉大なる先人がめちゃくちゃ詳しい説明を書いてくれているので読むべし。
pandasのSettingWithCopyWarningを理解する (1/3)
ざっくり言っちゃうと参照渡し(でいいのかしら)があるがゆえの警告的なものだと解釈した。
元のデータの一部を抽出→そのうち一部へ代入した場合、「元のデータのその部分」を修正したかったのか、「一部を変更した新しいデータ」を作りたかったのかどっちかわからないよ、という感じ?
実際起こったこと
適当に値とかColumn名は変えているけれど、まあこんなDataFrameがあったとして
print(df_origin)
>>>
yyyymm human monster animal
4901620 201908 3208 184 567
4981645 201412 958 6972 169
5120608 201907 157 71 9
5236191 201501 790 7257 139
5320444 201111 1 2468 793
2〜4列目の合計の列を作って、yyyymmの列と合計の列だけ抜き出して、yyyymmの列をdatetime64
型に変換してやりたかったわけですよ。
df_origin["living"] = df_origin[["human", "monster", "animal"]].sum(axis=1)
df_analyze = df_origin[["yyyymm", "living"]]
df_analyze["yyyymm"] = pd.to_datetime(df["yyyymm"], format="%Y%m")
print(df_analyze)
>>>
SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead
See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
df_analyze["yyyymm"] = pd.to_datetime(df["yyyymm"], format="%Y%m")
yyyymm living
4901620 2019-08-01 3959
4981645 2014-12-01 8099
5120608 2019-07-01 237
5236191 2015-01-01 8186
5320444 2011-11-01 3262
警告によるとスライスの代わりに.loc
を使えと。ということで警告の出た3行目を.loc
で書き換えてみたのだけれど、まったく警告が解消される様子がなく。pandas_to_dataframe
使うのが初めてだったのでそこ起因かなと思って一度list型に変換してから代入してみたりしてみても効果なし。
いや結局、「なぜ警告されたのか」をしっかり把握していなかった私が悪いのですが。
何が問題だったのか
結論、さっきのpandasのSettingWithCopyWarningを理解する (1/3)後半に書かれている「隠れた連鎖」関連が原因だった。
そもそも警告文をちゃんと読まずに後半の
Try using .loc[row_indexer,col_indexer] = value instead
ここだけ見て「代わりに.loc使ってね」と解釈していたのが良くなかった。
A value is trying to be set on a copy of a slice from a DataFrame.
ちゃんと前半のこっち「DataFrameからスライスされたものに値をセットしようとしています」ってのを理解しなければいけなかった…。つまり、
df_origin["living"] = df_origin[["human", "monster", "animal"]].sum(axis=1)
df_analyze = df_origin[["yyyymm", "living"]]
#このdf_analyzeは「df_origin内のyyyymm列&living列」をスライスしたもの
#(pandasからすると書いた人間が「df_originそのものを変更するため参照した」のか、
#「切り出したものを別に使いたい」のかわからない)
df_analyze["yyyymm"] = pd.to_datetime(df["yyyymm"], format="%Y%m")
#この書き方そのものは全く問題ないが
#df_analyzeが前述の通り「参照」なのか「コピー」なのか不明なのでエラーが出る
print(df_analyze)
と、いうことでした。
なので、
df_origin["living"] = df_origin[["human", "monster", "animal"]].sum(axis=1)
df_analyze = df_origin[["yyyymm", "living"]].copy()
#.copy()で、参照ではなくコピーであることを明示する
df_analyze["yyyymm"] = pd.to_datetime(df["yyyymm"], format="%Y%m")
print(df_analyze)
こうすれば無事SettingWithCopyWarningは解消されましたとさ。めでたしめでたし。
教訓
警告文(エラー文)はちゃんと読むべし
読んでたつもりだったんだけどやっぱり英語読みたくない甘えが出てしまった。
私のような人が1人でも減ってくれることを祈って、ここに自らの恥を晒します。