72 votes

Pandas: Savoir quand une opération affecte le dataframe d'origine

J'aime les pandas et ont été utilisé pendant des années et crois bien que j'ai une bonne poignée sur la façon de sous-ensemble dataframes et de traiter avec vue sur vs des copies de façon appropriée (même si j'utilise beaucoup d'affirmations pour être sûr). Je sais aussi qu'il y a eu des tonnes de questions sur SettingWithCopyWarning, par exemple, la Façon de traiter avec SettingWithCopyWarning dans les Pandas? et certains grands dernières guides de l'habillage autour de votre tête quand il arrive, par exemple, la Compréhension SettingWithCopyWarning dans les pandas.

Mais je sais aussi des choses spécifiques comme la citation de cette réponse ne sommes plus dans la plus récente docs (0.22.0) et que beaucoup de choses ont été dépréciés au cours des années (pour certains inapproprié vieux réponses), et que la situation continue d'évoluer.

Récemment, après l'enseignement de pandas à des nouveaux venus avec de très de base en Python connaissances sur des choses comme éviter les enchaînés-indexation (et l'aide d' .iloc/.loc- )), je l'ai toujours mal à fournir des règles générales de base pour savoir quand il est important de prêter attention à l' SettingWithCopyWarning (par exemple, quand il est sûr d'ignorer).

J'ai personnellement trouvé que le motif spécifique de subsetting un dataframe selon de sorte que certains de la règle (par exemple, de découpage ou de l'opération booléenne) et puis de modifier ce sous-ensemble, indépendants de l'original dataframe, il est beaucoup plus commun que les docs suggèrent. Dans cette situation, nous voulons modifier la copie et non l'original , et que l'avertissement est source de confusion/effrayant pour les nouveaux arrivants.

Je sais que c'est pas trivial de savoir à l'avance quand une vue par rapport à un exemplaire est retourné, par exemple
Quelles règles Pandas utiliser pour générer une vue vs une copie?
Vérifier si le bloc de données est de copier ou de la vue dans les Pandas

Donc à la place je suis à la recherche de la réponse à un plus générale (débutants) question: quand l'exécution d'une opération sur un incorporée dans un jeu partiel dataframe affecter l'original dataframe à partir de laquelle il a été créé, et quand sont-ils indépendants?.

J'ai créé certains cas ci-dessous que je pense sembler raisonnable, mais je ne sais pas si il y a une "chasse aux sorcières" je suis absent ou s'il ya un moyen plus facile de penser/vérifier ce point. J'espérais que quelqu'un pouvait confirmer mes intuitions sur les cas d'utilisation suivants sont correctes, comme le rapportent à ma question ci-dessus.

import pandas as pd
df1 = pd.DataFrame({'A':[2,4,6,8,10],'B':[1,3,5,7,9],'C':[10,20,30,40,50]})

1) Avertissement: Aucun
Version originale a été modifiée: Aucune

# df1 will be unaffected because we use .copy() method explicitly 
df2 = df1.copy()
#
# Reference: docs
df2.iloc[0,1] = 100

2) Avertissement: Oui (je n'ai pas vraiment compris pourquoi)
Version originale a été modifiée: Aucune

# df1 will be unaffected because .query() always returns a copy
#
# Reference:
# https://stackoverflow.com/a/23296545/8022335
df2 = df1.query('A < 10')
df2.iloc[0,1] = 100

3) Mise En Garde: Oui
Version originale a été modifiée: Aucune

# df1 will be unaffected because boolean indexing with .loc
# always returns a copy
#
# Reference:
# https://stackoverflow.com/a/17961468/8022335
df2 = df1.loc[df1['A'] < 10,:]
df2.iloc[0,1] = 100

4) Avertissement: Aucun
Version originale a été modifiée: Aucune

# df1 will be unaffected because list indexing with .loc (or .iloc)
# always returns a copy
#
# Reference:
# Same as 4)
df2 = df1.loc[[0,3,4],:]
df2.iloc[0,1] = 100

5) Attention: Pas De
Version originale a été modifiée: Oui (source de confusion pour les nouveaux arrivants, mais qui a du sens)

# df1 will be affected because scalar/slice indexing with .iloc/.loc
# always references the original dataframe, but may sometimes 
# provide a view and sometimes provide a copy
#
# Reference: docs
df2 = df1.loc[:10,:]
df2.iloc[0,1] = 100

tl;dr Lors de la création d'un nouveau dataframe de l'original, la modification de la nouvelle dataframe:
Va modifier l'original lors d'un scalaire/tranche d'indexation avec .loc/.lci est utilisé pour créer la nouvelle dataframe.
Va pas modifier l'original quand boolean indexation avec .loc, .query()ou .copy() est utilisé pour créer la nouvelle dataframe

20voto

JohnE Points 12788

C'est un peu déroutant, et même frustrant de pandas, mais pour la plupart, vous ne devriez pas vraiment à vous soucier de ce que si vous suivez quelques simples règles de workflow. En particulier, la note qu'il n'existe que deux cas ici, quand vous avez deux dataframes, l'un étant un sous-ensemble de l'autre.

C'est un cas où le Zen de Python règle "explicite est mieux qu'implicite" est une grande ligne directrice à suivre.

Cas A: Changements d' df2 ne devrait PAS affecter df1

C'est trivial, bien sûr. Vous voulez deux complètement indépendant dataframes donc, vous venez de faire explicitement une copie:

df2 = df1.copy()

Après cela, tout ce que vous faites pour df2 affecte seulement df2 et pas df1 , et vice-versa.

Cas B: Modifications apportées à l' df2 devrait ÉGALEMENT affecter df1

Dans ce cas, je ne pense pas qu'il existe une manière générale, pour résoudre le problème, car il dépend exactement ce que vous essayez de faire. Cependant, il ya un couple d'approches qui sont assez simple et ne devrait avoir aucune ambiguïté sur la façon dont ils travaillent.

Méthode 1: Copie df1 à df2, puis utilisez df2 pour mettre à jour df1

Dans ce cas, vous pouvez vous faire un-à-un de conversion des exemples ci-dessus. Voici l'exemple n ° 2:

df2 = df1.copy()
df2 = df1.query('A < 10')
df2.iloc[0,1] = 100

df1 = df2.append(df1).reset_index().drop_duplicates(subset='index').drop(columns='index')

Malheureusement, la re-fusion par append est un peu verbeux là. Vous pouvez le faire plus proprement par la suite, même si elle a pour effet de bord de la conversion des entiers de flotteurs.

df1.update(df2)   # note that this is an inplace operation

Méthode 2: Utiliser un masque (ne pas créer de df2 à tous)

Je pense que la meilleure approche générale ici n'est pas de créer df2 , mais plutôt à ce qu'il soit masqué version de df1. Malheureusement, vous ne pouvez pas faire une traduction directe du code ci-dessus en raison de son mélange de loc et iloc , ce qui est bien pour cet exemple, bien que probablement peu réaliste pour une utilisation réelle.

L'avantage est que vous pouvez écrire très simple et lisible le code. Voici une version alternative de l'exemple 2 ci-dessus où l' df2 est en fait juste un masqués version de df1. Mais au lieu de le remplacer par iloc, je vais changer si la colonne "C" == 10.

df2_mask = df1['A'] < 10
df1.loc[ df2_mask & (df1['C'] == 10), 'B'] = 100

Maintenant, si vous imprimez df1 ou df1[df2_mask] vous verrez que la colonne "B" = 100 pour la première ligne de chaque dataframe. Évidemment, ce n'est pas très surprenant, mais c'est l'avantage de la suite de "explicite est mieux que l'implicite".

0voto

romulomadu Points 369

J'ai le même doute, j'ai cherché cette réponse dans le passé sans succès. Alors maintenant, je viens de certifier que l'original ne change pas et d'utiliser cette paix de code pour le programme au début de supprimer les avertissements:

-5voto

alububu Points 11

Vous n'avez qu'à remplacer par .

Plus en général si vous souhaitez modifier un seul élément que vous devez utiliser ou méthode. Au lieu de cela, lorsque vous modifiez plus d'éléments à la fois, vous devriez utiliser ou des méthodes.

Faire de cette façon pandas shuldn't jeter un avertissement.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X