【Python】SettingWithCopyWarnig: A value is trying to be set on a copy of a slice from DataFrame が出た時の対処法
概要
PythonのPandasでDataFrameを操作していた際に、以下のエラーに遭遇したのでその対処ほうについてメモします。
SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
環境
- Python 3.8.10
- pandas 1.3.5
発生状況
まず次のようなDataFrameを作成します。
import pandas as pd
df = pd.DataFrame([[1,2,3],[4,5,6],[7,8,9,]], columns=['col1', 'col2', 'col3'])
df.head()
上記で作成したDataFrameから特定のカラムなどで抽出したものを変数へ格納し、さらにその変数(DataFrame)に列を追加しようとしたところSettingWithCopyWarnigが発生しました。
df_tmp = df
df_tmp['col4'] = df['col1']
df_tmp = df[['col1','col2']]
df_tmp['col5'] = df['col2']
----
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: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
df_tmp['col4'] = df['col1']
Warningは、「値がデータフレームのスライスのコピーが代入されようとしている。代わりに.locメソッドを試してください」といった内容でした。
Warningの解釈
途中にdisplay(df)を挟んでdfの挙動を確認してみたところ以下の通りでした。
df_tmp = df
df_tmp['col4'] = df['col1']
display(df) # ...(1)
df_tmp = df[['col1','col2']]
display(df) # ...(2)
df_tmp['col5'] = df['col2']
display(df) # ...(3)
(1), (2), (3)の結果はいずれも以下の通りでした。
この挙動から以下のように解釈しました。
df_tmp = df
df_tmp['col4'] = df['col1'] #参照渡しになっている
df_tmp = df[['col1','col2']] #df_tmpへコピーが渡されている(dfの値は変わらない)
df_tmp['col5'] = df['col2'] #コピーに対する代入なのでSettingWithCopyWarningが出る
どうやら、dfをそのまま = で渡した時には参照渡しになるが、列指定でスライスして渡した場合にはコピーが渡されるようです。
そして、コピー先のデータフレームに対して元のデータフレームのスライスを代入しようとした場合にPythonが気を利かせてWarningしている様子でした。
対処法
次のようにコピーで渡す箇所については明示的に.copy()メソッドを使用すると SettingWithCopyWarnig は出なくなりました。
df_tmp = df
df_tmp['col4'] = df['col1']
df_tmp = df[['col1','col2']].copy()
df_tmp['col5'] = df['col2']
まとめ
データフレームは参照渡しかコピー渡しかが分かりにくいためS SettingWithCopyWarnig が出た場合はなるべく明示的にcopy()を使用してやるのが良いかもしれません。