¿Por qué debería hacer una copia de un marco de datos en pandas?

189

Al seleccionar un subtrama de datos de un marco de datos primario, noté que algunos programadores hacen una copia del marco de datos utilizando el .copy()método. Por ejemplo,

X = my_dataframe[features_list].copy()

... en lugar de solo

X = my_dataframe[features_list]

¿Por qué están haciendo una copia del marco de datos? ¿Qué pasará si no hago una copia?

Elizabeth Susan Joseph
fuente
66
Supongo que están tomando precauciones adicionales para no modificar el marco de datos de origen. Probablemente innecesario, pero cuando estás juntando algo de manera interactiva, más vale prevenir que curar.
Paul H
8
Supongo que esta no es una pregunta estúpida por dar una negativa.
Elizabeth Susan Joseph

Respuestas:

207

Esto se expande en la respuesta de Paul. En Pandas, la indexación de un DataFrame devuelve una referencia al DataFrame inicial. Por lo tanto, cambiar el subconjunto cambiará el DataFrame inicial. Por lo tanto, querrá usar la copia si desea asegurarse 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

En contraste, lo siguiente deja df sin cambios:

df_sub_copy = df[0:1].copy()
df_sub_copy.x = -1
cgold
fuente
66
¿Es esta una copia profunda?
bikashg
66
Si. El modo predeterminado es la copia "profunda"! pandas.pydata.org/pandas-docs/stable/reference/api/…
Ambareesh
44

Porque si no hace una copia, los índices pueden manipularse en otro lugar, incluso si asigna el dataFrame a un nombre diferente.

Por ejemplo:

df2 = df
func1(df2)
func2(df)

func1 puede modificar df modificando df2, para evitar eso:

df2 = df.copy()
func1(df2)
func2(df)
gorrión
fuente
Espera, espera, ¿puedes explicar por qué ocurre esto? No tiene sentido
NoName
2
es porque en el primer ejemplo, `df2 = df , both variables reference the same DataFrame instance. So any changes made to df` o df2se realizará en la misma instancia de objeto. Mientras que en la df2 = df.copy()segunda instancia de objeto se crea, una copia de la primera, pero ahora dfy una df2referencia a diferentes instancias de objeto y cualquier cambio se realizará en su respectiva instancia de DataFrame.
Pedro
17

Es necesario mencionar que devolver la copia o la vista depende del tipo de indexación.

La documentación de los 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 la indexación y el corte de una sola etiqueta / escalar, por ejemplo, df.ix [3: 6] o df.ix [:, 'A'], se devolverá una vista.

Gusev Slava
fuente
12

El objetivo principal es evitar la indexación encadenada y eliminar el SettingWithCopyWarning .

Aquí la indexación encadenada es algo así como dfc['A'][0] = 111

El documento dice que se debe evitar la indexación encadenada al devolver una vista versus 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í aColumnhay una vista y no una copia del DataFrame original, por lo que modificarlo también modificará aColumnel original dfc. 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_rowes una copia, por lo que el original dfcno se modifica.

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í queríamos cambiar dfc, pero en realidad modificamos un valor intermedio dfc.loc[0]que es una copia y se descarta de inmediato. Es muy difícil predecir si el valor intermedio le gusta dfc.loc[0]o dfc['A']es una vista o una copia, por lo que no se garantiza si se actualizará DataFrame original o no. Es por eso que se debe evitar la indexación encadenada, y los pandas generan la SettingWithCopyWarningactualizació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 explícitamente:

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

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

Como está modificando una copia, sabe que el original dfcnunca cambiará y no espera que cambie. Su expectativa coincide con el comportamiento, luego SettingWithCopyWarningdesaparece.

Tenga en cuenta que 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
Cosyn
fuente
2

En general, es más seguro trabajar en copias que en marcos de datos originales, excepto cuando sabe que ya no necesitará el original y desea continuar con la versión manipulada. Normalmente, aún tendría algún uso para que el marco de datos original se compare con la versión manipulada, etc. Por lo tanto, la mayoría de las personas trabajan en copias y se fusionan al final.

bojax
fuente
0

Supongo que tiene el marco de datos como se muestra a continuación

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 desee crear otro df2que 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 quisiera modificar el valor de df2 solo de la siguiente manera

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, como un mismo objeto y uno cambia a otro, también pasará el mismo valor.


Si agregamos el copy, y ahora df1y df2se consideran diferentes object, si hacemos el mismo cambio en 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 se configura el marco de datos original, también es seguro agregar la copia para evitar SettingWithCopyWarning

YOBEN_S
fuente