¿Por qué debería hacer una copia de un marco de datos en pandas?
Al seleccionar un marco de datos secundario de un marco de datos principal, noté que algunos programadores hacen una copia del marco de datos usando el .copy()
método. Por ejemplo,
X = my_dataframe[features_list].copy()
...en lugar de sólo
X = my_dataframe[features_list]
¿Por qué están haciendo una copia del marco de datos? ¿Qué pasará si no hago una copia?
Esta respuesta ha quedado obsoleta en las versiones más recientes de pandas. Ver documentos
Esto amplía la respuesta de Paul. En Pandas, indexar un DataFrame devuelve una referencia al DataFrame inicial. Por lo tanto, cambiar el subconjunto cambiará el DataFrame inicial. Por lo tanto, querrás usar la copia si quieres asegurarte de que el DataFrame inicial no cambie. Considere el siguiente código:
df = DataFrame({'x': [1,2]})
df_sub = df[0:1]
df_sub.x = -1
print(df)
Obtendrás:
x
0 -1
1 2
Por el contrario, lo siguiente deja df sin cambios:
df_sub_copy = df[0:1].copy()
df_sub_copy.x = -1
Porque si no hace una copia, los índices aún se pueden manipular en otros lugares incluso si asigna el marco de datos a un nombre diferente.
Por ejemplo:
df2 = df
func1(df2)
func2(df)
func1 puede modificar df modificando df2, así que para evitar eso:
df2 = df.copy()
func1(df2)
func2(df)
Es necesario mencionar que devolver una copia o vista depende del tipo de indexación.
La documentación de pandas dice:
Devolver una vista versus una copia
Las reglas sobre cuándo se devuelve una vista de los datos dependen completamente de NumPy. Siempre que una matriz de etiquetas o un vector booleano estén involucrados en la operación de indexación, el resultado será una copia. Con indexación y división de etiqueta única/escalar, por ejemplo, df.ix[3:6] o df.ix[:, 'A'], se devolverá una vista.
El objetivo principal es evitar la indexación encadenada y eliminar el archivo SettingWithCopyWarning
.
Aquí la indexación encadenada es algo así comodfc['A'][0] = 111
El documento decía que se debe evitar la indexación encadenada al devolver una vista frente a una copia . Aquí hay un ejemplo ligeramente modificado de ese documento:
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
Aquí aColumn
hay una vista y no una copia del DataFrame original, por lo que la modificación aColumn
hará que el original dfc
también se modifique. A continuación, si indexamos la fila primero:
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
Esta vez zero_row
es una copia, por lo que el original dfc
no se modifica.
A partir de estos dos ejemplos anteriores, vemos que es ambiguo si desea o no cambiar el DataFrame original. Esto es especialmente peligroso si escribe algo como lo siguiente:
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
Esta vez no funcionó en absoluto. Aquí quisimos cambiar dfc
, pero en realidad modificamos un valor intermedio dfc.loc[0]
que es una copia y se descarta inmediatamente. Es muy difícil predecir si el valor intermedio dfc.loc[0]
es dfc['A']
una vista o una copia, por lo que no se garantiza si el DataFrame original se actualizará o no. Es por eso que se debe evitar la indexación encadenada, y pandas genera la SettingWithCopyWarning
actualización para este tipo de indexación encadenada.
Ahora es el uso de .copy()
. Para eliminar la advertencia, haga una copia para expresar su intención de forma explícita:
In [12]: zero_row_copy = dfc.loc[0].copy()
In [13]: zero_row_copy['A'] = 444 # This time no warning
Dado que está modificando una copia, sabe que el original dfc
nunca cambiará y no espera que cambie. Su expectativa coincide con el comportamiento, luego SettingWithCopyWarning
desaparece.
Nota: si desea modificar el DataFrame original, el documento sugiere que utilice loc
:
In [14]: dfc.loc[0,'A'] = 555
In [15]: dfc
Out[15]:
A B
0 555 1
1 bbb 2
2 ccc 3
Se supone que tiene un marco de datos como el siguiente
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
Cuando quieras crear otro df2
que sea idéntico a df1
, sincopy
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
Y me gustaría modificar el valor df2 solo como se muestra a continuación
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
Al mismo tiempo también se cambia el df1.
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
Dado que dos df son iguales object
, podemos verificarlo usando elid
id(df1)
140367679979600
id(df2)
140367679979600
Entonces, son el mismo objeto y uno cambia, otro también pasará el mismo valor.
Si sumamos el copy
, y ahora df1
y df2
se consideran diferentes object
, si le hacemos el mismo cambio a uno de ellos el otro no cambiará.
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
Es bueno mencionar que cuando crea un subconjunto del marco de datos original, también es seguro agregar la copia para evitar elSettingWithCopyWarning