17
Help us understand the problem. What are the problem?

posted at

DataFrameでSettingWithCopyWarningの意味と対処法

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)
DataFrameの最初の中身
   A   B
a  1  11
b  2  11
c  3  33

で、B列の値が11のA列の値を代入しようとします。

df[df['B']==11]['A'] = 4
print(df)

値は変わりません。

DataFrameの最初の中身
   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で以下のように動きが異なります。
image.png
上図の右側の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関数を使ってdf2dfの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で出るかわからないので、確認していません。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
17
Help us understand the problem. What are the problem?