Bagaimana cara menangani SettingWithCopyWarning di Pandas?

629

Latar Belakang

Saya baru saja memutakhirkan Panda saya dari 0,11 ke 0,13.0rc1. Sekarang, aplikasi tersebut mengeluarkan banyak peringatan baru. Salah satunya seperti ini:

E:\FinReporter\FM_EXT.py:449: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_index,col_indexer] = value instead
  quote_df['TVol']   = quote_df['TVol']/TVOL_SCALE

Saya ingin tahu apa artinya sebenarnya? Apakah saya perlu mengubah sesuatu?

Bagaimana saya harus menangguhkan peringatan jika saya bersikeras untuk menggunakan quote_df['TVol'] = quote_df['TVol']/TVOL_SCALE?

Fungsi yang memberikan kesalahan

def _decode_stock_quote(list_of_150_stk_str):
    """decode the webpage and return dataframe"""

    from cStringIO import StringIO

    str_of_all = "".join(list_of_150_stk_str)

    quote_df = pd.read_csv(StringIO(str_of_all), sep=',', names=list('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefg')) #dtype={'A': object, 'B': object, 'C': np.float64}
    quote_df.rename(columns={'A':'STK', 'B':'TOpen', 'C':'TPCLOSE', 'D':'TPrice', 'E':'THigh', 'F':'TLow', 'I':'TVol', 'J':'TAmt', 'e':'TDate', 'f':'TTime'}, inplace=True)
    quote_df = quote_df.ix[:,[0,3,2,1,4,5,8,9,30,31]]
    quote_df['TClose'] = quote_df['TPrice']
    quote_df['RT']     = 100 * (quote_df['TPrice']/quote_df['TPCLOSE'] - 1)
    quote_df['TVol']   = quote_df['TVol']/TVOL_SCALE
    quote_df['TAmt']   = quote_df['TAmt']/TAMT_SCALE
    quote_df['STK_ID'] = quote_df['STK'].str.slice(13,19)
    quote_df['STK_Name'] = quote_df['STK'].str.slice(21,30)#.decode('gb2312')
    quote_df['TDate']  = quote_df.TDate.map(lambda x: x[0:4]+x[5:7]+x[8:10])

    return quote_df

Lebih banyak pesan kesalahan

E:\FinReporter\FM_EXT.py:449: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_index,col_indexer] = value instead
  quote_df['TVol']   = quote_df['TVol']/TVOL_SCALE
E:\FinReporter\FM_EXT.py:450: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_index,col_indexer] = value instead
  quote_df['TAmt']   = quote_df['TAmt']/TAMT_SCALE
E:\FinReporter\FM_EXT.py:453: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_index,col_indexer] = value instead
  quote_df['TDate']  = quote_df.TDate.map(lambda x: x[0:4]+x[5:7]+x[8:10])
bigbug
sumber
2
Berikut ini adalah manajer konteks untuk mengatur sementara tingkat peringatan gist.github.com/notbanker/2be3ed34539c86e22ffdd88fd95ad8bc
Peter Cotton
2
Anda dapat menggunakan df.set_value, dokumentasi di sini - pandas.pydata.org/pandas-docs/stable/generated/…
leonprou
1
pandas.pydata.org/pandas-docs/stable/… dokumen resmi menjelaskan secara rinci
wyx
3
@leonprou df.set_valuetelah ditinggalkan. Panda sekarang merekomendasikan untuk digunakan .at[]atau .iat[]sebagai gantinya. docs here pandas.pydata.org/pandas-docs/stable/generated/…
Kyle C
Saya terkejut tidak ada yang menyebutkan panda di option_contextsini: pandas.pydata.org/pandas-docs/stable/user_guide/options.html , gunakan sebagaiwith pd.option_context("mode.chained_assignment", None): [...]
m-dz

Jawaban:

795

Itu SettingWithCopyWarningdibuat untuk menandai tugas yang berpotensi membingungkan "dirantai", seperti berikut ini, yang tidak selalu berfungsi seperti yang diharapkan, terutama ketika pilihan pertama mengembalikan salinan . [lihat GH5390 dan GH5597 untuk diskusi latar belakang.]

df[df['A'] > 2]['B'] = new_val  # new_val not set in df

Peringatan itu menawarkan saran untuk menulis ulang sebagai berikut:

df.loc[df['A'] > 2, 'B'] = new_val

Namun, ini tidak sesuai dengan penggunaan Anda, yang setara dengan:

df = df[df['A'] > 2]
df['B'] = new_val

Meskipun jelas bahwa Anda tidak peduli tentang penulisan yang membuatnya kembali ke bingkai asli (karena Anda menimpa referensi untuk itu), sayangnya pola ini tidak dapat dibedakan dari contoh tugas berantai pertama. Karena itu peringatan (false positive). Potensi untuk false positive dibahas dalam dokumen pengindeksan , jika Anda ingin membaca lebih lanjut. Anda dapat dengan aman menonaktifkan peringatan baru ini dengan tugas berikut.

import pandas as pd
pd.options.mode.chained_assignment = None  # default='warn'
Garrett
sumber
34
Saya pikir saya sebagian besar akan mendukung tidak memperingatkan tentang ini sama sekali. Jika Anda bekerja dengan sintaks penugasan berantai, Anda pasti dapat mengetahui urutan pengindeksan yang perlu terjadi agar itu berfungsi seperti yang diharapkan dalam situasi apa pun. Saya pikir itu terlalu paranoid bahwa ada tindakan pencegahan menyeluruh tentang hal ini. Dengan semangat yang sama seperti "membiarkan semua orang dewasa" tentang metode atau atribut kelas 'pribadi', saya pikir lebih baik bagi panda untuk membiarkan pengguna menjadi dewasa tentang tugas yang dirantai. Hanya gunakan jika Anda tahu apa yang Anda lakukan.
ely
48
Agak tidak khas untuk mencoba memperingatkan orang-orang ketika mereka mencari-cari alternatif. Metode Pandas gaya baru untuk akses (ditingkatkan .ix, ditingkatkan .iloc, dll.) Pasti dapat dilihat sebagai "cara utama" tanpa memperingatkan semua orang tanpa henti tentang cara lain. Alih-alih biarkan mereka menjadi orang dewasa dan jika mereka ingin melakukan tugas dirantai, maka jadilah itu. Lagi pula, dua sen saya. Seseorang melihat komentar yang tidak puas dari para devs Pandas di sini sering ketika tugas dirantai akan bekerja untuk memecahkan masalah, tetapi tidak akan dianggap sebagai cara "utama" untuk melakukannya.
ely
8
@ EMS masalahnya adalah tidak selalu jelas dari kode tempat salinan vs. tampilan dibuat dan sejumlah bug / kebingungan muncul dari masalah ini. Kami mempertimbangkan untuk memasukkan file rc / opsi untuk melakukan konfigurasi secara otomatis, yang mungkin lebih berguna mengingat bagaimana pengaturan dengan peringatan salinan berfungsi.
Jeff Tratner
3
Alasan untuk memperingatkan adalah untuk orang yang memperbarui kode lama, tentu saja. Dan saya pasti butuh peringatan, karena saya berurusan dengan beberapa kode lama yang sangat jelek.
Thomas Andrews
16
Di samping catatan, saya menemukan bahwa menonaktifkan peringatan chained_assignment: pd.options.mode.chained_assignment = Nonetelah menghasilkan kode saya berjalan sekitar 6 kali lebih cepat. Adakah yang mengalami hasil serupa?
Muon
209

Bagaimana cara berurusan SettingWithCopyWarningdi Panda?

Posting ini dimaksudkan untuk pembaca yang,

  1. Ingin mengerti apa artinya peringatan ini
  2. Ingin memahami berbagai cara untuk menekan peringatan ini
  3. Ingin memahami cara meningkatkan kode mereka dan mengikuti praktik yang baik untuk menghindari peringatan ini di masa mendatang.

Mendirikan

np.random.seed(0)
df = pd.DataFrame(np.random.choice(10, (3, 5)), columns=list('ABCDE'))
df
   A  B  C  D  E
0  5  0  3  3  7
1  9  3  5  2  4
2  7  6  8  8  1

Apa itu SettingWithCopyWarning?

Untuk mengetahui bagaimana menangani peringatan ini, penting untuk memahami apa artinya dan mengapa hal itu diangkat.

Saat memfilter DataFrames, dimungkinkan mengiris / mengindeks bingkai untuk mengembalikan tampilan , atau salinan , tergantung pada tata letak internal dan berbagai detail implementasi. "Tampilan" adalah, seperti istilah yang disarankan, tampilan ke data asli, sehingga memodifikasi tampilan dapat mengubah objek asli. Di sisi lain, "salinan" adalah replikasi data dari aslinya, dan memodifikasi salinan tidak berpengaruh pada aslinya.

Seperti disebutkan oleh jawaban lain, SettingWithCopyWarningitu dibuat untuk menandai operasi "tugas dirantai". Pertimbangkan dfdalam pengaturan di atas. Misalkan Anda ingin memilih semua nilai dalam kolom "B" di mana nilai dalam kolom "A" adalah> 5. Panda memungkinkan Anda untuk melakukan ini dengan cara yang berbeda, beberapa lebih benar daripada yang lain. Sebagai contoh,

df[df.A > 5]['B']

1    3
2    6
Name: B, dtype: int64

Dan,

df.loc[df.A > 5, 'B']

1    3
2    6
Name: B, dtype: int64

Ini mengembalikan hasil yang sama, jadi jika Anda hanya membaca nilai-nilai ini, tidak ada bedanya. Jadi, apa masalahnya? Masalah dengan tugas berantai, adalah bahwa umumnya sulit untuk memprediksi apakah tampilan atau salinan dikembalikan, jadi ini sebagian besar menjadi masalah ketika Anda mencoba untuk menetapkan nilai kembali. Untuk membangun contoh sebelumnya, pertimbangkan bagaimana kode ini dijalankan oleh penerjemah:

df.loc[df.A > 5, 'B'] = 4
# becomes
df.__setitem__((df.A > 5, 'B'), 4)

Dengan satu __setitem__panggilan ke df. OTOH, pertimbangkan kode ini:

df[df.A > 5]['B'] = 4
# becomes
df.__getitem__(df.A > 5).__setitem__('B", 4)

Sekarang, tergantung pada apakah __getitem__mengembalikan tampilan atau salinan, __setitem__operasi mungkin tidak berfungsi .

Secara umum, Anda harus menggunakan locuntuk penugasan berbasis-label, dan ilocuntuk penugasan berbasis integer / positional, karena spec menjamin bahwa mereka selalu beroperasi pada yang asli. Selain itu, untuk mengatur sel tunggal, Anda harus menggunakan atdan iat.

Lebih banyak dapat ditemukan dalam dokumentasi .

Catatan
Semua operasi pengindeksan boolean yang dilakukan dengan locjuga dapat dilakukan dengan iloc. Satu-satunya perbedaan adalah bahwa ilocmengharapkan bilangan bulat / posisi untuk indeks atau array numpy nilai boolean, dan indeks bilangan bulat / posisi untuk kolom.

Sebagai contoh,

df.loc[df.A > 5, 'B'] = 4

Bisa ditulis nas

df.iloc[(df.A > 5).values, 1] = 4

Dan,

df.loc[1, 'A'] = 100

Dapat ditulis sebagai

df.iloc[1, 0] = 100

Dan seterusnya.


Katakan saja padaku bagaimana menekan peringatan itu!

Pertimbangkan operasi sederhana pada kolom "A" di df. Memilih "A" dan membaginya dengan 2 akan memunculkan peringatan, tetapi operasi akan berhasil.

df2 = df[['A']]
df2['A'] /= 2
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/IPython/__main__.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

df2
     A
0  2.5
1  4.5
2  3.5

Ada beberapa cara untuk langsung membungkam peringatan ini:

  1. Membuat deepcopy

    df2 = df[['A']].copy(deep=True)
    df2['A'] /= 2
    
  2. Gantipd.options.mode.chained_assignment
    Dapat diatur untuk None, "warn", atau "raise". "warn"adalah standarnya. Noneakan sepenuhnya menekan peringatan, dan "raise"akan melempar SettingWithCopyError, mencegah operasi lewat.

    pd.options.mode.chained_assignment = None
    df2['A'] /= 2
    

@Peter Cotton dalam komentar, muncul dengan cara yang baik untuk tidak mengubah mode secara intrusi (dimodifikasi dari intisari ini ) menggunakan manajer konteks, untuk mengatur mode hanya selama diperlukan, dan mengatur ulang kembali ke keadaan asli saat selesai.

class ChainedAssignent:
    def __init__(self, chained=None):
        acceptable = [None, 'warn', 'raise']
        assert chained in acceptable, "chained must be in " + str(acceptable)
        self.swcw = chained

    def __enter__(self):
        self.saved_swcw = pd.options.mode.chained_assignment
        pd.options.mode.chained_assignment = self.swcw
        return self

    def __exit__(self, *args):
        pd.options.mode.chained_assignment = self.saved_swcw

Penggunaannya adalah sebagai berikut:

# some code here
with ChainedAssignent():
    df2['A'] /= 2
# more code follows

Atau, untuk meningkatkan pengecualian

with ChainedAssignent(chained='raise'):
    df2['A'] /= 2

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

"Masalah XY": Apa yang saya lakukan salah?

Sering kali, pengguna berusaha mencari cara untuk menekan pengecualian ini tanpa sepenuhnya memahami mengapa hal itu muncul sejak awal. Ini adalah contoh yang baik dari masalah XY , di mana pengguna berusaha memecahkan masalah "Y" yang sebenarnya merupakan gejala dari masalah yang berakar lebih dalam "X". Pertanyaan akan diajukan berdasarkan masalah umum yang menemui peringatan ini, dan solusi kemudian akan disajikan.

Pertanyaan 1
Saya punya DataFrame

df
       A  B  C  D  E
    0  5  0  3  3  7
    1  9  3  5  2  4
    2  7  6  8  8  1

Saya ingin menetapkan nilai dalam col "A"> 5 hingga 1000. Output yang saya harapkan adalah

      A  B  C  D  E
0     5  0  3  3  7
1  1000  3  5  2  4
2  1000  6  8  8  1

Cara yang salah untuk melakukan ini:

df.A[df.A > 5] = 1000         # works, because df.A returns a view
df[df.A > 5]['A'] = 1000      # does not work
df.loc[df.A  5]['A'] = 1000   # does not work

Cara yang benar menggunakan loc:

df.loc[df.A > 5, 'A'] = 1000


Pertanyaan 2 1
Saya mencoba mengatur nilai dalam sel (1, 'D') ke 12345. Output yang saya harapkan adalah

   A  B  C      D  E
0  5  0  3      3  7
1  9  3  5  12345  4
2  7  6  8      8  1

Saya telah mencoba berbagai cara untuk mengakses sel ini, seperti df['D'][1]. Apa cara terbaik untuk melakukan ini?

1. Pertanyaan ini tidak secara khusus terkait dengan peringatan, tetapi baik untuk memahami bagaimana melakukan operasi khusus ini dengan benar untuk menghindari situasi di mana peringatan tersebut berpotensi muncul di masa depan.

Anda dapat menggunakan salah satu metode berikut untuk melakukan ini.

df.loc[1, 'D'] = 12345
df.iloc[1, 3] = 12345
df.at[1, 'D'] = 12345
df.iat[1, 3] = 12345


Pertanyaan 3
Saya mencoba untuk mengelompokkan nilai berdasarkan beberapa kondisi. Saya memiliki DataFrame

   A  B  C  D  E
1  9  3  5  2  4
2  7  6  8  8  1

Saya ingin menetapkan nilai dalam "D" ke 123 sedemikian rupa sehingga "C" == 5. Saya mencoba

df2.loc[df2.C == 5, 'D'] = 123

Yang sepertinya baik-baik saja tetapi saya masih mendapatkan SettingWithCopyWarning! Bagaimana cara saya memperbaikinya?

Ini sebenarnya mungkin karena kode lebih tinggi di dalam pipa Anda. Apakah Anda membuat df2dari sesuatu yang lebih besar, seperti

df2 = df[df.A > 5]

? Dalam hal ini, pengindeksan boolean akan mengembalikan tampilan, sehingga df2akan referensi aslinya. Apa yang Anda akan perlu lakukan adalah menetapkan df2untuk copy :

df2 = df[df.A > 5].copy()
# Or,
# df2 = df.loc[df.A > 5, :]


Pertanyaan 4
Saya mencoba untuk menjatuhkan kolom "C" di tempat

   A  B  C  D  E
1  9  3  5  2  4
2  7  6  8  8  1

Tapi menggunakan

df2.drop('C', axis=1, inplace=True)

Melempar SettingWithCopyWarning. Mengapa ini terjadi?

Ini karena df2harus dibuat sebagai tampilan dari beberapa operasi pemotongan lain, seperti

df2 = df[df.A > 5]

Solusi di sini adalah baik membuat copy()dari df, atau penggunaan loc, seperti sebelumnya.

cs95
sumber
7
PS: Beri tahu saya kalau situasimu tidak tercakup dalam daftar pertanyaan bagian 3. Saya akan mengubah posting saya.
cs95
150

Secara umum intinya SettingWithCopyWarningadalah untuk menunjukkan kepada pengguna (dan terutama pengguna baru) bahwa mereka dapat beroperasi pada salinan dan bukan yang asli seperti yang mereka pikirkan. Ada yang positif palsu (TKI jika Anda tahu apa yang Anda lakukan bisa ok ). Salah satu kemungkinan adalah hanya untuk mematikan (secara default memperingatkan ) peringatan sebagai @Garrett menyarankan.

Berikut ini pilihan lain:

In [1]: df = DataFrame(np.random.randn(5, 2), columns=list('AB'))

In [2]: dfa = df.ix[:, [1, 0]]

In [3]: dfa.is_copy
Out[3]: True

In [4]: dfa['A'] /= 2
/usr/local/bin/ipython:1: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_index,col_indexer] = value instead
  #!/usr/local/bin/python

Anda dapat mengatur is_copytanda untuk False, yang secara efektif akan mematikan tanda centang, untuk objek itu :

In [5]: dfa.is_copy = False

In [6]: dfa['A'] /= 2

Jika Anda menyalin secara eksplisit maka tidak ada peringatan lebih lanjut yang akan terjadi:

In [7]: dfa = df.ix[:, [1, 0]].copy()

In [8]: dfa['A'] /= 2

Kode yang ditunjukkan OP di atas, walaupun sah, dan mungkin sesuatu yang saya lakukan juga, secara teknis merupakan kasus untuk peringatan ini, dan bukan positif palsu. Cara lain untuk tidak memiliki peringatan adalah dengan melakukan operasi seleksi via reindex, mis

quote_df = quote_df.reindex(columns=['STK', ...])

Atau,

quote_df = quote_df.reindex(['STK', ...], axis=1)  # v.0.21
Jeff
sumber
Terima kasih atas informasinya & diskusi, saya matikan saja peringatan untuk membiarkan konsol diam tentang ini. Kedengarannya seperti tampilan & tabel dalam database SQL. Saya perlu tahu lebih banyak tentang manfaat pengenalan konsep 'copy', tapi IMHO itu agak membebani untuk mengurus perbedaan sintaksis semantik 、 semantik ..
bigbug
19
Saya setuju dengan salinannya (); sudah jelas dan itu memperbaiki masalah saya (yang positif palsu).
rdchambers
5
setelah pembaruan 0.16saya melihat lebih banyak positif palsu, masalah dengan false positive adalah orang belajar untuk mengabaikannya, meskipun terkadang itu sah.
dashesy
3
@ bulu mata Anda kehilangan intinya. kadang-kadang bahkan mungkin sebagian besar waktu mungkin berhasil. Tapi itu bisa terjadi misalnya jika bingkai lebih besar / lebih kecil atau Anda menambahkan kolom katakanlah dtype yang berbeda bahwa itu tidak berfungsi. Itulah intinya. Anda melakukan sesuatu yang mungkin berhasil tetapi tidak dijamin. Ini sangat berbeda dari peringatan penghentian. Jika Anda ingin terus menggunakannya dan berfungsi, bagus. Tapi waspadalah.
Jeff
3
@ Jeff masuk akal sekarang, jadi itu adalah undefinedperilaku. Jika ada kesalahan maka itu harus (kemudian untuk menghindari jebakan dilihat C), karena apidibekukan perilaku peringatan saat ini masuk akal untuk kompatibilitas mundur. Dan saya akan membuat mereka membuang untuk menangkap mereka sebagai kesalahan dalam kode produksi saya ( warnings.filterwarnings('error', r'SettingWithCopyWarning). Juga saran untuk menggunakan .lockadang-kadang juga tidak membantu (jika ada dalam kelompok).
dashesy
41

Peringatan salinan bingkai data Pandas

Ketika Anda pergi dan melakukan sesuatu seperti ini:

quote_df = quote_df.ix[:,[0,3,2,1,4,5,8,9,30,31]]

pandas.ix dalam hal ini mengembalikan bingkai data baru yang berdiri sendiri.

Nilai apa pun yang Anda putuskan untuk diubah dalam kerangka data ini, tidak akan mengubah kerangka data asli.

Inilah yang mencoba memperingatkan panda tentang Anda.


Mengapa .ixitu ide yang buruk

The .ixobjek mencoba untuk melakukan lebih dari satu hal, dan untuk siapa saja yang telah membaca apa-apa tentang kode yang bersih, ini adalah bau yang kuat.

Dengan kerangka data ini:

df = pd.DataFrame({"a": [1,2,3,4], "b": [1,1,2,2]})

Dua perilaku:

dfcopy = df.ix[:,["a"]]
dfcopy.a.ix[0] = 2

Perilaku satu: dfcopysekarang merupakan kerangka data yang berdiri sendiri. Mengubahnya tidak akan berubahdf

df.ix[0, "a"] = 3

Perilaku dua: Ini mengubah kerangka data asli.


Gunakan .locsebagai gantinya

Para pengembang panda menyadari bahwa .ixobjek itu sangat berbau [spekulatif] dan dengan demikian menciptakan dua objek baru yang membantu dalam aksesi dan penugasan data. (Yang lainnya .iloc)

.loc lebih cepat, karena tidak mencoba membuat salinan data.

.loc dimaksudkan untuk memodifikasi kerangka data yang ada di tempat Anda, yang lebih hemat memori.

.loc dapat diprediksi, ia memiliki satu perilaku.


Solusinya

Apa yang Anda lakukan dalam contoh kode Anda adalah memuat file besar dengan banyak kolom, kemudian memodifikasinya menjadi lebih kecil.

The pd.read_csvFungsi dapat membantu Anda keluar dengan banyak ini dan juga membuat pemuatan file jauh lebih cepat.

Jadi, bukannya melakukan ini

quote_df = pd.read_csv(StringIO(str_of_all), sep=',', names=list('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefg')) #dtype={'A': object, 'B': object, 'C': np.float64}
quote_df.rename(columns={'A':'STK', 'B':'TOpen', 'C':'TPCLOSE', 'D':'TPrice', 'E':'THigh', 'F':'TLow', 'I':'TVol', 'J':'TAmt', 'e':'TDate', 'f':'TTime'}, inplace=True)
quote_df = quote_df.ix[:,[0,3,2,1,4,5,8,9,30,31]]

Melakukan hal ini

columns = ['STK', 'TPrice', 'TPCLOSE', 'TOpen', 'THigh', 'TLow', 'TVol', 'TAmt', 'TDate', 'TTime']
df = pd.read_csv(StringIO(str_of_all), sep=',', usecols=[0,3,2,1,4,5,8,9,30,31])
df.columns = columns

Ini hanya akan membaca kolom yang Anda minati, dan memberi nama dengan benar. Tidak perlu menggunakan .ixobjek jahat untuk melakukan hal-hal ajaib.

firelynx
sumber
"Para pengembang panda menyadari bahwa objek .ix cukup bau [secara spekulatif] dan karenanya menciptakan dua objek baru" - apa yang lainnya?
jf328
3
@ jf328 .iloc Saya pikir
Brian Bien
1
Ya, benar .iloc. Ini adalah dua metode utama untuk mengindeks struktur data panda. Baca lebih lanjut di dokumentasi.
Ninjakannon
Bagaimana cara mengganti kolom DataFrame dengan stempel waktu ke dalam kolom dengan objek atau string waktu tanggal?
boldnik
@boldnik Periksa jawaban ini stackoverflow.com/a/37453925/3730397
firelynx
20

Di sini saya menjawab pertanyaan secara langsung. Bagaimana cara mengatasinya?

Buat .copy(deep=False)setelah Anda mengiris. Lihat pandas . DataFrame.copy .

Tunggu, bukankah sepotong mengembalikan salinan? Lagipula, inilah yang ingin disampaikan oleh pesan peringatan? Baca jawaban panjangnya:

import pandas as pd
df = pd.DataFrame({'x':[1,2,3]})

Ini memberi peringatan:

df0 = df[df.x>2]
df0['foo'] = 'bar'

Ini tidak:

df1 = df[df.x>2].copy(deep=False)
df1['foo'] = 'bar'

Kedua df0dan df1yang DataFrameobjek, tetapi sesuatu tentang mereka berbeda yang memungkinkan panda untuk mencetak peringatan. Ayo cari tahu apa itu.

import inspect
slice= df[df.x>2]
slice_copy = df[df.x>2].copy(deep=False)
inspect.getmembers(slice)
inspect.getmembers(slice_copy)

Dengan menggunakan alat pilihan pilihan Anda, Anda akan melihat bahwa di luar beberapa alamat, satu-satunya perbedaan material adalah ini:

|          | slice   | slice_copy |
| _is_copy | weakref | None       |

Metode yang memutuskan apakah akan memperingatkan adalah DataFrame._check_setitem_copyyang memeriksa _is_copy. Jadi ini dia. Buat copyagar DataFrame Anda tidak _is_copy.

Peringatan itu menyarankan untuk digunakan .loc, tetapi jika Anda menggunakan .locpada frame itu _is_copy, Anda akan tetap mendapatkan peringatan yang sama. Menyesatkan? Iya. Mengganggu? Anda bertaruh. Bermanfaat? Berpotensi, ketika tugas dirantai digunakan. Tapi itu tidak bisa dengan benar mendeteksi penugasan berantai dan mencetak peringatan tanpa pandang bulu.

pengguna443854
sumber
11

Topik ini sangat membingungkan dengan Panda. Untungnya, ia memiliki solusi yang relatif sederhana.

Masalahnya adalah bahwa tidak selalu jelas apakah operasi penyaringan data (misalnya loc) mengembalikan salinan atau tampilan DataFrame. Oleh karena itu, penggunaan lebih lanjut dari DataFrame yang disaring tersebut dapat membingungkan.

Solusi sederhananya adalah (kecuali jika Anda perlu bekerja dengan set data yang sangat besar):

Setiap kali Anda perlu memperbarui nilai apa pun, selalu pastikan bahwa Anda secara tidak sah menyalin DataFrame sebelum penugasan.

df  # Some DataFrame
df = df.loc[:, 0:2]  # Some filtering (unsure whether a view or copy is returned)
df = df.copy()  # Ensuring a copy is made
df[df["Name"] == "John"] = "Johny"  # Assignment can be done now (no warning)
Mikulas
sumber
Ada kesalahan ketik: secara implisit harus secara eksplisit
s9527
7

Untuk menghilangkan keraguan, solusi saya adalah membuat salinan slice yang dalam dan bukan salinan biasa. Ini mungkin tidak berlaku tergantung pada konteks Anda (Memori kendala / ukuran irisan, potensi penurunan kinerja - terutama jika salinan terjadi dalam satu lingkaran seperti yang terjadi pada saya, dll ...)

Agar lebih jelas, inilah peringatan yang saya terima:

/opt/anaconda3/lib/python3.6/site-packages/ipykernel/__main__.py:54:
SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame
See the caveats in the documentation:
http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy

Ilustrasi

Saya ragu bahwa peringatan itu dilemparkan karena kolom saya menjatuhkan salinan salinan. Meskipun tidak secara teknis mencoba menetapkan nilai dalam salinan slice, itu masih merupakan modifikasi dari salinan slice. Di bawah ini adalah langkah-langkah (sederhana) yang telah saya ambil untuk mengkonfirmasi kecurigaan itu, saya harap ini akan membantu kita yang mencoba memahami peringatan itu.

Contoh 1: menjatuhkan kolom pada dokumen asli memengaruhi salinan

Kami sudah tahu itu tapi ini pengingat yang sehat. Ini BUKAN tentang peringatan itu.

>> data1 = {'A': [111, 112, 113], 'B':[121, 122, 123]}
>> df1 = pd.DataFrame(data1)
>> df1

    A   B
0   111 121
1   112 122
2   113 123


>> df2 = df1
>> df2

A   B
0   111 121
1   112 122
2   113 123

# Dropping a column on df1 affects df2
>> df1.drop('A', axis=1, inplace=True)
>> df2
    B
0   121
1   122
2   123

Dimungkinkan untuk menghindari perubahan yang dilakukan pada df1 untuk mempengaruhi df2

>> data1 = {'A': [111, 112, 113], 'B':[121, 122, 123]}
>> df1 = pd.DataFrame(data1)
>> df1

A   B
0   111 121
1   112 122
2   113 123

>> import copy
>> df2 = copy.deepcopy(df1)
>> df2
A   B
0   111 121
1   112 122
2   113 123

# Dropping a column on df1 does not affect df2
>> df1.drop('A', axis=1, inplace=True)
>> df2
    A   B
0   111 121
1   112 122
2   113 123

Contoh 2: menjatuhkan kolom pada salinan dapat memengaruhi yang asli

Ini sebenarnya menggambarkan peringatan itu.

>> data1 = {'A': [111, 112, 113], 'B':[121, 122, 123]}
>> df1 = pd.DataFrame(data1)
>> df1

    A   B
0   111 121
1   112 122
2   113 123

>> df2 = df1
>> df2

    A   B
0   111 121
1   112 122
2   113 123

# Dropping a column on df2 can affect df1
# No slice involved here, but I believe the principle remains the same?
# Let me know if not
>> df2.drop('A', axis=1, inplace=True)
>> df1

B
0   121
1   122
2   123

Dimungkinkan untuk menghindari perubahan yang dilakukan pada df2 untuk mempengaruhi df1

>> data1 = {'A': [111, 112, 113], 'B':[121, 122, 123]}
>> df1 = pd.DataFrame(data1)
>> df1

    A   B
0   111 121
1   112 122
2   113 123

>> import copy
>> df2 = copy.deepcopy(df1)
>> df2

A   B
0   111 121
1   112 122
2   113 123

>> df2.drop('A', axis=1, inplace=True)
>> df1

A   B
0   111 121
1   112 122
2   113 123

Bersulang!

Raphvanns
sumber
4

Ini seharusnya bekerja:

quote_df.loc[:,'TVol'] = quote_df['TVol']/TVOL_SCALE
jrouquie
sumber
4

Beberapa mungkin hanya ingin menekan peringatan:

class SupressSettingWithCopyWarning:
    def __enter__(self):
        pd.options.mode.chained_assignment = None

    def __exit__(self, *args):
        pd.options.mode.chained_assignment = 'warn'

with SupressSettingWithCopyWarning():
    #code that produces warning
delica
sumber
3

Jika Anda telah menetapkan slice ke variabel dan ingin mengatur menggunakan variabel seperti berikut ini:

df2 = df[df['A'] > 2]
df2['B'] = value

Dan Anda tidak ingin menggunakan solusi Jeffs karena kondisi komputer Anda terlalu df2lama atau karena alasan lain, maka Anda dapat menggunakan yang berikut:

df.loc[df2.index.tolist(), 'B'] = value

df2.index.tolist() mengembalikan indeks dari semua entri dalam df2, yang kemudian akan digunakan untuk mengatur kolom B dalam kerangka data asli.

Steohan
sumber
ini 9 kali lebih mahal daripada df ["B"] = nilai
Claudiu Creanga
Bisakah Anda jelaskan ini lebih dalam @ClaudiuCreanga?
gies0r
2

Bagi saya masalah ini terjadi pada <contoh berikut yang disederhanakan. Dan saya juga bisa menyelesaikannya (semoga dengan solusi yang benar):

kode lama dengan peringatan:

def update_old_dataframe(old_dataframe, new_dataframe):
    for new_index, new_row in new_dataframe.iterrorws():
        old_dataframe.loc[new_index] = update_row(old_dataframe.loc[new_index], new_row)

def update_row(old_row, new_row):
    for field in [list_of_columns]:
        # line with warning because of chain indexing old_dataframe[new_index][field]
        old_row[field] = new_row[field]  
    return old_row

Ini dicetak peringatan untuk garis old_row[field] = new_row[field]

Karena baris dalam metode update_row sebenarnya adalah tipe Series, saya mengganti baris dengan:

old_row.at[field] = new_row.at[field]

yaitu metode untuk mengakses / mencari Series. Walaupun keduanya bekerja dengan baik dan hasilnya sama, dengan cara ini saya tidak harus menonaktifkan peringatan (= simpan untuk masalah pengindeksan berantai lainnya di tempat lain).

Saya harap ini dapat membantu seseorang.

Petr Szturc
sumber
2

Anda dapat menghindari seluruh masalah seperti ini, saya percaya:

return (
    pd.read_csv(StringIO(str_of_all), sep=',', names=list('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefg')) #dtype={'A': object, 'B': object, 'C': np.float64}
    .rename(columns={'A':'STK', 'B':'TOpen', 'C':'TPCLOSE', 'D':'TPrice', 'E':'THigh', 'F':'TLow', 'I':'TVol', 'J':'TAmt', 'e':'TDate', 'f':'TTime'}, inplace=True)
    .ix[:,[0,3,2,1,4,5,8,9,30,31]]
    .assign(
        TClose=lambda df: df['TPrice'],
        RT=lambda df: 100 * (df['TPrice']/quote_df['TPCLOSE'] - 1),
        TVol=lambda df: df['TVol']/TVOL_SCALE,
        TAmt=lambda df: df['TAmt']/TAMT_SCALE,
        STK_ID=lambda df: df['STK'].str.slice(13,19),
        STK_Name=lambda df: df['STK'].str.slice(21,30)#.decode('gb2312'),
        TDate=lambda df: df.TDate.map(lambda x: x[0:4]+x[5:7]+x[8:10]),
    )
)

Menggunakan Assign. Dari dokumentasi : Tetapkan kolom baru ke DataFrame, mengembalikan objek baru (salinan) dengan semua kolom asli selain yang baru.

Lihat artikel Tom Augspurger tentang metode chaining di panda: https://tomaugspurger.github.io/method-chaining

hughdbrown
sumber
2

Tindak lanjut pertanyaan / komentar pemula

Mungkin klarifikasi untuk pemula lain seperti saya (saya berasal dari R yang tampaknya bekerja sedikit berbeda di bawah tenda). Kode fungsional dan tampak tidak berbahaya berikut terus menghasilkan peringatan SettingWithCopy, dan saya tidak tahu mengapa. Saya telah membaca dan memahami dikeluarkan dengan "pengindeksan berantai", tetapi kode saya tidak mengandung:

def plot(pdb, df, title, **kw):
    df['target'] = (df['ogg'] + df['ugg']) / 2
    # ...

Tapi kemudian, kemudian, sangat terlambat, saya melihat di mana fungsi plot () dipanggil:

    df = data[data['anz_emw'] > 0]
    pixbuf = plot(pdb, df, title)

Jadi "df" bukan bingkai data tetapi objek yang entah bagaimana mengingat bahwa itu dibuat dengan mengindeks bingkai data (jadi apakah itu tampilan?) Yang akan membuat baris dalam plot ()

 df['target'] = ...

setara dengan

 data[data['anz_emw'] > 0]['target'] = ...

yang merupakan pengindeksan berantai. Apakah saya benar?

Bagaimanapun,

def plot(pdb, df, title, **kw):
    df.loc[:,'target'] = (df['ogg'] + df['ugg']) / 2

memperbaikinya.

musbur
sumber
1

Karena pertanyaan ini sudah sepenuhnya dijelaskan dan didiskusikan dalam jawaban yang sudah ada, saya hanya akan memberikan pandaspendekatan yang rapi kepada manajer konteks menggunakan pandas.option_context(tautan ke dokumen dan contoh ) - sama sekali tidak perlu membuat kelas khusus dengan semua metode dunder dan bel lainnya dan peluit.

Pertama kode manajer konteks itu sendiri:

from contextlib import contextmanager

@contextmanager
def SuppressPandasWarning():
    with pd.option_context("mode.chained_assignment", None):
        yield

Lalu sebuah contoh:

import pandas as pd
from string import ascii_letters

a = pd.DataFrame({"A": list(ascii_letters[0:4]), "B": range(0,4)})

mask = a["A"].isin(["c", "d"])
# Even shallow copy below is enough to not raise the warning, but why is a mystery to me.
b = a.loc[mask]  # .copy(deep=False)

# Raises the `SettingWithCopyWarning`
b["B"] = b["B"] * 2

# Does not!
with SuppressPandasWarning():
    b["B"] = b["B"] * 2

Yang perlu diperhatikan adalah bahwa kedua pendekatan tidak memodifikasi a, yang agak mengejutkan bagi saya, dan bahkan salinan df dangkal dengan .copy(deep=False)akan mencegah peringatan ini dinaikkan (sejauh yang saya mengerti salinan dangkal setidaknya harus memodifikasi ajuga, tetapi tidak 't. pandassihir.).

m-dz
sumber
hmmm, saya mengerti jika peringatan mengangkat ada sesuatu yang salah, jadi lebih baik hindari peringatan seperti menekannya, bagaimana menurut Anda?
jezrael
Tidak, peringatan hanyalah peringatan. Seperti di sini, itu peringatan sesuatu mungkin salah yang besar untuk tahu, tapi jika Anda tahu apa dan mengapa Anda melakukannya baik-baik saja untuk menekan beberapa dari mereka. Lihat penjelasan di stackoverflow.com/a/20627316/4272484 tentang menetapkan ulang referensi.
m-dz
1

Saya telah mendapatkan masalah ini .apply()ketika menetapkan kerangka data baru dari kerangka data yang sudah ada sebelumnya di mana saya telah menggunakan .query()metode ini. Contohnya:

prop_df = df.query('column == "value"')
prop_df['new_column'] = prop_df.apply(function, axis=1)

Akan mengembalikan kesalahan ini. Perbaikan yang tampaknya menyelesaikan kesalahan dalam kasus ini adalah dengan mengubah ini menjadi:

prop_df = df.copy(deep=True)
prop_df = prop_df.query('column == "value"')
prop_df['new_column'] = prop_df.apply(function, axis=1)

Namun, ini TIDAK efisien terutama ketika menggunakan kerangka data yang besar, karena harus membuat salinan baru.

Jika Anda menggunakan .apply()metode dalam menghasilkan kolom baru dan nilainya, perbaikan yang mengatasi kesalahan dan lebih efisien adalah dengan menambahkan .reset_index(drop=True):

prop_df = df.query('column == "value"').reset_index(drop=True)
prop_df['new_column'] = prop_df.apply(function, axis=1)
ZG1997
sumber