Bagaimana cara berurusan SettingWithCopyWarning
di Panda?
Posting ini dimaksudkan untuk pembaca yang,
- Ingin mengerti apa artinya peringatan ini
- Ingin memahami berbagai cara untuk menekan peringatan ini
- 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, SettingWithCopyWarning
itu dibuat untuk menandai operasi "tugas dirantai". Pertimbangkan df
dalam 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 loc
untuk penugasan berbasis-label, dan iloc
untuk penugasan berbasis integer / positional, karena spec menjamin bahwa mereka selalu beroperasi pada yang asli. Selain itu, untuk mengatur sel tunggal, Anda harus menggunakan at
dan iat
.
Lebih banyak dapat ditemukan dalam dokumentasi .
Catatan
Semua operasi pengindeksan boolean yang dilakukan dengan loc
juga dapat dilakukan dengan iloc
. Satu-satunya perbedaan adalah bahwa iloc
mengharapkan 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:
Membuat deepcopy
df2 = df[['A']].copy(deep=True)
df2['A'] /= 2
Gantipd.options.mode.chained_assignment
Dapat diatur untuk None
, "warn"
, atau "raise"
. "warn"
adalah standarnya. None
akan 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 df2
dari sesuatu yang lebih besar, seperti
df2 = df[df.A > 5]
? Dalam hal ini, pengindeksan boolean akan mengembalikan tampilan, sehingga df2
akan referensi aslinya. Apa yang Anda akan perlu lakukan adalah menetapkan df2
untuk 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 df2
harus 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.