[Python / pandas] SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. が発生するのはどんなとき


概要

タイトルの通りです。
以下の warning を再現させる最小限(のつもり)のコードを記録に残しておいたものです。

/pandas/core/indexing.py:1743: 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
  isetter(ilocs[0], value)

長い記事を読みたくないので、サクッと読める分量にまとめました。
ほとんど何も考えなくても理解できる記事を目指しています。

ソースコード

google colaboratory 上で発生させました。

1. まずは準備から

import pandas as pd

numbers = pd.DataFrame({'number': [1, 2 ,3 ,4]})
numbers

    number
0   1
1   2
2   3
3   4

2. 原因

これが良くないらしい
つまりこれで再現する

# 行方向にスライス
evens = numbers.loc[numbers['number'] % 2 == 0]
evens

    number
1   2
3   4

3. 再現

# はい、発生
evens['str'] = ['2', '4']

/usr/local/lib/python3.7/dist-packages/ipykernel_launcher.py: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
  """Entry point for launching an IPython kernel.

4. 対策

「2. 原因」のところのコードで、スライス後にcopy()を使えば解決する

# 行方向にスライス、の後にcopy()を追加。これでOK。
evens = numbers.loc[numbers['number'] % 2 == 0].copy()
evens

    number
1   2
3   4

# 上記のように、スライス結果をcopy()したものに対してであれば、
# 行追加してもwarningは出ない
evens['str'] = ['2', '4']

5. 結論

スライスしたらcopy()しよう。

いつもこのwarning出して、原因思い出せなくて時間食う....(´;ω;`)ぐすっ

関連記事

詳細について知りたい場合はこの記事が詳しいです