ビュー、コピー、およびその迷惑な設定


すべてのパンダで任意の時間を費やしている場合は、見てきたSettingWithCopyWarning . ない場合は、すぐに!
どんな警告のようにでも、あなたが理由のためにそれを得るので、それを無視しないことは賢いです:あなたが多分何か間違っていることであるという徴候です.私のケースでは、私は通常、私はいくつかの分析で膝の深いときに、それを修正する方法を考え出すあまりにも多くの時間を過ごすしたくないときに、この警告を得る.
私はいくつかの典型的な例は、この警告が表示されるときに、なぜそれが表示され、どのように迅速に根本的な問題を修正する方法をカバーするつもりです.
まず、例を挙げましょうDataFrame . 便利なPythonパッケージを使っていますFaker テストデータを作成するには最初にそれをインストールする必要がありますpip .
%pip install Faker # notebook
pip install Faker # commmand line
すぐに、Fakerは、ユニットテスト、テストデータベース、または例のテストデータを構築する素晴らしい方法です.それはすべての偽ですが、それは実際の生活の中で発生する可能性がありますデータの組み合わせを生成する規則に基づいていますので、それは個人的に特定されていない本当の探してデータを生成します.
>>> import datetime
>>> import pandas as pd
>>> import numpy as np
>>> from faker import Faker
>>> fake = Faker()
>>> df = pd.DataFrame([
            [fake.first_name(),
             fake.last_name(),
             fake.date_of_birth(),
             fake.date_this_year(),
             fake.city(),
             fake.state_abbr(),
             fake.postalcode()]
                for _ in range(20)],
            columns = ['first_name', 'last_name', 'dob', 'lastupdate', 'city', 'state', 'zip'])

>>> df.head(3)
  first_name last_name dob        lastupdate city         state zip
0 Evan       Daniels   1943-05-27 2021-01-11 North Erin   AZ 27597
1 Christine  Herrera   2019-04-11 2021-01-29 Ellenview    AL 28989
2 Michelle   Warren    2015-05-29 2021-01-11 Mcknighttown VA 55551

どのように我々は再びデータを設定しますか?
まず、データを設定できる方法を確認しましょうDataFrame , 利用loc or iloc インデックス.これらはそれぞれラベルベースまたは整数オフセットベースのインデックスです.(参照)this article 二つの方法の詳細については
インデクサーの最初の引数は行、2番目は列(または列)です.そして、この式に代入すると、基になるDataFrame .
ここでのインデックスはRangeIndex , ラベルは数字です.そのため、私はINT値をloc , これは相対インデックスではなくラベルで調べています.
>>> df.head(1)['zip']
0 27597
Name: zip, dtype: object
>>> df.loc[0, 'zip'] = '60601'
>>> df.head(1)['zip']
0 60601
Name: zip, dtype: object
>>> df.loc[0, ['city', 'state']] = ['Chicago', 'IL']
>>> df.head(1)
  first_name last_name dob lastupdate city state zip
0 Evan Daniels 1943-05-27 2021-01-11 Chicago IL 60601
>>> # Here's an example of an iloc update.
>>> df.iloc[0, 0] = 'Josh'
>>> df.head(1)
  first_name last_name dob        lastupdate city    state zip
0 Josh        Daniels  1943-05-27 2021-01-11 Chicago IL    60601
今、あなたはまた、配列のインデックスの演算子を使用して更新を行うことができますが、これは非常に混乱を見ることができるので、それを覚えているDataFrame , あなたが最初に列を選択している.私は、単独でこの理由のためにこれをしないことを勧めます、しかし、あなたがすぐにわかるように、起こることができる他の問題があります.
>>> df["first_name"][0] = 'Joshy'
>>> df.head(1)
  first_name last_name dob        lastupdate city    state zip
0 Joshy      Daniels   1943-05-27 2021-01-11 Chicago IL    60601

この警告はいつですか.
を、今我々は更新しているDataFrame 首尾よく、それは物事がどこに行くことができるかの例を見る時間です.私にとっては、オリジナルのデータのサブセットを選択するのが非常に一般的です.例えば、2000年以前に人が生まれたデータだけで仕事をすると決めましょう.
>>> dob_limit = datetime.date(2000, 1, 1)
>>> sub = df[df['dob'] < dob_limit]
>>> sub.shape
(16, 7)
>>> idx = sub.head(1).index[0] # save the location for update attempts below
>>> sub.head(1)
  first_name last_name dob        lastupdate city    state zip
0 Joshy      Daniels   1943-05-27 2021-01-11 Chicago IL    60601
更新しましょうlastupdate カラム.
>>> sub.loc[idx, 'lastupdate'] = datetime.date.today()
/Users/mcw/.pyenv/versions/3.8.6/envs/pandas/lib/python3.8/site-packages/pandas/core/indexing.py:670: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  iloc._setitem_with_indexer(indexer, value)
<ipython-input-14-5f1769c87aaf>:1: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  sub.loc[idx, 'lastupdate'] = datetime.date.today()
ブーム!そこでは、私たちは、Aからスライスのコピーに値を設定しようとしていると言われますDataFrame . ここで何が起こったのか.まあ.sub が更新されましたがdf 私たちが警告をしていたとしても.
>>> sub.loc[idx, 'lastupdate']
datetime.date(2021, 2, 4)
>>> df.loc[idx, 'lastupdate']
datetime.date(2021, 1, 11)
パンダは、あなたが予想したことをしなかったかもしれないとあなたに警告しています.ときに作成sub , あなたは2007年にデータのコピーで終わりましたdf . 値を更新すると、オリジナルではなくコピーを更新するだけであることを警告します.

それで、あなたはそれを修理しなければなりませんか?
これに対処する2つの主要な方法があります、そして、あなたが選ぶものはあなたがあなたのコードで達成しようとしているものに依存します.警告は、道路を混乱またはエラーを引き起こす可能性のあるパスを選択し、データを更新するための最良のプラクティスを使用するように指示していることを伝えている.

オリジナルを更新
あなたの意図は、元のデータを更新する場合は、単に直接更新する必要があります.だからあなたのアップデートを行う代わりにsub , ドゥーイットオンdf 代わりに.
>>> df.loc[idx, 'lastupdate'] = datetime.date.today()
>>> df.loc[idx, 'lastupdate']
datetime.date(2021, 2, 4)
これを行うとき、あなたのビューがコピーであるので、それは更新されません.あなたが両方を望むならばsub and df 一致するには、両方の更新または再作成する必要がありますsub 更新後.このため、それを一時停止し、あなたが更新する任意の時間を考えることが重要ですDataFrame . 今すぐ更新する必要があるこのデータのビューを作成しましたか?

コピーを更新
あなたの目標は、データのコピーを更新する場合は、警告を排除するには、常にそのコピーをするビューをしたいパンダを教えてください.
>>> sub2 = df[df['dob'] < dob_limit].copy()
>>> sub2.loc[idx, 'lastupdate'] = datetime.date.today()
>>> sub2.loc[idx, 'lastupdate']
datetime.date(2021, 2, 4)

間に
発生する1つの一般的な状況はDataFrame データをフィルタリングすることによって、はるかに小さい方に絞り込まれます.たぶん、新しい列がいくつかの計算の一部として追加され、最終的な結果として、元のDataFrame 更新する必要があります.それをする一つの方法は、あなたを助けるためにインデックスを使うことです.
>>> sub3 = df[df['dob'] < dob_limit].copy() # we'll be updating this DataFrame
>>> sub3['manualupdate'] = datetime.date.today() - datetime.timedelta(days=10) # you can modify this DataFrame
>>> sub3 = sub3.head(3) # or even make it smaller
>>> sub3['manualupdate']
0 2021-01-25
1 2021-01-25
3 2021-01-25
Name: manualupdate, dtype: object
今、我々はその事実を使用しますsub3 インデックスをオリジナルで共有するdf データを更新するために使用します.列のすべてのマッチング行を更新できますlastupdate 例えば.
>>> df.loc[sub3.index, 'lastupdate'] = sub3['manualupdate']
>>> df.loc[sub3.index]
  first_name last_name dob        lastupdate city         state zip
0 Joshy      Daniels   1943-05-27 2021-01-25 Chicago      IL 60601
3 Vernon     Hernandez 1989-04-10 2021-01-25 South Mark   NE 05048
4 Mary       Munoz     1933-03-16 2021-01-25 Ewingborough OK 31127
さて、これらの行がデータの小さなサブセットから更新されたことがわかります.

列のサブセット
また、この警告に遭遇する可能性がありますDataFrame .
>>> df_d = df[['zip']]
>>> df_d.loc[idx, 'zip'] = "00313" # SettingWithCopyWarning
ここで警告を抑制する大きな方法は、完全なスライスを行うことですloc あなたの初期の選択で.また、使用することができますcopy .
>>> df_d = df.loc[:, ['zip']]
>>> df_d.loc[idx, 'zip'] = "00313"

完全性のために
今、あなたは多くのこの警告について読むことができますother places , そして、あなたが検索エンジンを通してここに来たならば、多分、あなたは多分彼らを混乱させるか、直接あなたの状況に適用できないとわかりました.私はいつもこのエラーを見ている状況を示すために、少し違ったアプローチをとった.しかし、より一般的な理由は、新しいパンダのユーザーがこのエラーに遭遇するときに更新しようとしているDataFrame 配列インデックス演算子の使用[] ).
>>> df[df['dob'] < dob_limit]['lastupdate'] = datetime.date.today()
file.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
  df[df['dob'] < dob_limit]['lastupdate'] = datetime.date.today()
ここでの修正はとても簡単ですloc . 試してみましょう.
>>> df.loc[df['dob'] < dob_limit, 'lastupdate'] = datetime.date.today() - datetime.timedelta(days=1)
>>> df.loc[df['dob'] < dob_limit].head(1)
  first_name last_name dob lastupdate city state zip
0 Joshy Daniels 1943-05-27 2021-02-03 Chicago IL 60601
その仕事.ここでの警告は、我々の最初のアップデートが我々のオリジナルデータのコピーに関して(潜在的に)動くと我々に話していました.私は、Pandasが時々コピーを返すのを選ぶいくつかの複雑な理由を持っているので、これが私たちのオープンケースと全く同じくらい明白であると思いません、そして、時々、元のデータに視野を返してください、そして、アップデートが1行にあるとき、これは明らかでないかもしれません.これが起こっていることを検出すると、この警告が発生します.
これを連鎖割り当てと呼ぶ.警告の上の課題は、実際にこれを行うことです.
df. __getitem__ (df. __getitem__ ('dob') < dob_limit). __setitem__ ('lastupdate', datetime.date.today())
配列インデックス演算子を使用する場合__getitem__ and __setitem__ メソッドは、それぞれ取得および設定のために呼び出されます.その最初の関数呼び出し__getitem__ はデータのコピーを返して、それからデータを設定しようとして、警告を引き起こします.
我々が使うならばloc , しかし、それは一時的な見解を返さずにこれをするでしょう.
df.loc. __setitem__ ((df. __getitem__ ('dob') < dob_limit, 'lastupdate'), datetime.date.today())
それで、あなたがこの警告を見るときはいつも、あなたのコードを見て、2つのものをチェックしてください.データを更新しようとしましたか[] ? もしそうならばloc (or iloc ). あなたがそれをしているならばDataFrame 別のDataFrame . あなたがそれを更新するか、あなたのオリジナルを更新するために植物を植えるならばDataFrame 代わりに.
郵便Views, Copies, and that annoying SettingWithCopyWarning 最初に現れたwrighters.io .