見出し画像

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)

この記事が気に入ったらサポートをしてみませんか?