PandasのSettingWithCopyWarningに対する対処方法
pandasのDataFrameのObjectをindexingするとき,よく遭遇するWithCopyWarning.どういう場合に起きて,どう対処すればいいのかを調べました.
試した環境は以下です.
pandas '0.23.4'
Python 3.6.6
IPython 7.1.1
例として,以下のpandas.DataFrameを使います.
name score age
John 50 12
Mike 60 13
Nancy 80 11
Yuki 49 11
よく起こる例 #1 | Chained assignment
例えば,年齢が11のスコアを全て100にしたいと思った時に,
df[df.age == 11]["score"] = 100
とすると,
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
SettingWithCopyWarningが起こります.
これは,double indexingすることで,indexingして得られた新しいDataFrameがviewなのか,copyなのかが判別がつかないからです.
そのため,実際に変更されたのは,copyの場合は,元のDataFrameは変更されません.実際に,dfを見てみると,変更されていないことがわかります.
name score age
John 50 12
Mike 60 13
Nancy 80 11
Yuki 49 11
この問題を回避するには,locを用いて,以下のようにします.
df.loc[df.age == 11, "score"] = 100
locはviewであることを保証してくれるので,viewを変更すれば,元のDataFrameも変更されます.
name score age
John 50 12
Mike 60 13
Nancy 100 11
Yuki 100 11
もう一つの方が難解です.
よく起こる例 #2 | Hidden chaining
年齢が11才の人のデータをlocを使って,別の変数に保存します.
part_df = df.loc[df.age == 11]
そして,実は,indexが3の名前が実はYukinoであることがわかりました.よって以下の修正をしました.
part_df.loc[3, "name"] = Yukino
すると,またSettingWithCopyWarningが出ました.
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
locを用いたのに,同じエラーがおきました.しかし,実際にpart_dfを見てみると,変更が反映されています.Warningが出た理由は,最初のindexingをした時に得られたDataFrameのpart_dfがcopyかviewかわからないからです.そのviewかcopyかわからないDataFrameに対して,2回目のindexingが施されたことによって,Warningが発生しました.
今回の場合は,copyだったので,元のDataFrameには変更が及んでいません.このようにpandasは,indexingした時にcopyかviewかわからない状況で,値が変更されそうになるとWarningが発生します.
元のDataFrameのdfは変更されたくない場合は,copyであることを明示的に教えてあげればWarningは出ません.
part_df = df.loc[df.age == 11].copy()
元のdfに対して#1の例のように,locを用いて値を変更すれば良いです.
Warningが出ないが,出すべき例
df.loc[df.age == 11, ("name", "score")]["score"] = 100
のように,double indexingをすると,変更がdfに適用されません.#1の例のように,double indexingがcopyを返しているからです.
しかし,Warningは出ません.一応,GitHubでもIssueになっています.
結局どうするか?
今のDataFrameが他のDataFrameのviewであるか,copyであるかが明確になるようなコーディングをすると良いです.
DataFrameの一部をコピーして操作したい場合は,indexingしたものにcopyメソッドを明示的に呼び出す.
DataFrameの一部をviewとして操作したい場合は,booleanの配列のmaskを用いる.例えば,
age_mask = df.age == 11
df.loc[age_mask].mean()
のように使う.
Reference
[1] SettingwithCopyWarning: How to Fix This Warning in Pandas
[2] [Indexing and selecting data](https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html)
この記事が気に入ったらサポートをしてみませんか?