Come affrontare SettingWithCopyWarning
in Panda?
Questo post è destinato a lettori che,
- Vorrei capire cosa significa questo avviso
- Vorrei comprendere diversi modi per sopprimere questo avviso
- 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, è SettingWithCopyWarning
stato creato per contrassegnare le operazioni di "assegnazione incatenata". Considerare df
nella 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 loc
per l'assegnazione basata sull'etichetta e iloc
per l' assegnazione basata su numero intero / posizionale, poiché la specifica garantisce che funzionano sempre sull'originale. Inoltre, per impostare una singola cella, è necessario utilizzare at
e iat
.
Maggiori informazioni sono disponibili nella documentazione .
Nota È anche possibile eseguire
tutte le operazioni di indicizzazione booleane loc
eseguite con iloc
. L'unica differenza è che si iloc
aspettano 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:
Fare un deepcopy
df2 = df[['A']].copy(deep=True)
df2['A'] /= 2
Cambiamentopd.options.mode.chained_assignment
può essere impostata su None
, "warn"
o "raise"
. "warn"
è l'impostazione predefinita. None
sopprimerà 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 df2
da qualcosa di più grande, come
df2 = df[df.A > 5]
? In questo caso, l'indicizzazione booleana restituirà una vista, quindi df2
farà riferimento all'originale. Quello che dovresti fare è assegnare df2
a 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é df2
deve 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.