Pandas: savoir quand une opération affecte le dataframe original

j'adore pandas et je l'utilise depuis des années et je me sens assez confiant j'ai une bonne maîtrise sur la façon de sous-traiter les images de données et de traiter les vues vs copies de manière appropriée (bien que j'utilise beaucoup d'assertions pour être sûr). Je sais aussi qu'il y a eu des tonnes de questions sur la mise en place D'un système D'alerte rapide, par exemple comment gérer la mise en place D'un système D'alerte rapide dans les Pandas? et quelques grands guides récents sur l'enroulement de votre tête quand il se produit, par exemple Understanding Settling Withcopywarning in pandas .

mais je sais aussi des choses spécifiques comme la citation de cette réponse ne sont plus dans les docs les plus récents ( 0.22.0 ) et que beaucoup de choses ont été dépréciées au cours des années (conduisant à quelques anciennes réponses so inappropriées), et que les choses sont continue de changer .

récemment après avoir enseigné les pandas aux nouveaux venus avec des connaissances générales de base en Python sur des choses comme éviter l'indexation enchaînée (et utiliser .iloc / .loc ), j'ai encore eu du mal à fournir règles générales du pouce pour savoir quand il est important de faire attention au SettingWithCopyWarning (par exemple quand il est sûr de l'ignorer).

j'ai personnellement constaté que le modèle spécifique de sous-traiter une base de données selon une règle (par exemple découpage ou opération booléenne) et puis modifier que sous-ensemble, indépendant de la base de données originale , est une opération beaucoup plus courante que les docs suggèrent. Dans cette situation, nous voulons modifier la copie pas l'original et l'avertissement est déroutant/effrayant pour les nouveaux venus.

je sais qu'il n'est pas anodin de savoir à l'avance quand une vue vs une copie est retournée, par exemple

quelles règles les Pandas utilisent-ils pour générer une vue vs une copie?

"vérifier si data frame est une copie ou une vue dans les Pandas

donc je cherche plutôt la réponse à une question plus générale (pour débutants): quand est-ce que l'exécution d'une opération sur une base de données subsetted affecte la base de données d'origine à partir de laquelle elle a été créée, et quand sont-ils indépendants? .

j'ai créé quelques cas ci-dessous qui me semblent raisonnables, mais je suis Je ne sais pas s'il y a un "gotcha" je suis manquant ou s'il y a une façon plus facile de penser/vérifier ceci. 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: No

Original changé: No

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

2) Avertissement: Oui (je ne comprends pas vraiment pourquoi)

Original changé: No

# 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) Avertissement: Oui

Original changé: No

# 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: No

Original changé: No

# 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) Avertissement: No

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'une nouvelle base de données à partir de l'original, changer la nouvelle base de données:

Va changer l'original quand scalaire/tranche d'indexation .loc/.iloc est utilisé pour créer la nouvelle base de données .

Va pas modifier l'original quand boolean d'indexation .loc, .query() , ou .copy() est utilisé pour créer la nouvelle base de données

27
demandé sur ejolly 2018-01-09 20:49:10

3 réponses

c'est une partie quelque peu confuse et même frustrante de pandas, mais pour la plupart, vous ne devriez pas vraiment avoir à vous soucier de cela si vous suivez quelques règles simples de flux de travail. En particulier, notez qu'il n'y a ici que deux cas généraux où vous avez deux dataframes, l'une é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.

Affaire

A: les changements à df2 ne devraient pas affecter df1

C'est trivial, bien sûr. Vous voulez deux images de données complètement indépendantes alors vous faites explicitement une copie:

df2 = df1.copy()

après cela, tout ce que vous faites à df2 n'affecte que df2 et non df1 et vice versa.

Cas B: les changements à df2 devraient également affecter df1

dans ce cas je ne pensez qu'il y a une façon générale de résoudre le problème parce que cela dépend de ce que vous essayez de faire exactement. Cependant, il existe quelques approches standard qui sont assez simples et ne devraient pas avoir d'ambiguïté sur la façon dont elles fonctionnent.

Méthode 1: copier df1 vers df2, puis utiliser df2 pour mettre à jour df1

dans ce cas, vous pouvez essentiellement faire une conversion un à un 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 via append est un peu verbeuse là. Vous pouvez le faire plus proprement avec ce qui suit, bien qu'il ait l'effet secondaire de convertir des entiers en flotteurs.

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

Méthode 2: Utiliser un masque (ne pas créer df2 du tout)

je pense que la meilleure approche générale ici n'est pas de créer df2 du tout, mais plutôt que ce soit un masqué version de df1 . Un peu malheureusement, vous ne pouvez pas faire une traduction directe du code ci-dessus en raison de son mélange de loc et iloc qui est très bien pour cet exemple mais probablement irréaliste pour l'utilisation réelle.

l'avantage est que vous pouvez écrire un code très simple et lisible. Voici une version alternative de l'exemple #2 ci-dessus où df2 est en fait juste une version masquée de df1 . Mais au lieu de changer via iloc , Je changerai 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 ici, mais c'est l'avantage inhérent de suivre "explicite est mieux qu'implicite".

1
répondu JohnE 2018-09-05 10:05:55

j'ai le même doute, j'ai cherché cette réponse, dans le passé, sans succès. Donc maintenant, je certifie juste que l'original ne change pas et utilise cette paix de code au programme au début pour supprimer les Avertissements:

 import pandas as pd
 pd.options.mode.chained_assignment = None  # default='warn'
0
répondu romulomadu 2018-02-05 04:45:35

il suffit de remplacer .iloc[0,1] par .iat[0,1] .

plus en général si vous voulez modifier un seul élément, vous devez utiliser la méthode .iat ou .at . Lorsque vous modifiez plusieurs éléments à la fois, vous devriez plutôt utiliser les méthodes .loc ou .iloc .

faisant ainsi pandas ne doit pas lancer d'avertissement.

0
répondu alububu 2018-02-18 12:45:13