pourquoi devrais-je faire une copie d'une trame de données dans pandas


189

Lors de la sélection d'une sous-trame de données à partir d'une trame de données parent, j'ai remarqué que certains programmeurs font une copie de la trame de données à l'aide de la .copy()méthode. Par exemple,

X = my_dataframe[features_list].copy()

... au lieu de juste

X = my_dataframe[features_list]

Pourquoi font-ils une copie de la trame de données? Que se passera-t-il si je ne fais pas de copie?


6
Je suppose qu'ils prennent des précautions supplémentaires pour ne pas modifier la base de données source. Probablement inutile, mais lorsque vous lancez quelque chose de manière interactive, mieux vaut prévenir que guérir.
Paul H

8
Je suppose que ce n'est pas une question stupide pour donner une question négative.
Elizabeth Susan Joseph

Réponses:


207

Cela élargit la réponse de Paul. Dans Pandas, l'indexation d'un DataFrame renvoie une référence au DataFrame initial. Ainsi, la modification du sous-ensemble modifiera le DataFrame initial. Ainsi, vous voudrez utiliser la copie si vous voulez vous assurer que le DataFrame initial ne doit pas changer. Considérez le code suivant:

df = DataFrame({'x': [1,2]})
df_sub = df[0:1]
df_sub.x = -1
print(df)

Tu auras:

x
0 -1
1  2

En revanche, ce qui suit laisse df inchangé:

df_sub_copy = df[0:1].copy()
df_sub_copy.x = -1

6
est-ce une copie complète?
bikashg

6
Oui. Le mode par défaut est la copie "profonde"! pandas.pydata.org/pandas-docs/stable/reference/api/…
Ambareesh

44

Parce que si vous ne faites pas de copie, les index peuvent toujours être manipulés ailleurs, même si vous affectez le dataFrame à un nom différent.

Par exemple:

df2 = df
func1(df2)
func2(df)

func1 peut modifier df en modifiant df2, donc pour éviter que:

df2 = df.copy()
func1(df2)
func2(df)

Attendez, attendez, pouvez-vous expliquer POURQUOI cela se produit? Ça n'a pas de sens.
NoName

2
c'est parce que dans le premier exemple, `df2 = df , both variables reference the same DataFrame instance. So any changes made to df` ou df2sera fait sur la même instance d'objet. Alors que dans la df2 = df.copy()seconde instance d'objet est créée, une copie de la première, mais maintenant dfet df2référence à différentes instances d'objet et toutes les modifications seront apportées à leur instance DataFrame respective.
Pedro le

17

Il est nécessaire de mentionner que le retour de la copie ou de la vue dépend du type d'indexation.

La documentation des pandas dit:

Retour d'une vue par rapport à une copie

Les règles sur le moment où une vue sur les données est renvoyée dépendent entièrement de NumPy. Chaque fois qu'un tableau d'étiquettes ou un vecteur booléen est impliqué dans l'opération d'indexation, le résultat sera une copie. Avec une seule étiquette / indexation et découpage scalaire, par exemple df.ix [3: 6] ou df.ix [:, 'A'], une vue sera renvoyée.



12

L'objectif principal est d'éviter l'indexation chaînée et d'éliminer les fichiers SettingWithCopyWarning.

Ici, l'indexation chaînée est quelque chose comme dfc['A'][0] = 111

Le document a déclaré que l'indexation chaînée devrait être évitée dans Renvoyer une vue par rapport à une copie . Voici un exemple légèrement modifié de ce document:

In [1]: import pandas as pd

In [2]: dfc = pd.DataFrame({'A':['aaa','bbb','ccc'],'B':[1,2,3]})

In [3]: dfc
Out[3]:
    A   B
0   aaa 1
1   bbb 2
2   ccc 3

In [4]: aColumn = dfc['A']

In [5]: aColumn[0] = 111
SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

In [6]: dfc
Out[6]:
    A   B
0   111 1
1   bbb 2
2   ccc 3

Ici, il aColumns'agit d'une vue et non d'une copie du DataFrame d'origine, donc la modification aColumnentraînera également la modification de l'original dfc. Ensuite, si nous indexons la ligne en premier:

In [7]: zero_row = dfc.loc[0]

In [8]: zero_row['A'] = 222
SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

In [9]: dfc
Out[9]:
    A   B
0   111 1
1   bbb 2
2   ccc 3

Cette fois zero_rowest une copie, donc l'original dfcn'est pas modifié.

À partir de ces deux exemples ci-dessus, nous voyons qu'il est ambigu que vous souhaitiez ou non modifier le DataFrame d'origine. Ceci est particulièrement dangereux si vous écrivez quelque chose comme ce qui suit:

In [10]: dfc.loc[0]['A'] = 333
SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

In [11]: dfc
Out[11]:
    A   B
0   111 1
1   bbb 2
2   ccc 3

Cette fois, ça n'a pas marché du tout. Ici, nous voulions changer dfc, mais nous avons en fait modifié une valeur intermédiaire dfc.loc[0]qui est une copie et est immédiatement supprimée. Il est très difficile de prédire si la valeur intermédiaire aime dfc.loc[0]ou dfc['A']est une vue ou une copie, il n'est donc pas garanti si le DataFrame d'origine sera mis à jour ou non. C'est pourquoi l'indexation chaînée doit être évitée, et pandas génère le SettingWithCopyWarningpour ce type de mise à jour d'indexation chaînée.

C'est maintenant l'utilisation de .copy(). Pour éliminer l'avertissement, faites une copie pour exprimer explicitement votre intention:

In [12]: zero_row_copy = dfc.loc[0].copy()

In [13]: zero_row_copy['A'] = 444 # This time no warning

Puisque vous modifiez une copie, vous savez que l'original dfcne changera jamais et vous ne vous attendez pas à ce qu'il change. Votre attente correspond au comportement, puis SettingWithCopyWarningdisparaît.

Remarque: si vous souhaitez modifier le DataFrame d'origine, le document vous suggère d'utiliser loc:

In [14]: dfc.loc[0,'A'] = 555

In [15]: dfc
Out[15]:
    A   B
0   555 1
1   bbb 2
2   ccc 3

2

En général, il est plus sûr de travailler sur des copies que sur des blocs de données d'origine, sauf lorsque vous savez que vous n'aurez plus besoin de l'original et que vous souhaitez continuer avec la version manipulée. Normalement, vous auriez encore une certaine utilité pour la trame de données originale à comparer avec la version manipulée, etc. Par conséquent, la plupart des gens travaillent sur des copies et fusionnent à la fin.


0

Supposons que vous ayez un bloc de données comme ci-dessous

df1
     A    B    C    D
4 -1.0 -1.0 -1.0 -1.0
5 -1.0 -1.0 -1.0 -1.0
6 -1.0 -1.0 -1.0 -1.0
6 -1.0 -1.0 -1.0 -1.0

Lorsque vous souhaitez en créer un autre df2identique à df1, sanscopy

df2=df1
df2
     A    B    C    D
4 -1.0 -1.0 -1.0 -1.0
5 -1.0 -1.0 -1.0 -1.0
6 -1.0 -1.0 -1.0 -1.0
6 -1.0 -1.0 -1.0 -1.0

Et souhaite modifier la valeur df2 uniquement comme ci-dessous

df2.iloc[0,0]='changed'

df2
         A    B    C    D
4  changed -1.0 -1.0 -1.0
5       -1 -1.0 -1.0 -1.0
6       -1 -1.0 -1.0 -1.0
6       -1 -1.0 -1.0 -1.0

En même temps, le df1 est également modifié

df1
         A    B    C    D
4  changed -1.0 -1.0 -1.0
5       -1 -1.0 -1.0 -1.0
6       -1 -1.0 -1.0 -1.0
6       -1 -1.0 -1.0 -1.0

Puisque deux df sont identiques object, nous pouvons le vérifier en utilisant leid

id(df1)
140367679979600
id(df2)
140367679979600

Donc, comme même objet et un changement, un autre passera également la même valeur.


Si nous ajoutons les copy, et maintenant df1et df2sont considérés comme différents object, si nous apportons le même changement à l'un d'eux, l'autre ne changera pas.

df2=df1.copy()
id(df1)
140367679979600
id(df2)
140367674641232

df1.iloc[0,0]='changedback'
df2
         A    B    C    D
4  changed -1.0 -1.0 -1.0
5       -1 -1.0 -1.0 -1.0
6       -1 -1.0 -1.0 -1.0
6       -1 -1.0 -1.0 -1.0

Il est bon de mentionner que lorsque vous sous-définissez la trame de données d'origine, vous pouvez également ajouter la copie en toute sécurité afin d'éviter le SettingWithCopyWarning

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.