Come gestire SettingWithCopyWarning in Panda?


630

sfondo

Ho appena aggiornato i miei Panda da 0,11 a 0,13,0 rc1. Ora l'applicazione sta spuntando molti nuovi avvisi. Uno di questi in questo modo:

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

Voglio sapere cosa significa esattamente? Devo cambiare qualcosa?

Come devo sospendere l'avviso se insisto per l'uso quote_df['TVol'] = quote_df['TVol']/TVOL_SCALE?

La funzione che dà errori

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

Più messaggi di errore

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
Ecco un gestore di contesto per impostare temporaneamente il livello di avviso gist.github.com/notbanker/2be3ed34539c86e22ffdd88fd95ad8bc
Peter Cotton,

2
puoi usare i df.set_valuedocumenti qui - pandas.pydata.org/pandas-docs/stable/generated/…
leonprou,

1
pandas.pydata.org/pandas-docs/stable/… il documento ufficiale spiega in dettaglio
wyx,

3
@leonprou df.set_valueè stato deprecato. Pandas ora consiglia di utilizzare .at[]o .iat[]invece. docs here pandas.pydata.org/pandas-docs/stable/generated/…
Kyle C

Sono sorpreso che nessuno abbia menzionato i panda option_contextqui: pandas.pydata.org/pandas-docs/stable/user_guide/options.html , usa comewith pd.option_context("mode.chained_assignment", None): [...]
m-dz il

Risposte:


795

The è SettingWithCopyWarningstato creato per contrassegnare assegnazioni "concatenate" potenzialmente confuse, come le seguenti, che non sempre funzionano come previsto, in particolare quando la prima selezione restituisce una copia . [vedi GH5390 e GH5597 per discussioni di base.]

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

L'avviso offre un suggerimento per riscrivere come segue:

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

Tuttavia, questo non si adatta al tuo utilizzo, che equivale a:

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

Mentre è chiaro che non ti interessa che le scritture tornino al frame originale (poiché stai sovrascrivendo il riferimento ad esso), sfortunatamente questo modello non può essere differenziato dal primo esempio di assegnazione incatenato. Da qui l'avvertimento (falso positivo). Il potenziale per falsi positivi è affrontato nei documenti sull'indicizzazione , se desideri leggere più avanti. Puoi disabilitare in modo sicuro questo nuovo avviso con la seguente assegnazione.

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

34
Penso che sarei principalmente a favore di non avvertire affatto di questo. Se lavori con la sintassi dell'assegnazione concatenata, puoi sicuramente capire l'ordine di indicizzazione che deve avvenire affinché funzioni come previsto in una determinata situazione. Penso che sia eccessivamente paranoico che ci siano queste precauzioni esaustive al riguardo. Con lo stesso spirito del "lasciare che tutti siano adulti" riguardo ai metodi o agli attributi di classe "privati", penso che per i panda sia meglio permettere agli utenti di diventare adulti per compiti concatenati. Usali solo se sai cosa stai facendo.
ely,

48
È un po 'poco pitonico cercare di avvisare le persone quando vanno in giro a cercare alternative. I metodi Pandas di accesso più recenti (migliorati .ix, migliorati .iloc, ecc.) Possono sicuramente essere visti come "il modo principale" senza avvisare tutti incessantemente di altri modi. Invece lascia che siano adulti e se vogliono fare un incatenamento, così sia. I miei due centesimi comunque. Si vedono spesso commenti scontenti da parte degli sviluppatori Pandas quando gli incarichi concatenati lavoreranno per risolvere un problema, ma non sarebbero considerati il ​​modo "primario" per farlo.
ely,

8
@EMS il problema è che non è sempre chiaro dal codice dove viene fatta una copia rispetto a una vista e da questo problema sorgono numerosi bug / confusioni. Stavamo pensando di inserire un file rc / opzioni per eseguire automaticamente la configurazione, il che potrebbe essere più utile dato il funzionamento dell'impostazione con avviso di copia.
Jeff Tratner,

3
Il motivo per avvertire è ovviamente per le persone che aggiornano il vecchio codice. E ho sicuramente bisogno di un avvertimento, perché ho a che fare con un vecchio brutto codice.
Thomas Andrews,

16
In una nota a margine, ho scoperto che disabilitando l'avviso chained_assignment: il pd.options.mode.chained_assignment = Nonemio codice è stato eseguito circa 6 volte più velocemente. Qualcun altro ha riscontrato risultati simili?
Muon,

209

Come affrontare SettingWithCopyWarningin Panda?

Questo post è destinato a lettori che,

  1. Vorrei capire cosa significa questo avviso
  2. Vorrei comprendere diversi modi per sopprimere questo avviso
  3. Vorrei capire come migliorare il loro codice e seguire le buone pratiche per evitare questo avvertimento in futuro.

Impostare

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

Qual è il SettingWithCopyWarning?

Per sapere come gestire questo avviso, è importante capire che cosa significa e perché viene generato in primo luogo.

Durante il filtraggio di DataFrames, è possibile suddividere / indicizzare un frame per restituire una vista o una copia , a seconda del layout interno e dei vari dettagli di implementazione. Una "vista" è, come suggerisce il termine, una vista nei dati originali, quindi la modifica della vista può modificare l'oggetto originale. D'altra parte, una "copia" è una replica dei dati dall'originale e la modifica della copia non ha alcun effetto sull'originale.

Come menzionato da altre risposte, è SettingWithCopyWarningstato creato per contrassegnare le operazioni di "assegnazione incatenata". Considerare dfnella configurazione sopra. Supponi di voler selezionare tutti i valori nella colonna "B" dove i valori nella colonna "A" sono> 5. Panda ti consente di farlo in diversi modi, alcuni più corretti di altri. Per esempio,

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

1    3
2    6
Name: B, dtype: int64

E,

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

1    3
2    6
Name: B, dtype: int64

Questi restituiscono lo stesso risultato, quindi se stai solo leggendo questi valori, non fa alcuna differenza. Allora, qual è il problema? Il problema con l'assegnazione concatenata è che in genere è difficile prevedere se viene restituita una vista o una copia, quindi questo diventa in gran parte un problema quando si tenta di riassegnare i valori. Per basarsi sull'esempio precedente, considerare come questo codice viene eseguito dall'interprete:

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

Con una sola __setitem__chiamata a df. OTOH, considera questo codice:

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

Ora, a seconda che sia stata __getitem__restituita una vista o una copia, l' __setitem__operazione potrebbe non funzionare .

In generale, è necessario utilizzare locper l'assegnazione basata sull'etichetta e ilocper l' assegnazione basata su numero intero / posizionale, poiché la specifica garantisce che funzionano sempre sull'originale. Inoltre, per impostare una singola cella, è necessario utilizzare ate iat.

Maggiori informazioni sono disponibili nella documentazione .

Nota È anche possibile eseguire
tutte le operazioni di indicizzazione booleane loceseguite con iloc. L'unica differenza è che si ilocaspettano numeri interi / posizioni per indice o una matrice numpy di valori booleani e indici numeri interi / posizione per le colonne.

Per esempio,

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

Può essere scritto nas

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

E,

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

Può essere scritto come

df.iloc[1, 0] = 100

E così via.


Dimmi solo come eliminare l'avvertimento!

Considera un'operazione semplice nella colonna "A" di df. Selezionare "A" e dividere per 2 genererà un avviso, ma l'operazione funzionerà.

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

Esistono un paio di modi per mettere a tacere direttamente questo avviso:

  1. Fare un deepcopy

    df2 = df[['A']].copy(deep=True)
    df2['A'] /= 2
    
  2. Cambiamentopd.options.mode.chained_assignment
    può essere impostata su None, "warn"o "raise". "warn"è l'impostazione predefinita. Nonesopprimerà completamente l'avvertimento e "raise"lancerà a SettingWithCopyError, impedendo l'operazione.

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

@Peter Cotton nei commenti, ha escogitato un modo simpatico di cambiare la modalità in modo non intrusivo (modificato da questa sintesi ) usando un gestore di contesto, per impostare la modalità solo finché è necessario e ripristinarla stato originale al termine.

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

L'utilizzo è il seguente:

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

Oppure, per sollevare l'eccezione

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

Il "problema XY": cosa sto facendo di sbagliato?

Molte volte, gli utenti tentano di cercare modi per sopprimere questa eccezione senza comprendere appieno il motivo per cui è stata sollevata in primo luogo. Questo è un buon esempio di un problema XY , in cui gli utenti tentano di risolvere un problema "Y" che in realtà è un sintomo di un problema più profondo radicato "X". Verranno poste domande sulla base di problemi comuni che incontrano questo avviso e verranno quindi presentate le soluzioni.

Domanda 1
Ho un 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

Voglio assegnare valori nella colonna "A"> da 5 a 1000. Il mio output previsto è

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

Modo sbagliato per farlo:

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

Modo giusto usando loc:

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


Domanda 2 1
Sto cercando di impostare il valore nella cella (1, 'D') su 12345. Il mio output previsto è

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

Ho provato diversi modi per accedere a questa cella, come ad esempio df['D'][1]. Qual è il modo migliore per farlo?

1. Questa domanda non è specificamente correlata all'avvertimento, ma è bene capire come eseguire correttamente questa particolare operazione in modo da evitare situazioni in cui l'avvertimento potrebbe potenzialmente sorgere in futuro.

È possibile utilizzare uno dei seguenti metodi per eseguire questa operazione.

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


Domanda 3
Sto cercando di sottoinsieme valori basati su alcune condizioni. Ho un DataFrame

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

Vorrei assegnare valori in "D" a 123 in modo che "C" == 5. Ho provato

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

Il che sembra buono ma sto ancora ottenendo il SettingWithCopyWarning! Come posso risolvere questo problema?

Questo è probabilmente probabilmente a causa del codice più in alto nella tua pipeline. Hai creato df2da qualcosa di più grande, come

df2 = df[df.A > 5]

? In questo caso, l'indicizzazione booleana restituirà una vista, quindi df2farà riferimento all'originale. Quello che dovresti fare è assegnare df2a una copia :

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


Domanda 4
Sto cercando di eliminare la colonna "C" dal posto

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

Ma usando

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

Lancia SettingWithCopyWarning. Perché sta succedendo?

Questo perché df2deve essere stato creato come vista da un'altra operazione di suddivisione, ad esempio

df2 = df[df.A > 5]

La soluzione è quella di dare sia un copy()di df, o l'uso loc, come prima.


7
PS: Fammi sapere se la tua situazione non è coperta dalla lista di domande della sezione 3. Modificherò il mio post.
cs95,

150

In generale, il punto SettingWithCopyWarningè mostrare agli utenti (e specialmente ai nuovi utenti) che potrebbero operare su una copia e non sull'originale come pensano. Ci sono falsi positivi (IOW se sai cosa stai facendo potrebbe andare bene ). Una possibilità è semplicemente disattivare l' avviso (per impostazione predefinita avvisa ) come suggerisce @Garrett.

Ecco un'altra opzione:

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

È possibile impostare il is_copyflag su False, che disattiverà effettivamente il segno di spunta, per quell'oggetto :

In [5]: dfa.is_copy = False

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

Se copi esplicitamente, non avverrà alcun ulteriore avviso:

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

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

Il codice che l'OP mostra sopra, sebbene legittimo, e probabilmente anche qualcosa che faccio anch'io, è tecnicamente un caso per questo avvertimento e non un falso positivo. Un altro modo per non avere l'avviso sarebbe quello di eseguire l'operazione di selezione tramite reindex, ad es

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

O,

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

Grazie per l'informazione e la discussione, ho appena disattivato l'avviso per far tacere la console. Sembra la vista e la tabella nel database SQL. Ho bisogno di saperne di più sul vantaggio dell'introduzione del concetto di "copia", ma IMHO è un po 'un peso prendersi cura della sottile differenza di sintassi semantica 、 ..
bigbug,

19
Sono d'accordo con la copia (); è chiaro e risolto il mio problema (che era un falso positivo).
rdchambers,

5
dopo l'aggiornamento a 0.16vedo molti più falsi positivi, il problema con i falsi positivi è che si impara a ignorarlo, anche se a volte è legittimo.
dashesy,

3
@dashesy ti manca il punto. a volte forse anche il più delle volte potrebbe funzionare. Ma può succedere, ad esempio, se il frame è più grande / più piccolo o se aggiungi una colonna che dice di un tipo diverso che non funziona. Questo è il punto. Stai facendo qualcosa che potrebbe funzionare ma non è garantito. Questo è molto diverso dagli avvisi di deprecazione. Se vuoi continuare a usarlo e funziona, fantastico. Ma sii avvisato.
Jeff,

3
@Jeff ha un senso ora, quindi è un undefinedcomportamento. Semmai dovrebbe lanciare un errore, quindi (per evitare insidie C), poiché il apicongelamento dell'attuale comportamento degli avvisi ha senso per la compatibilità con le versioni precedenti. E li farò lanciare per catturarli come errori nel mio codice di produzione ( warnings.filterwarnings('error', r'SettingWithCopyWarning). Anche il suggerimento da usare a .locvolte non aiuta neanche (se è in un gruppo).
dashesy

41

Avviso di copia del frame di dati di Panda

Quando vai e fai qualcosa del genere:

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

pandas.ix in questo caso restituisce un nuovo frame di dati autonomo.

Qualsiasi valore che decidi di modificare in questo frame di dati, non cambierà il frame di dati originale.

Questo è ciò che i panda cercano di avvisarti.


Perché .ixè una cattiva idea

L' .ixoggetto cerca di fare più di una cosa, e per chiunque abbia letto qualcosa sul codice pulito, questo è un forte odore.

Dato questo frame di dati:

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

Due comportamenti:

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

Comportamento 1: dfcopyora è un frame di dati autonomo. Cambiarlo non cambieràdf

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

Comportamento due: modifica il frame di dati originale.


Usa .locinvece

Gli sviluppatori di Panda hanno riconosciuto che l' .ixoggetto era piuttosto puzzolente [speculativamente] e quindi hanno creato due nuovi oggetti che aiutano nell'adesione e nell'assegnazione dei dati. (L'altro essere .iloc)

.loc è più veloce, perché non tenta di creare una copia dei dati.

.loc è pensato per modificare il tuo dataframe esistente sul posto, che è più efficiente in termini di memoria.

.loc è prevedibile, ha un comportamento.


La soluzione

Quello che stai facendo nell'esempio di codice è caricare un file di grandi dimensioni con molte colonne, quindi modificarlo per renderlo più piccolo.

La pd.read_csvfunzione può aiutarti molto e rendere il caricamento del file molto più veloce.

Quindi invece di farlo

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

Fai questo

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

Questo leggerà solo le colonne che ti interessano e le nominerà correttamente. Non c'è bisogno di usare l' .ixoggetto malvagio per fare cose magiche.


"Gli sviluppatori di Panda hanno riconosciuto che l'oggetto .ix era abbastanza puzzolente [speculativamente] e quindi hanno creato due nuovi oggetti" - qual è l'altro?
jf328,

3
@ jf328 .iloc Penso
Brian Bien,

1
Sì, lo è .iloc. Questi sono i due metodi principali per indicizzare le strutture di dati dei panda. Maggiori informazioni nella documentazione.
Ninjakannon,

Come si dovrebbe sostituire una colonna DataFrame con i timestamp nella colonna con oggetto data / ora o stringa?
boldnik,

@boldnik Controllare questa risposta stackoverflow.com/a/37453925/3730397
firelynx

20

Qui rispondo direttamente alla domanda. Come affrontarlo?

Fai un .copy(deep=False)dopo aver tagliato. Vedi panda.DataFrame.copy .

Aspetta, una fetta non restituisce una copia? Dopo tutto, questo è ciò che il messaggio di avviso sta tentando di dire? Leggi la risposta lunga:

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

Questo dà un avvertimento:

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

Questo non:

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

Entrambi df0e df1sono DataFrameoggetti, ma qualcosa in loro è diverso che consente ai panda di stampare l'avvertimento. Scopriamo di cosa si tratta.

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

Usando il tuo strumento diff preferito, vedrai che oltre un paio di indirizzi, l'unica differenza materiale è questa:

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

Il metodo che decide se avvisare è DataFrame._check_setitem_copyquali controlli _is_copy. Quindi eccoti. Fai in copymodo che il tuo DataFrame non lo sia _is_copy.

L'avvertimento suggerisce di usare .loc, ma se lo usi .locsu un frame _is_copy, otterrai comunque lo stesso avviso. Ingannevole? Sì. Fastidioso? Scommetti. Utile? Potenzialmente, quando viene utilizzato il compito incatenato. Ma non è in grado di rilevare correttamente l'assegnazione della catena e stampa l'avviso in modo indiscriminato.


11

Questo argomento è davvero confuso con i panda. Fortunatamente, ha una soluzione relativamente semplice.

Il problema è che non è sempre chiaro se le operazioni di filtraggio dei dati (ad es. Loc) restituiscono una copia o una vista di DataFrame. L'ulteriore utilizzo di tale DataFrame filtrato potrebbe quindi essere fonte di confusione.

La soluzione semplice è (a meno che non sia necessario lavorare con set di dati molto grandi):

Ogni volta che è necessario aggiornare qualsiasi valore, assicurarsi sempre di copiare implicitamente DataFrame prima dell'assegnazione.

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)

C'è un errore di battitura: implicitamente dovrebbe essere esplicitamente
s9527

7

Per rimuovere ogni dubbio, la mia soluzione era quella di fare una copia profonda della sezione anziché una copia normale. Questo potrebbe non essere applicabile a seconda del contesto (vincoli di memoria / dimensione della porzione, potenziale degrado delle prestazioni - specialmente se la copia si verifica in un ciclo come ha fatto per me, ecc ...)

Per essere chiari, ecco l'avvertimento che ho ricevuto:

/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

Illustrazione

Avevo dubbi sul fatto che l'avvertimento fosse stato lanciato a causa di una colonna che stavo lasciando cadere su una copia della fetta. Pur non cercando tecnicamente di impostare un valore nella copia della sezione, questa era comunque una modifica della copia della sezione. Di seguito sono riportati i passaggi (semplificati) che ho fatto per confermare il sospetto, spero che possa aiutare quelli di noi che stanno cercando di capire l'avvertimento.

Esempio 1: il rilascio di una colonna sull'originale influisce sulla copia

Lo sapevamo già, ma questo è un promemoria salutare. Questo NON è ciò di cui parla l'avvertimento.

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

È possibile evitare che le modifiche apportate su df1 influiscano su 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

Esempio 2: il rilascio di una colonna sulla copia può influire sull'originale

Questo in realtà illustra l'avvertimento.

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

È possibile evitare che le modifiche apportate su df2 influiscano su 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

Saluti!


4

Questo dovrebbe funzionare:

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

4

Alcuni potrebbero voler semplicemente sopprimere l'avviso:

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

Se la sezione è stata assegnata a una variabile e si desidera impostare utilizzando la variabile come indicato di seguito:

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

E non vuoi usare la soluzione Jeffs perché la tua condizione informatica df2è troppo lunga o per qualche altro motivo, quindi puoi usare quanto segue:

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

df2.index.tolist() restituisce gli indici da tutte le voci in df2, che verranno quindi utilizzati per impostare la colonna B nel frame di dati originale.


questo è 9 volte più costoso di df ["B"] = valore
Claudiu Creanga,

Puoi spiegarlo più in profondità @ClaudiuCreanga?
gies0r

2

Per me questo problema si è verificato in un esempio> semplificato <seguente. E sono stato anche in grado di risolverlo (speriamo con una soluzione corretta):

vecchio codice con avviso:

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

Questo ha stampato l'avvertimento per la linea old_row[field] = new_row[field]

Dato che le righe nel metodo update_row sono effettivamente di tipo Series, ho sostituito la riga con:

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

cioè metodo di accesso / ricerche per a Series. Anche se entrambi funzionano bene e il risultato è lo stesso, in questo modo non devo disabilitare gli avvisi (= conservali per altri problemi di indicizzazione della catena da qualche altra parte).

Spero che questo possa aiutare qualcuno.


2

Potresti evitare l'intero problema in questo modo, credo:

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

Utilizzando Assign. Dalla documentazione : assegnare nuove colonne a un DataFrame, restituendo un nuovo oggetto (una copia) con tutte le colonne originali oltre a quelle nuove.

Vedi l'articolo di Tom Augspurger sul concatenamento dei metodi nei panda: https://tomaugspurger.github.io/method-chaining


2

Domanda / osservazione per principianti di follow-up

Forse un chiarimento per altri principianti come me (vengo da R che sembra funzionare un po 'diversamente sotto il cofano). Il seguente codice funzionale e innocuo continuava a produrre l'avviso SettingWithCopy e non riuscivo a capire perché. Avevo letto e compreso l'emissione di "indicizzazione concatenata", ma il mio codice non contiene alcun:

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

Ma poi, troppo tardi, ho visto dove si chiama la funzione plot ():

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

Quindi "df" non è un frame di dati ma un oggetto che ricorda in qualche modo che è stato creato indicizzando un frame di dati (quindi è una vista?) Che renderebbe la linea in trama ()

 df['target'] = ...

equivalente a

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

che è un'indicizzazione concatenata. Ho capito bene?

Comunque,

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

aggiustato.


1

Poiché questa domanda è già completamente spiegata e discussa nelle risposte esistenti, fornirò semplicemente un pandasapproccio accurato al gestore del contesto utilizzando pandas.option_context(collegamenti a documenti ed esempi ) - non è assolutamente necessario creare una classe personalizzata con tutti i metodi di dunder e altre campane e fischietti.

Innanzitutto il codice del gestore del contesto stesso:

from contextlib import contextmanager

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

Quindi un esempio:

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

Vale la pena notare che entrambe le approvazioni non si modificano a, il che è un po 'sorprendente per me, e anche una copia df superficiale con .copy(deep=False)impedirebbe la generazione di questo avviso (per quanto ho capito la copia superficiale dovrebbe almeno modificare aanche, ma non lo fa è pandasmagia.).


hmmm, lo capisco se l'avvertimento sollevato qualcosa è sbagliato ovviamente, quindi è meglio evitare l'avvertimento come sopprimerlo, cosa pensi?
jezrael,

No, l'avvertimento è solo un avvertimento. Come qui, ti avverte che potrebbe esserci qualcosa che non va, il che è fantastico da sapere, ma se sai cosa e perché lo stai facendo è perfettamente bene sopprimerne alcuni. Vedere la spiegazione in stackoverflow.com/a/20627316/4272484 sulla riassegnazione dei riferimenti.
martedì

1

Avevo riscontrato questo problema .apply()durante l'assegnazione di un nuovo frame di dati da un frame di dati preesistente su cui ho utilizzato il .query()metodo. Per esempio:

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

Restituirebbe questo errore. La correzione che sembra risolvere l'errore in questo caso è cambiandola in:

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

Tuttavia, questo NON è efficace soprattutto quando si utilizzano frame di dati di grandi dimensioni, a causa della necessità di crearne una nuova.

Se stai usando il .apply()metodo per generare una nuova colonna e i suoi valori, una correzione che risolve l'errore ed è più efficiente è aggiungendo .reset_index(drop=True):

prop_df = df.query('column == "value"').reset_index(drop=True)
prop_df['new_column'] = prop_df.apply(function, axis=1)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.