【Python】Pandasで発生する警告「SettingWithCopyWarning」の解決策

Pandas

この記事ではSettingWithCopyWarningの解決と原因を紹介します。

この記事での目標
・SettingWithCopyWarningの解決
・SettingWithCopyWarningの原因理解

 

DataFrameとSeries型の違いがわからない人はまず以下の記事で理解しましょう!!
DataFrameとSeriesの違いを紹介
その他Pythonの記事は以下にまとめています!!

Python記事まとめ

 

DataFrameを色々いじって実行すると警告で「SettingWithCopyWarning」が出ることがあります。
例えば以下のような動作をすると出ます。

import pandas as pd
#df作成
df = pd.DataFrame([[1,10],[2,20],[2,30]])

#dfの1行目未満(0行目)をコピー?
df_c = df[:1]

#コピーの0列目に+1をする
df_c[0] = df_c[0] + 1
print(df_c)

"""
#実行結果
   0 1
0 2 10
C:/Users/yokohama6/.spyder-py3/temp.py:5: 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: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
d['col3']= d['col2'] * 3
"""

警告内容としては元データが変更されているけどいいの??って感じのことを言ってます。
よくわからないかもしれませんが、原因を知れば納得するはずです。
とはいえまずはすっきりしたいと思うので解決策を紹介します。

[スポンサーリンク]


SettingWithCopyWarningの解決策

解決策を端的に言うとDataFrameをコピーする時にcopy()を使ってないからです。
先ほどの例でDataFrameをコピーしている部分にcopy()を付けて実行してみます。

import pandas as pd
#df作成
df = pd.DataFrame([[1,10],[2,20],[2,30]])
#dfの1行目未満(0行目)をコピー
df_c = df[:1].copy()
#コピーの0列目に+1をする
df_c[0] = df_c[0] + 1
print(df_c)

#実行結果
#  0 1
#0 2 10

copy()を付けることで警告がでなくなりました。
ひとまずこれで解決です。

 

SettingWithCopyWarningの原因

SettingWithCopyWarningが発生する原因は=を使ってもコピーできておらず参照渡しになっているためです。
以下のような値がすべて1のDataFrameであるdfが存在しているとしましょう。

df=pd.DataFrame([[1,1],[1,1]])
print(df)
#出力結果
#  0 1
#0 1 1
#1 1 1

dfをコピーしたdf_cの0列目に100を足してみます。

#dfをdf_cにコピー?
df_c=df
#df_cの0列目に100を足す
df_c[0]=df_c[0]+100
print(df_c)

#出力結果
#  0 1
#0 101 1
#1 101 1

はい、きちんとdf_cの0列目に100が足されていました。
ここまではいいですね!

しかしここでdfを見てみると…

print(df)
#出力結果
#  0 1
#0 101 1
#1 101 1

なんとdfにも100が足されています!!
処理の流れを見てもdf自体に100を足していません。
足したのはdf_cですが、なぜかdfにも100が足されてしまいました。

この理由は参照渡しだからです。
最初の「df_c=df」を見るとdf_cをdfにコピーしているように思えますが、
df_cはdfのアドレスを保持しているだけなんです!!

つまりdf_cが表示する値はあくまでそのアドレスにあるdfの値なのです。
そのためdf_cを編集するということはdfも編集されてしまうことになるのです。
つまり=じゃコピーできてないよということ。

なので「SettingWithCopyWarning」は元のDataFrameが変わってるけどいいの??ということを言ってくれてたんですね。

ではコピーするにはどうすればいいのか?というと冒頭の解決策で紹介したcopyメソッドを使用することです。
コピーしたいDataFrame.copy()を付けましょう。

import pandas as pd
df=pd.DataFrame([[1,1],[1,1]])
df_c=df.copy()
df_c[0]=df_c[0]+100
print(df_c)

#df_cの出力結果
#    0 1
#0 101 1
#1 101 1

print(df)
#dfの出力結果
#  0 1
#0 1 1
#1 1 1

この通り、df_cは+100されており、dfは+100されませんでした。

今回、DataFrameで確認しましたが、Series型も参照渡しがデフォルトになっているのでコピーしたい時はcopyメソッドを使用しましょう。

リストも参照渡しになっている

ちなみにリストもデフォルトが参照渡しになっています。
確認するために値が1しかないlist1list2に参照渡しして、list2を-100します。

list1=[1,1,1]
print("初期のlist1の値")
print(list1)
list2=list1
list2[0]=list2[0]-100
print("list2を編集後のlist1の値")
print(list1)
print("list2の値")
print(list2)

"""
#実行結果
初期のlist1の値
[1, 1, 1]
list2を編集後のlist1の値
[-99, 1, 1]
list2の値
[-99, 1, 1]
"""

はい、予想通りlist1list2が同じ値になってしまいました。

これを防ぐためにリストでもcopyメソッドを使用することで解決します。

list1=[1,1,1]
print("初期のlist1の値")
print(list1)
list2=list1.copy()
list2[0]=list2[0]-100
print("list2を編集後のlist1の値")
print(list1)
print("list2の値")
print(list2)

"""
#実行結果
初期のlist1の値
[1, 1, 1]
list2を編集後のlist1の値
[1, 1, 1]
list2の値
[-99, 1, 1]
"""

copy()を付けることでlist1が編集されなくなりました。

まとめ

PandasDataFrameSeriesリストコピーする時は=ではなくcopyメソッドを使いましょう。
警告が出ていなくとも思わぬバグを防ぐことができます。

そもそもなぜ参照渡しがデフォルトかというと
リストDataFrameなどは容量が大きくなりがちなので値渡しだとメモリを圧迫してしまうからです。
値渡しは重たいけど参照渡しならアドレスだけだからめっちゃ軽いというわけ

ということでバグ防止のためにもコピーする時はcopy()をつけ忘れないように気を付けましょう!

[スポンサーリンク]


コメント

  1. […] ・SettingWithCopyWarningの対策 […]

タイトルとURLをコピーしました