![見出し画像](https://assets.st-note.com/production/uploads/images/99227279/rectangle_large_type_2_91d25c24bb1ee04733f67b39b0a1dc10.png?width=800)
pandas_SettingWithCopyWarningを回避しながらDataFrameに列を挿入する #354
SettingWithCopyWarningとは
pandasではDataFrameのget操作がデータのビューとコピーのどちらを返すのか保証しておらず、そのことに起因して発生する警告です。どのDataFrameを操作しているのか曖昧になっている、ということが警告されています。
例えば以下のようにbidderというカラムの値が'parakeet2004'であるレコードを取得(get)する場合です。ここではビューとコピーのどちらが返ってくるか保証されていません。
data[data.bidder == 'parakeet2004']
上記コードではPythonインタプリタが以下を実行しています。
data.__getitem__(data.__getitem__('bidder') == 'parakeet2004')
このget操作の際にビューかコピーのいずれかが返ってきています。ここで取得した値をそのまま使うなら問題ありません。
ただ、値を更新したい場合はそうもいきません。bidderというカラムの値が'parakeet2004'であるレコードの、'bidderrate'というカラムに100という値を入力します。以下のように記述するとSettingWithCopyWarningが出ます。
data[data.bidder == 'parakeet2004']['bidderrate'] = 100
上記コードではPythonインタプリタが以下を実行しています。
data.__getitem__(data.__getitem__('bidder') == 'parakeet2004').__setitem__('bidderrate', 100)
これはget操作に連鎖してset操作が行われています。これは連鎖インデックス (Chain Indexing)呼ばれ、SettingWithCopyWarningの原因です。
連鎖インデックスとは、pandasの内部的には、単一の操作を実行するために__getitem__または__setitem__を複数回呼び出すこと
つまり、ビューかコピーか曖昧なままgetしてきたデータに対してsetしているので、どのメモリで値が更新されたか曖昧で警告されています。
ビューとコピーについて分かりやすい図があったので掲載させていただきます。
![](https://assets.st-note.com/img/1677761942348-7pB5EsLM81.png?width=800)
ではこの警告をどうやって回避するか。
連鎖した処理ではなく、一つの処理になるようにまとめてあげれば良いです。
locを使って上記コードを修正すると警告は出なくなります。
data.loc[data.bidder == 'parakeet2004', 'bidderrate'] = 100
上記コードではPythonインタプリタが以下を実行しています。
data.loc.__setitem__((data.__getitem__('bidder') == 'parakeet2004', 'bidderrate'), 100)
getしたデータに対してsetするのではなく、setする位置を特定するところでgetしています。連鎖インデックスではなくなっているので警告も出ません。また、locプロパティはコピーではなく元のDataFrameであることが保証されています。
SettingWithCopyWarningの実例をみてみる
より実際っぽいコードで確認してみます。
df_masterはdf_originからget操作で作成されているので、以下のように1列追加しようとするとSettingWithCopyWarningが出ます。
# 元のデータフレーム
df_origin = pd.read_csv('sample.csv').reset_index(drop=True)
# 元のデータフレームから必要なカラムだけ抽出したもの
df_master = df_origin[[
'id',
'first_date_29h',
'type_num',
'fee']]
# 1列追加する
df_master['type_name'] = 'sample'
<string>:43: 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は出ません。get操作も挟んでおらず、元のデータフレームに対する操作であることが確定しているためです。
# 元のデータフレーム
df_origin = pd.read_csv('sample.csv').reset_index(drop=True)
df_origin['type_name'] = 'sample'
SettingWithCopyWarningを回避しながらDataFrameに列を挿入するには
より複雑な処理をしながらSettingWithCopyWarningを回避する方法をメモしておきます。ここではmapで処理を加えてみてます。type_numの内容に応じて、動的にtype_nameの値を挿入していくイメージです。
まず警告が出るパターンです。
お馴染みになってきましたが、以下のように1列追加するとChain indexingになっているため、SettingWithCopyWarningが出ます。
# 元のデータフレーム
df_origin = pd.read_csv('sample.csv').reset_index(drop=True)
# 元のデータフレームから必要なカラムだけ抽出したもの
df_master = df_origin[[
'id',
'first_date_29h',
'type_num',
'fee']]
# 1列追加する
df_master['type_name'] = df_origin['type_num'].map(Type.convert_type_num_to_name)
<string>:41: 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
以下のように元のデータフレームに対して1度の処理として書き切ってしまえば、Chain indexingにならずSettingWithCopyWarningが出ません。
# 元のデータフレーム
df_origin = pd.read_csv('sample.csv').reset_index(drop=True)
# 元のデータフレームから必要なカラムだけ抽出しつつ、行によって値が変わる列を1列追加し、不要になった列を削除する
df_master = df_origin[[
'id',
'first_date_29h',
'last_date_29h',
'type_num',
'fee']].assign(type_name=df_origin['type_num'].map(GrpType.convert_type_num_to_name)).drop('type_num', axis=1)
もしくは、df_masterを宣言するところで明示的にcopyしてしまう手もあります。ビューではなくコピーであることを明示的にしてあげれば、これはこれでChain indexingになりません。
df_origin = pd.read_csv('sample.csv').reset_index(drop=True)
df_master = df_origin[[
'id',
'first_date_29h',
'last_date_29h',
'type_num',
'fee']].copy()
df_master['type_name'] = df_origin['type_num'].map(Type.convert_type_num_to_name)
df_master.drop('type_num', axis=1, inplace=True)
ただ、データ量が多いとcopyの多用はメモリ逼迫に繋がります。そのため実装上は不適切なケースもあるので、そのような場合はcopyせずに前者の方法を取る方が無難です。
ここまでお読みいただきありがとうございました!
参考
この記事が気に入ったらサポートをしてみませんか?