LoginSignup
109
64

More than 1 year has passed since last update.

pandas の SettingWithCopyWarning で苦労した話

Last updated at Posted at 2019-09-23

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人でも減ってくれることを祈って、ここに自らの恥を晒します。

109
64
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
109
64