Comment faire face aux SettingWithCopyWarning
Pandas?
Ce message est destiné aux lecteurs qui,
- Voudrais comprendre ce que cet avertissement signifie
- Aimerait comprendre différentes façons de supprimer cet avertissement
- Souhaite comprendre comment améliorer son code et suivre les bonnes pratiques pour éviter cet avertissement à l'avenir.
Installer
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
C'est quoi SettingWithCopyWarning
?
Pour savoir comment gérer cet avertissement, il est important de comprendre ce qu'il signifie et pourquoi il est émis en premier lieu.
Lors du filtrage des DataFrames, il est possible de découper / indexer un cadre pour renvoyer soit une vue , soit une copie , selon la disposition interne et divers détails d'implémentation. Une "vue" est, comme le terme l'indique, une vue dans les données d'origine, donc la modification de la vue peut modifier l'objet d'origine. D'un autre côté, une "copie" est une réplication des données de l'original, et la modification de la copie n'a aucun effet sur l'original.
Comme mentionné dans d'autres réponses, le a SettingWithCopyWarning
été créé pour signaler les opérations d '"affectation chaînée". Considérez df
dans la configuration ci-dessus. Supposons que vous souhaitiez sélectionner toutes les valeurs de la colonne "B" où les valeurs de la colonne "A" sont> 5. Pandas vous permet de le faire de différentes manières, certaines plus correctes que d'autres. Par exemple,
df[df.A > 5]['B']
1 3
2 6
Name: B, dtype: int64
Et,
df.loc[df.A > 5, 'B']
1 3
2 6
Name: B, dtype: int64
Ceux-ci renvoient le même résultat, donc si vous ne lisez que ces valeurs, cela ne fait aucune différence. Alors, quel est le problème? Le problème avec l'affectation chaînée, c'est qu'il est généralement difficile de prédire si une vue ou une copie est retournée, donc cela devient largement un problème lorsque vous essayez de réattribuer des valeurs. Pour construire sur l'exemple précédent, considérez comment ce code est exécuté par l'interpréteur:
df.loc[df.A > 5, 'B'] = 4
# becomes
df.__setitem__((df.A > 5, 'B'), 4)
Avec un seul __setitem__
appel à df
. OTOH, considérez ce code:
df[df.A > 5]['B'] = 4
# becomes
df.__getitem__(df.A > 5).__setitem__('B", 4)
Maintenant, selon qu'il a __getitem__
renvoyé une vue ou une copie, l' __setitem__
opération peut ne pas fonctionner .
En général, vous devez utiliser loc
pour l'affectation basée sur les étiquettes et iloc
pour l' affectation basée sur les nombres entiers / positionnels, car la spécification garantit qu'ils fonctionnent toujours sur l'original. De plus, pour définir une seule cellule, vous devez utiliser at
etiat
.
Plus d'informations peuvent être trouvées dans la documentation .
Remarque
Toutes les opérations d'indexation booléennes effectuées avec loc
peuvent également être effectuées avec iloc
. La seule différence est queiloc
attend soit des entiers / positions pour l'index, soit un tableau numpy de valeurs booléennes, et des index entiers / position pour les colonnes.
Par exemple,
df.loc[df.A > 5, 'B'] = 4
Peut être écrit nas
df.iloc[(df.A > 5).values, 1] = 4
Et,
df.loc[1, 'A'] = 100
Peut être écrit comme
df.iloc[1, 0] = 100
Etc.
Dites-moi simplement comment supprimer l'avertissement!
Considérez une opération simple sur la colonne "A" de df
. La sélection de "A" et la division par 2 déclencheront l'avertissement, mais l'opération fonctionnera.
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
Il existe deux façons de désactiver directement cet avertissement:
Faire un deepcopy
df2 = df[['A']].copy(deep=True)
df2['A'] /= 2
Changementpd.options.mode.chained_assignment
peut -il être réglé sur None
, "warn"
ou "raise"
. "warn"
est la valeur par défaut. None
supprimera complètement l'avertissement et "raise"
lancera un SettingWithCopyError
, empêchant l'opération de se poursuivre.
pd.options.mode.chained_assignment = None
df2['A'] /= 2
@Peter Cotton dans les commentaires, a trouvé une belle façon de changer le mode de manière non intrusive (modifié à partir de cet essentiel ) en utilisant un gestionnaire de contexte, pour définir le mode uniquement aussi longtemps qu'il est nécessaire, et le réinitialiser à la état d'origine une fois terminé.
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'utilisation est la suivante:
# some code here
with ChainedAssignent():
df2['A'] /= 2
# more code follows
Ou, pour soulever l'exception
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
Le "problème XY": qu'est-ce que je fais mal?
La plupart du temps, les utilisateurs tentent de rechercher des moyens de supprimer cette exception sans bien comprendre pourquoi elle a été levée en premier lieu. Ceci est un bon exemple d'un problème XY , où les utilisateurs tentent de résoudre un problème "Y" qui est en fait le symptôme d'un problème enraciné plus profond "X". Des questions seront posées sur la base des problèmes courants rencontrés par cet avertissement, et des solutions seront ensuite présentées.
Question 1
J'ai 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
Je veux affecter des valeurs dans le col "A"> 5 à 1000. Ma sortie attendue est
A B C D E
0 5 0 3 3 7
1 1000 3 5 2 4
2 1000 6 8 8 1
Mauvaise façon de procéder:
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
Bonne façon d'utiliser loc
:
df.loc[df.A > 5, 'A'] = 1000
Question 2 1
J'essaie de définir la valeur de la cellule (1, 'D') sur 12345. Ma sortie attendue est
A B C D E
0 5 0 3 3 7
1 9 3 5 12345 4
2 7 6 8 8 1
J'ai essayé différentes façons d'accéder à cette cellule, telles que
df['D'][1]
. Quelle est la meilleure façon de procéder?
1. Cette question n'est pas spécifiquement liée à l'avertissement, mais il est bon de comprendre comment effectuer correctement cette opération particulière afin d'éviter des situations où l'avertissement pourrait éventuellement se produire à l'avenir.
Vous pouvez utiliser l'une des méthodes suivantes pour ce faire.
df.loc[1, 'D'] = 12345
df.iloc[1, 3] = 12345
df.at[1, 'D'] = 12345
df.iat[1, 3] = 12345
Question 3
J'essaie de sous-définir les valeurs en fonction d'une condition. J'ai un DataFrame
A B C D E
1 9 3 5 2 4
2 7 6 8 8 1
Je voudrais attribuer des valeurs de "D" à 123 telles que "C" == 5. J'ai essayé
df2.loc[df2.C == 5, 'D'] = 123
Ce qui semble bien mais je reçois toujours le
SettingWithCopyWarning
! Comment puis-je réparer ça?
C'est en fait probablement à cause du code plus haut dans votre pipeline. Avez-vous créé df2
quelque chose de plus grand, comme
df2 = df[df.A > 5]
? Dans ce cas, l'indexation booléenne retournera une vue, donc df2
référencera l'original. Ce que vous devez faire est d'assigner df2
à une copie :
df2 = df[df.A > 5].copy()
# Or,
# df2 = df.loc[df.A > 5, :]
Question 4
J'essaie de supprimer la colonne "C" en place de
A B C D E
1 9 3 5 2 4
2 7 6 8 8 1
Mais en utilisant
df2.drop('C', axis=1, inplace=True)
Jette SettingWithCopyWarning
. Pourquoi cela arrive-t-il?
En effet, df2
doit avoir été créé en tant que vue à partir d'une autre opération de découpage, telle que
df2 = df[df.A > 5]
La solution est ici pour faire soit un copy()
des df
, ou l' utilisation loc
, comme avant.