Panda'da SettingWithCopyWarning ile nasıl başa çıkılır?


629

Arka fon

Pandalarımı 0.11'den 0.13.0rc1'e yükselttim. Şimdi, uygulama birçok yeni uyarı ortaya çıkıyor. Bunlardan biri şöyle:

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

Bunun tam olarak ne anlama geldiğini bilmek istiyorum? Bir şeyi değiştirmem gerekiyor mu?

Kullanmakta ısrar edersem uyarıyı nasıl askıya almalıyım quote_df['TVol'] = quote_df['TVol']/TVOL_SCALE?

Hata veren işlev

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

Diğer hata mesajları

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])

2
Uyarı düzeyini geçici olarak ayarlamak için bir bağlam yöneticisi aşağıdadır gist.github.com/notbanker/2be3ed34539c86e22ffdd88fd95ad8bc
Peter Cotton

2
kullanabilirsiniz df.set_value, burada dokümanlar - pandas.pydata.org/pandas-docs/stable/generated/…
leonprou

1
pandas.pydata.org/pandas-docs/stable/… resmi belge ayrıntılı olarak açıklayın
Wyx

3
@leonprou df.set_valuekullanımdan kaldırıldı. Pandalar şimdi .at[]ya da .iat[]bunun yerine kullanmanızı önerir . docs here pandas.pydata.org/pandas-docs/stable/generated/…
Kyle C

Kimsenin pandalardan bahsetmediğine şaşırdım option_context: pandas.pydata.org/pandas-docs/stable/user_guide/options.html ,with pd.option_context("mode.chained_assignment", None): [...]
m-dz

Yanıtlar:


794

Bu SettingWithCopyWarning, özellikle ilk seçim bir kopyasını döndürdüğünde, her zaman beklendiği gibi çalışmayan aşağıdakiler gibi potansiyel olarak karmaşık "zincirleme" atamaları işaretlemek için oluşturuldu . [ arka plan tartışması için GH5390 ve GH5597'ye bakınız .]

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

Uyarı aşağıdaki gibi yeniden yazma önerisi sunar:

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

Ancak bu, aşağıdakilere eşdeğer olan kullanımınıza uymuyor:

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

Yazmaları orijinal kareye geri döndürmeyi umursamadığınız açık olsa da (referansı üzerine yazdığınızdan), ne yazık ki bu kalıp ilk zincirleme atama örneğinden ayırt edilemez. Bu nedenle (yanlış pozitif) uyarısı. Daha fazla okumak isterseniz, yanlış pozitif potansiyeli dizine ekleme dokümanlarında ele alınmıştır . Aşağıdaki atama ile bu yeni uyarıyı güvenle devre dışı bırakabilirsiniz.

import pandas as pd
pd.options.mode.chained_assignment = None  # default='warn'

34
Sanırım çoğunlukla bu konuda hiç uyarmamaktan yanayım. Zincirleme atama sözdizimi ile çalışıyorsanız, herhangi bir durumda beklendiği gibi çalışması için mutlaka dizine eklenmesinin sırasını belirleyebilirsiniz. Bence bu konuda kapsamlı önlemler alınması aşırı paranoyak. 'Özel' sınıf yöntemleri veya nitelikleri hakkında "herkesin yetişkin olmasına izin vermek" ile aynı ruhla, pandaların kullanıcıların zincirleme görevler konusunda yetişkin olmasına izin vermelerinin daha iyi olduğunu düşünüyorum. Bunları yalnızca ne yaptığınızı biliyorsanız kullanın.
ely

48
İnsanları alternatifler için hacklendiğinde uyarmaya çalışmak biraz pythonic. Daha yeni stil olan Pandalar erişim yöntemleri (iyileştirilmiş .ix, iyileştirilmiş .ilocvb.) Kesinlikle herkesi başka yollar hakkında sürekli uyarmadan "birincil yol" olarak görülebilir. Bunun yerine yetişkinler olsun ve zincirleme atama yapmak istiyorlarsa, öyle olsun. Zaten iki sentim. Zincirleme görevler bir sorunu çözmek için işe yarayacağında sık sık Pandas devs'den hoşnutsuz yorumlar görüyor, ancak bunu yapmanın "birincil" yolu olarak görülmeyecek.
ely

8
@EMS sorun, bir kopyaya karşı bir görünümün yapıldığı koddan her zaman net olmaması ve bu sorundan bir dizi hata / karışıklık ortaya çıkmasıdır . Kopyalama uyarısı ile ayarın nasıl çalıştığı göz önüne alındığında, otomatik olarak yapılandırma yapmak için bir rc dosyası / seçenekleri koymayı düşünüyoruz.
Jeff Tratner

3
Uyarmanın nedeni elbette eski kodu yükselten insanlar içindir. Ve kesinlikle bir uyarıya ihtiyacım var, çünkü bazı çok çirkin eski kodlarla uğraşıyorum.
Thomas Andrews

15
Bir yan notta, chained_assignment uyarısının devre dışı bırakılmasını buldum: kodumun pd.options.mode.chained_assignment = Noneyaklaşık 6 kat daha hızlı çalışmasına neden oldu. Başka biri benzer sonuçlar yaşadı mı?
Muon

209

SettingWithCopyWarningPandalar ile nasıl başa çıkılır?

Bu yazı okuyucular içindir,

  1. Bu uyarının ne anlama geldiğini anlamak ister misiniz?
  2. Bu uyarıyı bastırmanın farklı yollarını anlamak ister misiniz?
  3. 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. dfYukarı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 lociçin etiket tabanlı atama ilociçin ve tamsayı / konum tabanlı atama için kullanmalısınız. Ayrıca, tek bir hücre ayarlamak için atve kullanmalısınız iat.

Daha fazlası dokümanlarda bulunabilir .

Not
Tüm boole indeksleme işlemleri ile locde yapılabilir iloc. Tek fark, ilocdizin 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:

  1. Yapmak deepcopy

    df2 = df[['A']].copy(deep=True)
    df2['A'] /= 2
    
  2. Değişimpd.options.mode.chained_assignment
    için ayarlanabilir None, "warn"veya "raise". "warn"varsayılan değerdir. Noneuyarı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. df2Daha 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 df2orijinaline referans verir. Yapmanız gereken df2bir 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 dfveya kullanımına locdaha önce olduğu gibi.


7
Not: Durumunuzun 3. bölümün soru listesi kapsamında olmadığını bildirin. Yazımı değiştireceğim.
cs95

150

Genelde noktası SettingWithCopyWarningonlar kullanıcıları (ve özellikle yeni kullanıcılar) göstermektir olabilir düşündüklerini olarak orijinali bir kopyası üzerinde faaliyet gösteren ve edilmeyecektir. Orada olan yanlış pozitif (eğer ne yaptığınızı biliyorsanız IOW bu olabilir Tamam ). Bir olasılık, @Garrett'in önerdiği gibi (varsayılan olarak uyar ) uyarısını kapatmaktır.

İşte başka bir seçenek:

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

Bu nesne için denetimi etkin bir şekilde kapatacak olan is_copybayrağı şu şekilde ayarlayabilirsiniz :False

In [5]: dfa.is_copy = False

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

Açık bir şekilde kopyalarsanız, başka uyarı yapılmaz:

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

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

OP'nin yukarıda gösterilen kodu meşru iken ve muhtemelen de yaptığım bir şey, teknik olarak bu uyarı için bir durumdur ve yanlış bir pozitif değildir. Uyarı almamak için başka bir yol , seçim işlemini reindexörn.

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

Veya,

quote_df = quote_df.reindex(['STK', ...], axis=1)  # v.0.21

Bilgi ve tartışma için teşekkürler, konsolu sessiz bırakmak için uyarıyı kapatıyorum. SQL veritabanındaki görünüm ve tablo gibi geliyor. 'Kopya' kavramının getirilmesinin faydası hakkında daha fazla bilgi sahibi olmalıyım, ancak IMHO ince anlamsal 、 sözdizimi farkına dikkat etmek biraz zor ..
bigbug

19
Kopyanı kabul ediyorum (); açık ve benim sorunum düzeltildi (bu yanlış bir pozitifti).
rdchambers

5
Güncellemeden sonra 0.16daha fazla yanlış pozitif görüyorum, yanlış pozitiflerle ilgili sorun, bazen yasal olsa bile, görmezden gelmeyi öğrenmektir.
dashesy

3
@dashesy noktayı kaçırıyorsun. bazen belki de çoğu zaman işe yarayabilir. Çerçeve daha küçük / büyük olan ya da bunu farklı bir d_type bir sütun söz eklerseniz Ama örneğin gerçekleşebilir değil işi. Mesele bu. Bir şey yapıyorsun olabilir çalışmak ancak garanti edilmez. Bu, kullanımdan kaldırma uyarılarından çok farklıdır. Kullanmaya devam etmek istiyorsanız ve çalışıyorsa, harika. Ama dikkat edin.
Jeff

3
@ Jeff şimdi mantıklı, bu yüzden bir undefineddavranış. Herhangi bir şey varsa, bir hata atmalıdır (o zaman görülen tuzaklardan kaçınmak için C), apidondurulmuş olduğu için mevcut uyarı davranışı geriye dönük uyumluluk için mantıklıdır. Ve onları üretim kodumdaki hatalar olarak yakalamak için fırlatacağım ( warnings.filterwarnings('error', r'SettingWithCopyWarning). Ayrıca, kullanım önerisi .locbazen de yardımcı olmaz (eğer bir grupta ise).
dashesy

41

Pandalar veri çerçevesi kopya uyarısı

Gidip böyle bir şey yaptığınızda:

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

pandas.ix bu durumda yeni, tek başına bir veri çerçevesi döndürür.

Bu veri çerçevesinde değiştirmeye karar verdiğiniz değerler orijinal veri çerçevesini değiştirmez.

Pandalar sizi bu konuda uyarmaya çalışır.


Neden .ixkötü bir fikir

.ixNesne birden fazla şey yapmaya çalışır ve temiz kod hakkında bir şey okumuş herkes için, bu güçlü koku olduğunu.

Bu veri çerçevesi göz önüne alındığında:

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

İki davranış:

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

Birinci davranış: dfcopyartık bağımsız bir veri çerçevesi. Değiştirmek değişmeyecekdf

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

İkinci davranış: Bu, orijinal veri çerçevesini değiştirir.


.locBunun yerine kullan

Panda geliştiricileri, .ixnesnenin [spekülatif olarak] oldukça kötü kokulu olduğunu fark ettiler ve böylece verilerin erişimine ve atanmasına yardımcı olan iki yeni nesne yarattılar. (Diğeri .iloc)

.loc daha hızlıdır, çünkü verilerin bir kopyasını oluşturmaya çalışmaz.

.loc mevcut veri çerçevenizi yerinde değiştirmek anlamına gelir, bu da bellekte daha verimlidir.

.loc tahmin edilebilir, bir davranışı vardır.


Çözüm

Kod örneğinizde yaptığınız şey, çok sayıda sütun içeren büyük bir dosya yüklemek, ardından daha küçük olacak şekilde değiştirmek.

pd.read_csvFonksiyon bu bir sürü size yardımcı ve ayrıca çok daha hızlı dosya yükleme yapabilirsiniz.

Yani bunu yapmak yerine

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]]

Bunu yap

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

Bu, yalnızca ilgilendiğiniz sütunları okuyacak ve doğru şekilde adlandıracaktır. .ixBüyülü şeyler yapmak için kötü nesneyi kullanmaya gerek yok.


"Pandalar geliştiricileri .ix nesnesinin [spekülatif olarak] oldukça kötü kokulu olduğunu fark ettiler ve böylece iki yeni nesne yarattılar" diğeri ne?
jf328

3
Sanırım
Brian Bien

1
Evet, öyle .iloc. Bunlar panda veri yapılarını endekslemek için iki ana yöntemdir. Dokümantasyonda daha fazla bilgi edinin.
Ninjakannon

Nasıl bir DataFrame sütun zaman damgaları ile tarih saat nesnesi veya dize ile sütuna değiştirmek gerekir?
boldnik

@boldnik Bu yanıtı kontrol edin stackoverflow.com/a/37453925/3730397
firelynx

20

Burada soruyu doğrudan cevaplıyorum. Nasıl başa çıkılır bununla?

.copy(deep=False)Dilim yaptıktan sonra yapın . Bkz. Pandalar.DataFrame.copy .

Bekle, bir dilim kopyasını geri vermiyor mu? Sonuçta, uyarı mesajı söylemeye çalışıyor mu? Uzun cevabı okuyun:

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

Bu bir uyarı verir:

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

Bu değil:

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

Hem df0ve df1olan DataFramenesneler, ancak onlar hakkında bir şey uyarı yazdırmak için pandalar sağlayan farklıdır. Ne olduğunu bulalım.

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

Fark seçim aracınızı kullanarak, birkaç adresin ötesinde, tek önemli farkın şu olduğunu göreceksiniz:

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

Uyarılıp uyarılmayacağına karar veren yöntem DataFrame._check_setitem_copyhangi kontrollerin yapıldığıdır _is_copy. İşte buyurun. Bir Make copysenin DataFrame değildir ki _is_copy.

Uyarının kullanılması önerilmektedir .loc, ancak bu .locçerçevede kullanırsanız _is_copyaynı uyarıyı almaya devam edersiniz. Yanıltıcı? Evet. Can sıkıcı? Emin ol. Faydalı? Potansiyel olarak, zincirleme atama kullanıldığında. Ancak zincir atamasını doğru bir şekilde algılayamaz ve uyarıyı rastgele yazdırır.


11

Bu konu Pandalar ile gerçekten kafa karıştırıcı. Neyse ki, nispeten basit bir çözümü var.

Sorun, veri filtreleme işlemlerinin (örn. Loc) DataFrame'in bir kopyasını veya görünümünü döndürüp döndürmediğinin her zaman net olmamasıdır. Bu tür filtrelenmiş DataFrame'in daha fazla kullanılması bu nedenle kafa karıştırıcı olabilir.

Basit çözüm (çok büyük veri kümeleriyle çalışmanız gerekmedikçe):

Herhangi bir değeri güncellemeniz gerektiğinde, her zaman atamadan önce DataFrame'i kopyaladığınızdan emin olun.

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)

Bir yazım hatası var: açıkça açıkça olmalı
s9527

7

Herhangi bir şüpheyi ortadan kaldırmak için çözümüm, normal bir kopya yerine dilimin derin bir kopyasını yapmaktı. Bağlamınıza bağlı olarak bu geçerli olmayabilir (Bellek kısıtlamaları / dilimin boyutu, performans düşüşü potansiyeli - özellikle kopya benim için olduğu gibi bir döngüde gerçekleşirse, vb.)

Açık olmak gerekirse, aldığım uyarı:

/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

örnekleme

Dilimin bir kopyasına bıraktığım bir sütun yüzünden uyarının atıldığına dair şüphelerim vardı. Teknik olarak dilimin kopyasında bir değer ayarlamaya çalışmasa da, yine de dilimin kopyasının bir değişikliğiydi. Şüpheyi doğrulamak için attığım (basitleştirilmiş) adımlar aşağıdadır, umarım uyarıyı anlamaya çalışanlarımıza yardımcı olacaktır.

Örnek 1: Orijinalin üzerine bir sütun bırakmak kopyayı etkiler

Bunu zaten biliyorduk ama bu sağlıklı bir hatırlatma. Bu uyarı ile ilgili DEĞİLDİR .

>> 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

Df1'de df2'yi etkilemek için yapılan değişikliklerden kaçınmak mümkündür

>> 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

Örnek 2: Kopyaya bir sütun düşürmek orijinali etkileyebilir

Bu aslında uyarıyı gösterir.

>> 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

Df2'yi etkilemek için df2'de yapılan değişikliklerden kaçınmak mümkündür

>> 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

Şerefe!


4

Bu çalışmalı:

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

4

Bazıları sadece uyarıyı bastırmak isteyebilir:

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

3

Dilim bir değişkene atadıysanız ve değişkeni aşağıdaki gibi kullanarak ayarlamak istiyorsanız:

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

Ve Jeffs çözümünü kullanmak istemiyorsunuz çünkü durum hesaplama df2işleminiz uzun veya başka bir nedenden dolayı, o zaman aşağıdakileri kullanabilirsiniz:

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

df2.index.tolist() df2'deki tüm girdilerin dizinlerini döndürür; bu sütun daha sonra orijinal veri çerçevesinde B sütununu ayarlamak için kullanılır.


Bu 9 kat daha pahalı sonra df ["B"] = değer
Claudiu Creanga

@ClaudiuCreanga daha derinlemesine açıklayabilir misiniz?
gies0r

2

Benim için bu sorun aşağıdaki> basitleştirilmiş <örnekte ortaya çıktı. Ve bunu da çözebildim (umarım doğru bir çözümle):

uyarı ile eski kod:

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

Bu hat için uyarı yazdırdı old_row[field] = new_row[field]

Update_row yöntemindeki satırlar aslında yazıldığından Series, satırı şu şekilde değiştirdim:

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

yani erişim yöntemi / aramalar için a Series. Satış her ikisi de gayet iyi çalışıyor ve sonuç aynı, bu şekilde uyarıları devre dışı bırakmak zorunda değilim (= başka bir yerde diğer zincir indeksleme sorunları için onları tutmak).

Umarım bu birine yardımcı olabilir.


2

Böyle bir problemden kaçınabilirsiniz, inanıyorum:

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]),
    )
)

Ata'yı kullanma. Gönderen belgelere yenileri ilave olarak tüm orijinal sütunlu yeni bir nesne (kopya) dönen bir DataFrame atama başlıklı yeni sütunlar,:.

Tom Augspurger'in pandalarda yöntem zincirleme hakkındaki makalesine bakın: https://tomaugspurger.github.io/method-chaining


2

Takip acemi soru / açıklama

Belki benim gibi diğer yeni başlayanlar için bir açıklama (başlıktan biraz farklı çalışıyor gibi görünen R'den geliyorum). Aşağıdaki zararsız görünümlü ve fonksiyonel kod SettingWithCopy uyarısını üretmeye devam etti ve nedenini bulamadım. Ben hem okudum ve "zincirleme indeksleme" ile verilen anladım, ama benim kod herhangi içermez:

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

Ama sonra, çok geç, plot () fonksiyonunun nerede çağrıldığına baktım:

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

Yani "df" bir veri çerçevesi değil, bir şekilde veri grafiğini dizine ekleyerek oluşturulduğunu hatırlayan bir nesne (yani bir görünüm mü?)

 df['target'] = ...

eşittir

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

Bu zincirleme bir indeksleme. Bunu doğru anladım mı?

Neyse,

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

onu düzeltti.


1

Bu soru zaten tam olarak açıklandığı ve mevcut cevaplarda tartışıldığı pandasiçin, içerik yöneticisine pandas.option_context( dokümanlara ve örneğe bağlantılar) kullanarak düzgün bir yaklaşım sunacağım - tüm dunder yöntemleri ve diğer çanlarla özel bir sınıf oluşturmaya kesinlikle gerek yok ve ıslık çalar.

İlk önce içerik yöneticisi kodunun kendisi:

from contextlib import contextmanager

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

Sonra bir örnek:

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

Worth Farkettiğin hem metodlardan değiştirmek kalmamasıdır abiraz bana şaşırtıcı olan, ve ile kopya df bile sığ .copy(deep=False)olarak (bu uyarıyı önleyecek yetiştirilmek üzere kadarıyla sığ kopya en az değiştirmesi gerektiğini anladığım kadarıyla ada, ama o değil 't. pandasbüyü.).


hmmm, uyarı bir şey yanlış ortaya çıkarsa açıkça anlıyorum, bu yüzden bastırmak gibi uyarı önlemek daha iyi, ne düşünüyorsun?
jezrael

Hayır, uyarı sadece bir uyarıdır. Burada olduğu gibi, bilmeniz harika bir şeylerin yanlış olabileceği konusunda sizi uyarıyor , ancak ne ve neden yaptığınızı biliyorsanız, bazılarını bastırmak mükemmel bir şekilde iyi. Referansları yeniden atama hakkında stackoverflow.com/a/20627316/4272484 adresindeki açıklamaya bakın .
m-dz

1

Yöntemi .apply()kullandığım önceden var olan bir veri çerçevesinden yeni bir veri çerçevesi atarken bu sorunu alıyordum .query(). Örneğin:

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

Bu hatayı döndürür. Bu durumda hatayı gideren düzeltme, bunu şu şekilde değiştirmektir:

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

Ancak, yeni bir kopya yapmak zorunda kaldığından, özellikle büyük veri çerçeveleri kullanılırken bu etkili DEĞİLDİR.

Eğer kullanıyorsanız .apply()yeni bir sütun ve değerlerini, hatayı giderir ve ekleyerek daha verimli bir düzeltme üreten yöntemi .reset_index(drop=True):

prop_df = df.query('column == "value"').reset_index(drop=True)
prop_df['new_column'] = prop_df.apply(function, axis=1)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.