cours
SettingWithCopyWarning dans Pandas : Comment corriger cet avertissement
SettingWithCopyWarning
est un avertissement que Pandas peut soulever lorsque nous effectuons une affectation à un DataFrame. Cela peut se produire lorsque nous utilisons des affectations enchaînées ou lorsque nous utilisons un DataFrame créé à partir d'une tranche. Il s'agit d'une source courante de bogues dans le code Pandas, à laquelle nous avons tous déjà été confrontés. Il peut être difficile de déboguer car l'avertissement peut apparaître dans un code qui semble fonctionner correctement.
Il est important de comprendre le site SettingWithCopyWarning
car il signale des problèmes potentiels liés à la manipulation des données. Cet avertissement indique que votre code ne modifie peut-être pas les données comme prévu, ce qui peut entraîner des conséquences imprévues et des bogues obscurs difficiles à détecter.
Dans cet article, nous allons explorer le site SettingWithCopyWarning
dans pandas et comment l'éviter. Nous discuterons également de l'avenir de Pandas et de la façon dont l'option copy_on_write
modifiera notre façon de travailler avec les DataFrames.
Vues et copies de DataFrame
Lorsque nous sélectionnons une tranche d'un DataFrame et que nous l'assignons à une variable, nous pouvons obtenir soit une vue, soit une nouvelle copie du DataFrame.
Dans le cas d'une vue, la mémoire est partagée entre les deux DataFrame. Cela signifie que la modification d'une valeur d'une cellule présente dans les deux DataFrame les modifiera toutes les deux.
Lors d'une copie, une nouvelle mémoire est allouée et un DataFrame indépendant contenant les mêmes valeurs que l'original est créé. Dans ce cas, les deux DataFrame sont des entités distinctes, de sorte que la modification d'une valeur dans l'un d'eux n'affecte pas l'autre.
Pandas essaie d'éviter de créer une copie lorsqu'il le peut afin d'optimiser les performances. Cependant, il est impossible de prévoir à l'avance si nous obtiendrons une vue ou une copie. La question SettingWithCopyWarning
est soulevée chaque fois que nous attribuons une valeur à un DataFrame dont on ne sait pas s'il s'agit d'une copie ou d'une vue d'un autre DataFrame.
Comprendre la SettingWithCopyWarning
avec des données réelles
Nous utiliserons ce jeu de données Kaggle Real Estate Data London 2024 pour apprendre comment SettingWithCopyWarning
se produit et comment y remédier.
Cet ensemble de données contient des données récentes sur l'immobilier à Londres. Voici un aperçu des colonnes présentes dans l'ensemble de données :
addedOn
: La date à laquelle l'inscription a été ajoutée.title
: Le titre de l'annonce.descriptionHtml
: Une description HTML de l'annonce.propertyType
: Le type de bien. La valeur sera"Not Specified"
si le type n'a pas été spécifié.sizeSqFeetMax
: La taille maximale en pieds carrés.bedrooms
: Le nombre de chambres à coucher.listingUpdatedReason
: Raison de la mise à jour de l'annonce (par exemple, nouvelle annonce, réduction de prix).price
: Le prix de l'annonce en livres sterling.
Exemple avec une variable temporaire explicite
Supposons que l'on nous dise que les biens dont le type n'est pas spécifié sont des maisons. Nous voulons donc mettre à jour toutes les lignes où propertyType
est égal à "Not Specified"
en "House"
. Une façon de procéder consiste à filtrer les lignes dont le type de propriété n'est pas spécifié dans une variable DataFrame temporaire et à mettre à jour les valeurs de la colonne propertyType
de la façon suivante :
import pandas as pd
dataset_name = "realestate_data_london_2024_nov.csv"
df = pd.read_csv(dataset_name)
# Obtain all rows with unspecified property type
no_property_type = df[df["propertyType"] == "Not Specified"]
# Update the property type to “House” on those rows
no_property_type["propertyType"] = "House"
En exécutant ce code, pandas produira le fichier SettingWithCopyWarning
:
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
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
no_property_type["propertyType"] = "House"
La raison en est que pandas ne peut pas savoir si le DataFrame no_property_type
est une vue ou une copie de df
.
C'est un problème car le comportement du code suivant peut être très différent selon qu'il s'agit d'une vue ou d'une copie.
Dans cet exemple, notre objectif est de modifier le DataFrame original. Cela ne se produira que si no_property_type
est une vue. Si le reste de notre code suppose que df
a été modifié, il peut se tromper car il n'y a aucun moyen de garantir que c'est le cas. En raison de ce comportement incertain, Pandas lance un avertissement pour nous en informer.
Même si notre code s'exécute correctement parce que nous avons obtenu une vue, nous pourrions en obtenir une copie lors d'exécutions ultérieures, et le code ne fonctionnerait pas comme prévu. Il est donc important de ne pas ignorer cet avertissement et de s'assurer que notre code fera toujours ce que nous voulons qu'il fasse.
Exemple avec une variable temporaire cachée
Dans l'exemple précédent, il est clair qu'une variable temporaire est utilisée car nous affectons explicitement une partie du DataFrame à une variable nommée no_property_type
.
Toutefois, dans certains cas, ce n'est pas aussi explicite. L'exemple le plus courant de SettingWithCopyWarning
est celui de l'indexation en chaîne. Supposons que nous remplacions les deux dernières lignes par une seule :
df[df["propertyType"] == "Not Specified"]["propertyType"] = "House"
À première vue, il ne semble pas qu'une variable temporaire soit créée. Toutefois, l'exécution de cette procédure entraîne également l'envoi d'un message à l'adresse SettingWithCopyWarning
.
La façon dont ce code est exécuté est la suivante :
df[df["propertyType"] == "Not Specified"]
est évaluée et stockée temporairement en mémoire.- L'index
["propertyType"]
de cet emplacement de mémoire temporaire est consulté.
Les accès à l'index sont évalués un par un et, par conséquent, l'indexation en chaîne donne lieu au même avertissement car nous ne savons pas si les résultats intermédiaires sont des vues ou des copies. Le code ci-dessus est essentiellement le même que le code suivant :
tmp = df[df["propertyType"] == "Not Specified"]
tmp["propertyType"] = "House"
Cet exemple est souvent appelé indexation chaînée car nous enchaînons les accès indexés à l'aide de []
. Tout d'abord, nous accédons à [df["propertyType"] == "Not Specified"]
puis à ["propertyType"]
.
Comment résoudre le problème de la SettingWithCopyWarning
Nous allons apprendre à écrire notre code de manière à ce qu'il n'y ait pas d'ambiguïté et que le site SettingWithCopyWarning
ne soit pas déclenché. Nous avons appris que l'avertissement provient d'une ambiguïté sur la question de savoir si un DataFrame est une vue ou une copie d'un autre DataFrame.
La solution consiste à s'assurer que chaque DataFrame que nous créons est une copie si nous voulons qu'elle soit une copie ou une vue si nous voulons qu'elle soit une vue.
Modifier en toute sécurité le DataFrame d'origine avec loc
Corrigeons le code de l'exemple ci-dessus où nous voulons modifier le DataFrame original. Pour éviter d'utiliser une variable temporaire, utilisez la propriété loc
indexer.
df.loc[df["propertyType"] == "Not Specified", "propertyType"] = "House"
Avec ce code, nous agissons directement sur le DataFrame original df
via la propriété loc
indexer, il n'y a donc pas besoin de variables intermédiaires. C'est ce que nous devons faire lorsque nous voulons modifier directement le DataFrame d'origine.
À première vue, cela peut ressembler à une indexation en chaîne, car il y a encore des paramètres, mais ce n'est pas le cas. Les crochets []
définissent chaque indexation.
Notez que l'utilisation de loc
n'est sûre que si vous affectez directement une valeur, comme nous l'avons fait ci-dessus. Si nous utilisons plutôt une variable temporaire, nous retombons dans le même problème. Voici deux exemples de codes qui ne résolvent pas le problème :
- Utilisation de
loc
avec une variable temporaire :
# Using loc plus temporary variable doesn’t fix the issue
no_property_type = df.loc[df["propertyType"] == "Not Specified"]
no_property_type["propertyType"] = "House"
- Utilisation de
loc
avec un index (identique à l'indexation en chaîne) :
# Using loc plus indexing is the same as chained indexing
df.loc[df["propertyType"] == "Not Specified"]["propertyType"] = "House"
Ces deux exemples ont tendance à troubler les gens parce que l'on croit souvent, à tort, que tant qu'il y a une adresse loc
, on modifie les données d'origine. Ce n'est pas le cas. La seule façon de s'assurer que la valeur est affectée au DataFrame d'origine est de l'affecter directement à l'aide d'un seul loc
sans indexation séparée.
Travailler en toute sécurité avec une copie du DataFrame original avec copy()
Lorsque nous voulons nous assurer que nous opérons sur une copie du DataFrame, nous devons utiliser la méthode suivante la méthode .copy()
.
Supposons que l'on nous demande d'analyser le prix au mètre carré des biens immobiliers. Nous ne voulons pas modifier les données originales. L'objectif est de créer un nouveau DataFrame avec les résultats de l'analyse pour l'envoyer à une autre équipe.
La première étape consiste à filtrer certaines lignes et à nettoyer les données. Plus précisément, nous devons
- Supprimez les lignes où
sizeSqFeetMax
n'est pas défini. - Supprimez les lignes où le site
price
est"POA"
(prix sur demande). - Convertissez les prix en valeurs numériques (dans l'ensemble de données original, les prix sont des chaînes au format suivant :
"£25,000,000"
).
Nous pouvons effectuer les étapes ci-dessus à l'aide du code suivant :
# 1. Filter out all properties without a size or a price
properties_with_size_and_price = df[df["sizeSqFeetMax"].notna() & (df["price"] != "POA")]
# 2. Remove the £ and , characters from the price columns
properties_with_size_and_price["price"] = properties_with_size_and_price["price"].str.replace("£", "", regex=False).str.replace(",", "", regex=False)
# 3. Convert the price column to numeric values
properties_with_size_and_price["price"] = pd.to_numeric(properties_with_size_and_price["price"])
Pour calculer le prix au pied carré, nous créons une nouvelle colonne dont les valeurs sont le résultat de la division de la colonne price
par la colonne sizeSqFeetMax
:
properties_with_size_and_price["pricePerSqFt"] = properties_with_size_and_price["price"] / properties_with_size_and_price["sizeSqFeetMax"]
Si nous exécutons ce code, nous obtenons à nouveau le site SettingWithCopyWarning
. Cela ne devrait pas être une surprise car nous avons explicitement créé et modifié une variable DataFrame temporaire properties_with_size_and_price
.
Puisque nous voulons travailler sur une copie des données plutôt que sur le DataFrame original, nous pouvons résoudre le problème en nous assurant que properties_with_size_and_price
est une copie fraîche du DataFrame et non une vue en utilisant la méthode la méthode .copy()
sur la première ligne :
properties_with_size_and_price = df[df["sizeSqFeetMax"].notna() & (df["price"] != "POA")].copy()
Ajouter de nouvelles colonnes en toute sécurité
La création de nouvelles colonnes se comporte de la même manière que l'attribution de valeurs. Chaque fois qu'il est ambigu de savoir si nous travaillons avec une copie ou une vue, pandas lèvera un SettingWithCopyWarning
.
Si nous voulons travailler avec une copie des données, nous devons les copier explicitement à l'aide de la méthode .copy()
. Ensuite, nous sommes libres d'attribuer une nouvelle colonne de la manière que nous souhaitons. C'est ce que nous avons fait en créant la colonne pricePerSqFt
dans l'exemple précédent.
En revanche, si nous voulons modifier le DataFrame d'origine, il y a deux cas à considérer.
- Si la nouvelle colonne s'étend sur toutes les lignes, nous pouvons modifier directement le DataFrame d'origine. Cela ne provoquera pas d'avertissement car nous ne sélectionnerons pas un sous-ensemble de lignes. Par exemple, nous pourrions ajouter une colonne
note
pour chaque ligne où le type de maison est manquant :
df["notes"] = df["propertyType"].apply(lambda house_type: "Missing house type" if house_type == "Not Specified" else "")
- Si la nouvelle colonne ne définit des valeurs que pour un sous-ensemble de lignes, nous pouvons utiliser la propriété d'indexation
loc
. Par exemple :
df.loc[df["propertyType"] == "Not Specified", "notes"] = "Missing house type"
Notez que dans ce cas, la valeur des colonnes qui n'ont pas été sélectionnées sera indéfinie. La première approche est donc préférable car elle nous permet de spécifier une valeur pour chaque ligne.
SettingWithCopyWarning
Erreur dans Pandas 3.0
Pour l'instant, SettingWithCopyWarning
n'est qu'un avertissement, pas une erreur. Notre code est toujours exécuté, et Pandas nous informe simplement de faire attention.
Selon la documentation la documentation officielle de PandasSettingWithCopyWarning
ne sera plus utilisé à partir de la version 3.0 et sera remplacé par une véritable fonction erreur par défaut, ce qui permettra d'appliquer des normes de code plus strictes.
Pour s'assurer que notre code reste compatible avec les futures versions de pandas, il est recommandé de le mettre à jour pour qu'il génère une erreur au lieu d'un avertissement.
Pour ce faire, vous devez définir l'option suivante après l'importation de pandas :
import pandas as pd
pd.options.mode.copy_on_write = True
En ajoutant cela au code existant, nous nous assurons de traiter chaque affectation ambiguë dans notre code et nous nous assurons que le code fonctionne toujours lorsque nous mettons à jour pandas 3.0.
Conclusion
Le site SettingWithCopyWarning
apparaît chaque fois que notre code rend ambiguë la question de savoir si une valeur que nous modifions est une vue ou une copie. Nous pouvons y remédier en étant toujours explicites sur ce que nous voulons :
- Si nous voulons travailler avec une copie, nous devons la copier explicitement en utilisant la méthode
copy()
. - Si nous voulons modifier le DataFrame original, nous devons utiliser la propriété
loc
indexer et assigner la valeur directement lors de l'accès aux données sans utiliser de variables intermédiaires.
Bien qu'il ne s'agisse pas d'une erreur, nous ne devrions pas ignorer cet avertissement car il peut conduire à des résultats inattendus. De plus, à partir de Pandas 3.0, cela deviendra une erreur par défaut, nous devrions donc protéger notre code en activant la fonction Copy-on-Write dans notre code actuel à l'aide de la commande pd.options.mode.copy_on_write = True
. Cela permettra de s'assurer que le code reste fonctionnel pour les futures versions de Pandas.
FAQ
Pourquoi est-ce que j'obtiens l'avertissement SettingWithCopyWarning même lorsque j'utilise .loc ?
L'utilisation de loc
est toujours sûre et ne déclenche pas d'avertissement ni n'entraîne de comportement inattendu, à condition que df
n'ait pas été créé à partir d'un autre DataFrame. Par exemple, si df
est le DataFrame qui a été créé à partir de la lecture de l'ensemble de données, il n'y a pas de danger. Toutefois, s'il s'agit du résultat d'un calcul intermédiaire, il n'est sûr que si le DataFrame a été copié à l'aide de la méthode copy()
.
Comment supprimer l'avertissement SettingWithCopy ?
Nous pouvons utiliser le paquet warnings
et ajouter le code :
import warnings
warnings.simplefilter(action='ignore', category=pd.errors.SettingWithCopyWarning)
Cependant, il est fortement conseillé de ne pas supprimer ou ignorer cet avertissement, car il signifie que votre code ne fait peut-être pas ce que vous pensez qu'il fait.
Comment ignorer l'avertissement SettingWithCopy ?
Nous pouvons ignorer l'avertissement en utilisant temporairement le paquetage warnings
et en enveloppant notre code avec :
import warnings
with warnings.catch_warnings():
warnings.simplefilter("ignore", category=pd.errors.SettingWithCopyWarning)
# Our code goes here
Cependant, il est fortement conseillé de ne pas supprimer ou ignorer cet avertissement, car il signifie que votre code ne fait peut-être pas ce que vous pensez qu'il fait.
Comment corriger l'avertissement SettingWithCopyWarning lors de l'ajout d'une nouvelle colonne ?
La correction de l'avertissement lors de l'ajout d'une colonne se fait de la même manière que pour l'attribution de valeurs. Veillez à copier d'abord le DataFrame si vous avez l'intention de travailler sur une copie des données ou d'utiliser loc
.
J'ai suivi ce guide et j'obtiens toujours l'avertissement SettingWithCopyWarning. Comment puis-je y remédier ?
Sur les versions les plus récentes de pandas, suivre les pratiques de ce tutoriel ne devrait pas déclencher d'avertissement.
Si vous utilisez une version plus ancienne, nous vous conseillons de la mettre à jour vers la dernière version stable. Les versions antérieures sont connues pour avoir des bogues autour de cet avertissement et pour le déclencher alors qu'il n'y a pas de problème avec le code.
Si la version de pandas est à jour, il est probable que le problème sous-jacent soit un cas d'indexation en chaîne non évident. Examinez attentivement la manière dont le DataFrame a été créé.
Apprenez les Pandas avec ces cours !
cours
Data Manipulation with pandas
cours
Analyzing Marketing Campaigns with pandas
blog
Les 20 meilleures questions d'entretien pour les flocons de neige, à tous les niveaux
Nisha Arya Ahmed
20 min
blog