この記事は、
SettingwithCopyWarning: How to Fix This Warning in Pandas – Dataquest
の日本語訳です。3回にわたって掲載予定で、この記事は2回目です。
1回目の記事はこちら:
linus-mk.hatenablog.com
SettingWithCopyWarningを処理するためのヒントとコツ
以下でもっと詳細な分析をする前に、顕微鏡を取り出して、SettingWithCopyWarningの細かい点と核心的な点をいくつか見てみよう。
警告を消す
最初に、この記事を完全なものにするためには、SettingWithCopy
の設定を明示的に制御する方法を説明せねばなるまい。pandas内のmode.chained_assignment
オプションは、以下のいずれかの値を取ることができる。
'raise'
- 警告の代わりに例外を上げる。
'warn'
- 警告を生成する(デフォルト)。
None
- 警告を完全にオフにする。
たとえば、警告をオフにしてみよう。
pd.set_option('mode.chained_assignment', None)
data[data.bidder == 'parakeet2004']['bidderrate'] = 100
この設定では警告が一切発生しないので、あなたが自分がしていることを十分に理解しているのでない限り、この設定は推奨されない。あなたがほんのわずかでも疑問を感じるのであれば、これはお勧めできない。SettingWithCopy
を非常に重要なものと考えて、警告ではなく例外に昇格させることを選ぶ開発者もいる。以下のようになる:
pd.set_option('mode.chained_assignment', 'raise')
data[data.bidder == 'parakeet2004']['bidderrate'] = 100
---------------------------------------------------------------------------
SettingWithCopyError Traceback (most recent call last)
<ipython-input-13-80e3669cab86> in <module>()
1 pd.set_option('mode.chained_assignment', 'raise')
----> 2 data[data.bidder == 'parakeet2004']['bidderrate'] = 100
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/pandas/core/frame.py in __setitem__(self, key, value)
2427 else:
2428
-> 2429 self._set_item(key, value)
2430
2431 def _setitem_slice(self, key, value):
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/pandas/core/frame.py in _set_item(self, key, value)
2500
2501 if len(self):
-> 2502 self._check_setitem_copy()
2503
2504 def insert(self, loc, column, value, allow_duplicates=False):
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/pandas/core/generic.py in _check_setitem_copy(self, stacklevel, t, force)
1758
1759 if value == 'raise':
-> 1760 raise SettingWithCopyError(t)
1761 elif value == 'warn':
1762 warnings.warn(t, SettingWithCopyWarning, stacklevel=stacklevel)
SettingWithCopyError:
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
あなたのチームでpandasの経験の浅い開発者と一緒のプロジェクトで作業をしている場合、またはその完全性において高いレベルの厳格さまたは確実性を必要とするプロジェクトで作業をしている場合、これは特に役に立つ。
この設定を使用するより正確な方法は、コンテキストマネージャを使用することである。
pd.reset_option('mode.chained_assignment')
with pd.option_context('mode.chained_assignment', None):
data[data.bidder == 'parakeet2004']['bidderrate'] = 100
コードを見れば分かるように、このアプローチをとれば、一律に環境全体に影響を及ぼすのではなく、警告を細かく抑制することができる。
is_copyプロパティ
この警告を回避するためのやり方がもう一つある。それはSettingWithCopy
シナリオを解釈するためにpandasが使っているツールの1つを変更することである。
それぞれのDataFrameは、is_copy
プロパティを持ち、デフォルトではNone
である。コピーである場合は、is_copy
プロパティはソースのDataFrame
を参照するためにweakrefを使用する。is_copy
をNone
に設定すると、警告を生成しないようにすることができる。
winners = data.loc[data.bid == data.price]
winners.is_copy = None
winners.loc[304, 'bidder'] = 'therealname'
しかしながら、これは奇跡的に問題を解決するわけではなく、バグの検出が非常に困難になる可能性がある。
single-dtyped 対 multi-dtyped オブジェクト
強調しておく価値のあるさらなる点は、single-dtypedオブジェクトとmulti-dtypedオブジェクトの区別である。DataFrameは、そのすべての列が同じdtypeの場合、single-dtypedである。例えば:
import numpy as np
single_dtype_df = pd.DataFrame(np.random.rand(5,2), columns=list('AB'))
print(single_dtype_df.dtypes)
single_dtype_df
A float64
B float64
dtype: object
- |
A |
B |
0 |
0.383197 |
0.895652 |
1 |
0.077943 |
0.905245 |
2 |
0.452151 |
0.677482 |
3 |
0.533288 |
0.768252 |
4 |
0.389799 |
0.674594 |
一方、 DataFrameは、そのすべての列のうち同じdtypeではないものがある場合、multi-dtypedである。たとえば:
multiple_dtype_df = pd.DataFrame({'A': np.random.rand(5),'B': list('abcde')})
print(multiple_dtype_df.dtypes)
multiple_dtype_df
A float64
B object
dtype: object
- |
A |
B |
0 |
0.615487 |
a |
1 |
0.946149 |
b |
2 |
0.701231 |
c |
3 |
0.756522 |
d |
4 |
0.481719 |
e |
以下の「歴史」のセクション(※訳注:第3回で掲載予定)で説明している理由から、multi-dtypedオブジェクトに対するindexer-get操作は常にコピーを返す。 ただし、主に効率のために、single-dtyped型オブジェクトに対するindexer-get操作はほとんどの場合ビューを返す。ここで注意すべき点は、これ(ビューとコピーのどちらが返るのか)はオブジェクトのメモリレイアウトに依存し、保証されていないということである。
(訳注:ここでの「偽陽性」は「実際には問題が起きていないのに、警告が発生すること」を指す)
偽陽性、すなわち連鎖割り当てが実際には起きていないのに報告されている状況は、以前のバージョンのpandasではよりよくあることだったが、その後はほとんど解決されている。完全を期すために、現在は修正された偽陽性の例をここに述べておいた方がよいだろう。pandasの以前のバージョンで以下のような状況が発生した場合は、警告を無視しても抑制しても安全である(またはアップグレードすることで完全に回避できる)。
現在の列の値を使用してDataFrame
に新しい列を追加すると、警告が発生していたが、これは修正された。
data['bidtime_hours'] = data.bidtime.map(lambda x: x * 24)
data.head(2)
- |
auctionid |
bid |
bidtime |
bidder |
bidderrate |
openbid |
price |
bidtime_hours |
0 |
8213034705 |
95.0 |
2.927373 |
jake7870 |
0 |
95.0 |
117.5 |
70.256952 |
1 |
8213034705 |
115.0 |
2.943484 |
davidbresler2 |
1 |
95.0 |
117.5 |
70.643616 |
最近まで、DataFrame
のスライスに対してapply
メソッドを使って値を設定するときにも、偽陽性が発生したが、これも修正されている。
data.loc[:, 'bidtime_hours'] = data.bidtime.apply(lambda x: x * 24)
data.head(2)
- |
auctionid |
bid |
bidtime |
bidder |
bidderrate |
openbid |
price |
bidtime_hours |
0 |
8213034705 |
95.0 |
2.927373 |
jake7870 |
0 |
95.0 |
117.5 |
70.256952 |
1 |
8213034705 |
115.0 |
2.943484 |
davidbresler2 |
1 |
95.0 |
117.5 |
70.643616 |
そして最後に、バージョン0.17.0までは、DataFrame.sample
メソッド に誤ったSettingWithCopy
の警告を引き起こすバグが存在した。現在では、sample
メソッドは常にコピーを返す。
sample = data.sample(2)
sample.loc[:, 'price'] = 120
sample.head()
- |
auctionid |
bid |
bidtime |
bidder |
bidderrate |
openbid |
price |
bidtime_hours |
481 |
8215408023 |
91.01 |
2.990741 |
sailer4eva |
1 |
0.99 |
120 |
71.777784 |
503 |
8215571039 |
100.00 |
1.965463 |
lambonius1 |
0 |
50.00 |
120 |
47.171112 |
2019年5月18日 追記:
3回目
linus-mk.hatenablog.com