Pandas
の DataFrame でSettingWithCopyWarning
の警告の意味と対処方法について書きます。
DataFrame使っているとSettingWithCopyWarning
によく遭遇していました。その度にその場しのぎの修正をして対応していましたが、さすがにそろそろ根本的に理解しないと時間がもったいないと思い、この記事で整理しました。
公式Pandas
のヘルプと偉大な先人が書いてくれた以下の記事が非常にわかりやすいです。この記事では、簡潔に内容を書きます。
結論
結論から先に述べます。
暗黙のCopyをやめる。そのためにChain Indexingをやめる。警告をなくすにはそれだけです。
現象
SettingWithCopyWarning
が起こる一例を最初に紹介します。
シンプルなDataFrameを作ります。
import pandas as pd
df = pd.DataFrame([[1, 11],
[2, 11],
[3, 33]],
columns=['A', 'B'],
index=['a', 'b', 'c'])
print(df)
A B
a 1 11
b 2 11
c 3 33
で、B列の値が11のA列の値を代入しようとします。
df[df['B']==11]['A'] = 4
print(df)
値は変わりません。
A B
a 1 11
b 2 11
c 3 33
で、SettingWithCopyWarning
が出力。
<ipython-input-62-77f8496ed860>:1: 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: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
df[df['B']==11]['A'] = 4
警告が起こる前提知識(View or Copy)
SettingWithCopyWarning
を理解するためには、DataFrame処理の種類を知る必要があります。
PandasはDataFrameを処理するときにViewまたはCopyを返します。
ViewとCopyで以下のように動きが異なります。
**上図の右側のCopyに対して値代入をした場合にSettingWithCopyWarning
**が発生し、元のDataFrameへの値代入が行われません。理由は元のDataFrameと別メモリのオブジェクトだからです。
つまり、値代入時にCopyではなくViewを使うことで警告がなくなり、問題が発生しません。
警告発生パターンと対処法
Pandas
はCopyを返すかViewを返すかの関数が明示的に決まっているわけでないので、問題を単純化できません。そのため、多くの人が悩みます。詳しくは記事「pandasのSettingWithCopyWarningを理解する (3/3)」の「歴史」章を読んでください。
Chained Indexing
Chained Indexingの再現条件
最も基本的なSettingWithCopyWarning
が発生するパターンはChained Indexingと呼ばれる処理です。
下記のようにdf[df['B']==11]
という抽出処理に、さらに['A']
の抽出を重ねて(Chainして)います。この場合はViewではなくCopyとなるみたいです。また、警告が発生する以外にもそもそもパフォーマンスが悪く望ましくない書き方です。
ブールインデックスを使っていますが、query
メソッドでも同じです。
※記事「DataFrameレシピ: データ抽出条件」にブールインデックスやquery
関数を使った抽出方法も書いています。
df[df['B']==11]['A'] = 4
print(df)
実行しても、Copyなので元のDataFrameであるdf
の値は変化がありません。
A B
a 1 11
b 2 11
c 3 33
で、警告が出力されます。
<ipython-input-62-77f8496ed860>:1: 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: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
df[df['B']==11]['A'] = 4
Chained Indexing対処法
警告で出ているTry using .loc[row_indexer,col_indexer] = value instead
をそのまま実施。
loc
プロパティを使います。
df.loc[df['B']==11, ['A']] = 4
print(df)
警告は消え、A列の値が置き換わっているのがわかります。
A B
a 4 11
b 4 11
c 3 33
隠れ連鎖
一見するとChained Indexingを使っていないように見える隠れた連鎖(Chain)です。
隠れ連鎖の再現条件
今度は、copyしたDataFrameに対して代入をした場合です。df2
というdf
のCopyを作り、列A
に値の代入をします。df2['A'] = 4
部分だけを見るとなぜ警告が出るのか理解できないパターンです。
df2 = df[df['B']==11]
df2['A'] = 4
print('--df--')
print(df)
print('--df2--')
print(df2)
df
の値は変わっておらずCopyであるdf2
は値が変更されます。
--df--
A B
a 1 11
b 2 11
c 3 33
--df2--
A B
a 4 11
b 4 11
警告が出力されます。
<ipython-input-82-a08d45cf3537>:2: 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: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
df2['A'] = 4
上記の警告を見て単純にloc
使っても警告は出たままです。
df2 = df[df['B']==11]
df2.loc[:,'A'] = 4 # df2['A'] = 4 から変更
print('--df--')
print(df)
print('--df2--')
print(df2)
--df--
A B
a 1 11
b 2 11
c 3 33
--df2--
A B
a 4 11
b 4 11
警告内容は少し変わっています。
/home/i348221/Apps/python/venv/py39/lib/python3.9/site-packages/pandas/core/indexing.py:1843: 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: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
self.obj[item_labels[indexer[info_axis]]] = value
隠れ連鎖の対処法
copy
関数を使ってdf2
はdf
のCopyだということ明示的に処理すれば警告は消えます。(熟考していませんが)おそらく、元のDataFrameであるdf
の値も変えたいことは無いのではないでしょうか。
df2 = df[df['B']==11].copy()
df2['A'] = 4
print('--df--')
print(df)
print('--df2--')
print(df2)
実行結果は、警告が消える以外は変わりません。
--df--
A B
a 1 11
b 2 11
c 3 33
--df2--
A B
a 4 11
b 4 11
おまけ(偽陰性)
警告が出ないけど、値代入が失敗するパターンもあります。やはりChain Indexingが原因です。警告出ないので普段からChain Indexをしない癖をつけておくことが大事みたいです。
※偽陰性(False Negative)の意味は別記事「【入門者向け】機械学習の分類問題評価指標解説(正解率・適合率・再現率など)」を参照ください。
df.loc[df.B == 11, ('A', 'B')]['A'] = 4
print(df)
A B
a 1 11
b 2 11
c 3 33
Chain Index やめればもちろん代入成功です。
df.loc[df.B == 11, 'A'] = 4
print(df)
A B
a 4 11
b 4 11
c 3 33
他にも偽陽性(警告出る必要ないのに誤って警告出力)パターンもあるらしいですが、最新のPandasで出るかわからないので、確認していません。