SettingWithCopyWarning
Pandalar ile nasıl başa çıkılır?
Bu yazı okuyucular içindir,
- Bu uyarının ne anlama geldiğini anlamak ister misiniz?
- Bu uyarıyı bastırmanın farklı yollarını anlamak ister misiniz?
- Kodlarını nasıl geliştireceğinizi anlamak ve gelecekte bu uyarıyı önlemek için iyi uygulamaları takip etmek istiyorum.
Kurmak
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
Nedir SettingWithCopyWarning
?
Bu uyarıyla nasıl başa çıkılacağını bilmek için bunun ne anlama geldiğini ve neden ilk başta yükseltildiğini anlamak önemlidir.
DataFrames filtre uygularken, mümkün dilim / indeksi bir ya dönmek için bir çerçeve olduğu görünümü ya da bir kopyasını iç düzeni ve çeşitli uygulama ayrıntıları bağlı. Terimin de belirttiği gibi, bir "görünüm" orijinal verilere bir görünümdür, bu nedenle görünümü değiştirmek orijinal nesneyi değiştirebilir. Öte yandan, bir "kopya" orijinalden veri çoğaltmasıdır ve kopyanın değiştirilmesinin orijinal üzerinde hiçbir etkisi yoktur.
Diğer cevapların da belirttiği gibi, SettingWithCopyWarning
"zincirleme atama" operasyonlarını işaretlemek için oluşturuldu. df
Yukarıdaki kurulumu düşünün . "A" sütunundaki değerlerin> 5 olduğu tüm "B" sütunundaki değerleri seçmek istediğinizi varsayalım. Pandalar bunu farklı şekillerde yapmanızı sağlar, bazıları diğerlerinden daha doğrudur. Örneğin,
df[df.A > 5]['B']
1 3
2 6
Name: B, dtype: int64
Ve,
df.loc[df.A > 5, 'B']
1 3
2 6
Name: B, dtype: int64
Bunlar aynı sonucu döndürür, bu nedenle yalnızca bu değerleri okuyorsanız, hiçbir fark yaratmaz. Peki sorun nedir? Zincirleme atamayla ilgili sorun, bir görünümün veya kopyanın döndürülüp döndürülmediğini tahmin etmenin genellikle zor olmasıdır, bu nedenle değerleri geri atamaya çalıştığınızda bu büyük ölçüde bir sorun haline gelir. Önceki örneği oluşturmak için, bu kodun yorumlayıcı tarafından nasıl yürütüldüğünü düşünün:
df.loc[df.A > 5, 'B'] = 4
# becomes
df.__setitem__((df.A > 5, 'B'), 4)
İçin tek bir __setitem__
çağrı ile df
. OTOH, şu kodu göz önünde bulundurun:
df[df.A > 5]['B'] = 4
# becomes
df.__getitem__(df.A > 5).__setitem__('B", 4)
Şimdi, __getitem__
bir görünüm veya kopya döndürülmesine bağlı olarak , __setitem__
işlem çalışmayabilir .
Genel olarak, spesifikasyon her zaman orijinal üzerinde çalıştıklarını garanti ettiği loc
için etiket tabanlı atama iloc
için ve tamsayı / konum tabanlı atama için kullanmalısınız. Ayrıca, tek bir hücre ayarlamak için at
ve kullanmalısınız iat
.
Daha fazlası dokümanlarda bulunabilir .
Not
Tüm boole indeksleme işlemleri ile loc
de yapılabilir iloc
. Tek fark, iloc
dizin için tamsayılar / konumlar veya boole değerlerinin sayısal bir dizisini ve sütunlar için tamsayı / konum dizinlerini beklemesidir.
Örneğin,
df.loc[df.A > 5, 'B'] = 4
Nas yazılabilir
df.iloc[(df.A > 5).values, 1] = 4
Ve,
df.loc[1, 'A'] = 100
Olarak yazılabilir
df.iloc[1, 0] = 100
Ve bunun gibi.
Bana uyarıyı nasıl bastıracağımı söyle!
"A" sütununda basit bir işlem düşünün df
. "A" seçmek ve 2'ye bölmek uyarıyı yükseltir, ancak işlem çalışır.
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
Bu uyarıyı doğrudan susturmanın birkaç yolu vardır:
Yapmak deepcopy
df2 = df[['A']].copy(deep=True)
df2['A'] /= 2
Değişimpd.options.mode.chained_assignment
için ayarlanabilir None
, "warn"
veya "raise"
. "warn"
varsayılan değerdir. None
uyarıyı tamamen bastıracak ve işlemin geçmesini engelleyerek "raise"
a atacaktır SettingWithCopyError
.
pd.options.mode.chained_assignment = None
df2['A'] /= 2
@Peter Cotton yorumlarda, modu sadece gerektiği kadar ayarlamak ve bir bağlam yöneticisi kullanarak modu müdahaleci olmayan bir şekilde değiştirmeyi ( bu özgeçmişten değiştirilmiş) güzel bir yolla geldi ve tekrar bittiğinde orijinal durumu.
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
Kullanımı aşağıdaki gibidir:
# some code here
with ChainedAssignent():
df2['A'] /= 2
# more code follows
Veya istisnayı gündeme getirmek
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
"XY Sorunu": Neyi yanlış yapıyorum?
Çoğu zaman, kullanıcılar neden ilk etapta ortaya çıktığını tam olarak anlamadan bu istisnayı bastırmanın yollarını aramaya çalışırlar. Bu, kullanıcıların aslında daha köklü bir "X" sorununun belirtisi olan "Y" sorununu çözmeye çalıştığı bir XY sorununa iyi bir örnektir . Sorular, bu uyarıyla karşılaşılan yaygın sorunlara dayanarak sorulacak ve daha sonra çözümler sunulacaktır.
Soru 1
Bir DataFrame'im var
df
A B C D E
0 5 0 3 3 7
1 9 3 5 2 4
2 7 6 8 8 1
"A"> 5 ile 1000 arasında değerler atamak istiyorum. Beklediğim çıktı
A B C D E
0 5 0 3 3 7
1 1000 3 5 2 4
2 1000 6 8 8 1
Bunu yapmanın yanlış yolu:
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
Doğru yolu kullanarak loc
:
df.loc[df.A > 5, 'A'] = 1000
Soru 2 1
(1, 'D') hücresindeki değeri 12345 olarak ayarlamaya çalışıyorum. Beklediğim çıktı
A B C D E
0 5 0 3 3 7
1 9 3 5 12345 4
2 7 6 8 8 1
Bu hücreye erişmek için farklı yollar denedim
df['D'][1]
. Bunu yapmanın en iyi yolu nedir?
1. Bu soru özellikle uyarı ile ilgili değildir, ancak uyarının gelecekte ortaya çıkabileceği durumlardan kaçınmak için bu özel işlemin nasıl doğru bir şekilde yapılacağını anlamak iyidir.
Bunu yapmak için aşağıdaki yöntemlerden herhangi birini kullanabilirsiniz.
df.loc[1, 'D'] = 12345
df.iloc[1, 3] = 12345
df.at[1, 'D'] = 12345
df.iat[1, 3] = 12345
Soru 3
Bazı koşullara göre değerleri alt kümeye koymaya çalışıyorum. Bir DataFrame'im var
A B C D E
1 9 3 5 2 4
2 7 6 8 8 1
Ben "C" == 5 öyle ki "D" 123 değerleri atamak istiyorum.
df2.loc[df2.C == 5, 'D'] = 123
Hangi iyi görünüyor ama hala alıyorum
SettingWithCopyWarning
! Bunu nasıl düzeltirim?
Bu aslında boru hattınızda daha yüksek kod nedeniyle. df2
Daha büyük bir şeyden mi yarattınız?
df2 = df[df.A > 5]
? Bu durumda, boole indeksleme bir görünüm döndürür, bu yüzden df2
orijinaline referans verir. Yapmanız gereken df2
bir kopyaya atamaktır :
df2 = df[df.A > 5].copy()
# Or,
# df2 = df.loc[df.A > 5, :]
Soru 4
"C" sütununu yerinde bırakmaya çalışıyorum
A B C D E
1 9 3 5 2 4
2 7 6 8 8 1
Ancak
df2.drop('C', axis=1, inplace=True)
Atar SettingWithCopyWarning
. Bu neden oluyor?
Bunun nedeni df2
, diğer bazı dilimleme işlemlerinden bir görünüm olarak oluşturulmuş olması gerektiğidir.
df2 = df[df.A > 5]
Burada çözüm ya bir hale getirmektir copy()
arasında df
veya kullanımına loc
daha önce olduğu gibi.