trying panda from drop python pandas chained-assignment

python - panda - ¿Por qué está utilizando ciegamente df.copy() una mala idea para arreglar el SettingWithCopyWarning



a value is trying to be set on a copy of a slice from a dataframe (4)

Aquí está mi 2 centavo en esto con un ejemplo muy simple de por qué la advertencia es importante.

asumiendo que estoy creando un df tal tiene

x = pd.DataFrame(list(zip(range(4), range(4))), columns=[''a'', ''b'']) print(x) a b 0 0 0 1 1 1 2 2 2 3 3 3

ahora quiero crear un nuevo marco de datos basado en un subconjunto del original y modificarlo, por ejemplo:

q = x.loc[:, ''a'']

ahora esto es una parte del original y todo lo que haga afectará a x:

q += 2 print(x) # checking x again, wow! it changed! a b 0 2 0 1 3 1 2 4 2 3 5 3

Esto es lo que te dice la advertencia. está trabajando en un sector, por lo que todo lo que haga se reflejará en el DataFrame original

ahora utilizando .copy() , no será una parte del original , por lo que hacer una operación en q no afectará a x:

x = pd.DataFrame(list(zip(range(4), range(4))), columns=[''a'', ''b'']) print(x) a b 0 0 0 1 1 1 2 2 2 3 3 3 q = x.loc[:, ''a''].copy() q += 2 print(x) # oh, x did not change because q is a copy now a b 0 0 0 1 1 1 2 2 2 3 3 3

y por cierto, una copia solo significa que q será un nuevo objeto en la memoria. donde una porción comparte el mismo objeto original en la memoria

imo, usar .copy() es muy seguro. como ejemplo, df.loc[:, ''a''] devuelve una df.loc[df.index, ''a''] pero df.loc[df.index, ''a''] devuelve una copia. Jeff me dijo que este era un comportamiento inesperado y : o df.index debería tener el mismo comportamiento que un indexador en .loc [], pero usar .copy() en ambos devolverá una copia, más .copy() esté seguro. así que use .copy() si no quiere afectar el marco de datos original.

ahora usando .copy() devuelva una copia en profundidad del DataFrame , que es un método muy seguro para no recibir la llamada telefónica de la que habla.

pero el uso de df.is_copy = None , es solo un truco que no copia nada, lo que es una muy mala idea, todavía estará trabajando en una porción del DataFrame original

Una cosa más que la gente tiende a no saber:

df[columns] puede devolver una vista.

df.loc[indexer, columns] también puede devolver una vista, pero casi siempre no lo hace en la práctica. énfasis en el mayo aquí

Hay innumerables preguntas sobre el temido SettingWithCopyWarning

Tengo una buena idea de cómo se produce. (Note que dije bien, no genial)

Ocurre cuando un df marco de datos se "adjunta" a otro marco de datos a través de un atributo almacenado en is_copy .

Aquí un ejemplo

df = pd.DataFrame([[1]]) d1 = df[:] d1.is_copy <weakref at 0x1115a4188; to ''DataFrame'' at 0x1119bb0f0>

Podemos establecer ese atributo en None o

d1 = d1.copy()

He visto desarrolladores como @Jeff y no recuerdo quién más, advirtió sobre hacer eso. Citando que el SettingWithCopyWarning tiene un propósito.

Pregunta
Ok, entonces, ¿cuál es un ejemplo concreto que demuestra por qué ignorar la advertencia asignando una copy al original es una mala idea?

Definiré "mala idea" para aclarar.

Mala idea
Es una mala idea poner el código en producción, lo que conducirá a una llamada telefónica en medio de una noche de sábado que dice que su código está dañado y debe repararse.

Ahora, ¿cómo se puede usar df = df.copy() para omitir el SettingWithCopyWarning y obtener ese tipo de llamada telefónica? Quiero que lo explique porque esto es una fuente de confusión y estoy tratando de encontrar claridad. Quiero ver el caso de borde que explota!


Mientras que las otras respuestas proporcionan buena información sobre por qué uno no debería simplemente ignorar la advertencia, creo que su pregunta original aún no ha sido respondida.

@thn señala que el uso de copy() depende completamente del escenario en cuestión. Cuando quiere que se conserven los datos originales, usa .copy() , de lo contrario no lo hace. Si está utilizando copy() para eludir la SettingWithCopyWarning la SettingWithCopyWarning , está ignorando el hecho de que puede introducir un error lógico en su software. Mientras esté absolutamente seguro de que esto es lo que quiere hacer, está bien.

Sin embargo, cuando usa .copy() ciegas, puede encontrarse con otro problema, que ya no es realmente específico de pandas, sino que ocurre cada vez que está copiando datos.

Modifiqué ligeramente tu código de ejemplo para que el problema sea más evidente:

@profile def foo(): df = pd.DataFrame(np.random.randn(2 * 10 ** 7)) d1 = df[:] d1 = d1.copy() if __name__ == ''__main__'': foo()

Al usar memory_profile se puede ver claramente que .copy() duplica nuestro consumo de memoria:

> python -m memory_profiler demo.py Filename: demo.py Line # Mem usage Increment Line Contents ================================================ 4 61.195 MiB 0.000 MiB @profile 5 def foo(): 6 213.828 MiB 152.633 MiB df = pd.DataFrame(np.random.randn(2 * 10 ** 7)) 7 8 213.863 MiB 0.035 MiB d1 = df[:] 9 366.457 MiB 152.594 MiB d1 = d1.copy()

Esto se relaciona con el hecho de que todavía hay una referencia ( df ) que apunta al marco de datos original. Por lo tanto, df no se limpia con el recolector de basura y se guarda en la memoria.

Cuando está utilizando este código en un sistema de producción, puede o no obtener un MemoryError dependiendo del tamaño de los datos que está tratando y de la memoria disponible.

Para concluir, no es una buena idea usar .copy() ciegas . No solo porque puede introducir un error lógico en su software, sino también porque puede exponer peligros de tiempo de ejecución como MemoryError .

Edición: incluso si está haciendo df = df.copy() , y puede asegurarse de que no haya otras referencias a la df original, todavía se evalúa copy() antes de la asignación. Lo que significa que por un corto tiempo ambos marcos de datos estarán en la memoria.

Ejemplo (observe que no puede ver este comportamiento en el resumen de la memoria) :

> mprof run -T 0.001 demo.py Line # Mem usage Increment Line Contents ================================================ 7 62.9 MiB 0.0 MiB @profile 8 def foo(): 9 215.5 MiB 152.6 MiB df = pd.DataFrame(np.random.randn(2 * 10 ** 7)) 10 215.5 MiB 0.0 MiB df = df.copy()

Pero si visualiza el consumo de memoria a lo largo del tiempo, a 1,6 s ambos marcos de datos están en la memoria:


Actualizar:

TL; DR: Creo que el tratamiento de SettingWithCopyWarning depende de los propósitos. Si uno quiere evitar modificar df , trabajar en df.copy() es seguro y la advertencia es redundante. Si uno quiere modificar df , entonces usar .copy() significa una forma incorrecta y la advertencia debe respetarse.

Descargo de responsabilidad: no tengo comunicaciones privadas / personales con expertos de Pandas como otros respondedores. Entonces, esta respuesta se basa en los documentos oficiales de Pandas, en lo que basaría un usuario típico y en mis propias experiencias.


SettingWithCopyWarning no es el problema real, advierte sobre el problema real. El usuario debe comprender y resolver el problema real, no pasar por alto la advertencia.

El problema real es que, al indexar un marco de datos puede devolver una copia, luego, al modificar esta copia no se cambiará el marco de datos original. La advertencia pide a los usuarios que comprueben y eviten ese error lógico. Por ejemplo:

import pandas as pd, numpy as np np.random.seed(7) # reproducibility df = pd.DataFrame(np.random.randint(1, 10, (3,3)), columns=[''a'', ''b'', ''c'']) print(df) a b c 0 5 7 4 1 4 8 8 2 8 9 9 # Setting with chained indexing: not work & warning. df[df.a>4][''b''] = 1 print(df) a b c 0 5 7 4 1 4 8 8 2 8 9 9 # Setting with chained indexing: *may* work in some cases & no warning, but don''t rely on it, should always avoid chained indexing. df[''b''][df.a>4] = 2 print(df) a b c 0 5 2 4 1 4 8 8 2 8 2 9 # Setting using .loc[]: guarantee to work. df.loc[df.a>4, ''b''] = 3 print(df) a b c 0 5 3 4 1 4 8 8 2 8 3 9

Acerca de la manera incorrecta de eludir la advertencia:

df1 = df[df.a>4][''b''] df1.is_copy = None df1[0] = -1 # no warning because you trick pandas, but will not work for assignment print(df) a b c 0 5 7 4 1 4 8 8 2 8 9 9 df1 = df[df.a>4][''b''] df1 = df1.copy() df1[0] = -1 # no warning because df1 is a separate dataframe now, but will not work for assignment print(df) a b c 0 5 7 4 1 4 8 8 2 8 9 9

Por lo tanto, establecer df1.is_copy en False o None es solo una forma de evitar la advertencia, no de resolver el problema real al asignar. La configuración de df1 = df1.copy() también df1 = df1.copy() la advertencia de otra manera aún más incorrecta, porque df1 no es una weakref de df , sino un marco de datos totalmente independiente. Entonces, si los usuarios desean cambiar los valores en df , no recibirán ninguna advertencia, sino un error lógico. Los usuarios inexpertos no entenderán por qué df no cambia después de que se les asignen nuevos valores. Por eso es recomendable evitar estos enfoques por completo.

Si los usuarios solo desean trabajar en la copia de los datos, es decir, no modificando estrictamente el df original, entonces es perfectamente correcto llamar a .copy() explícitamente. Pero si quieren modificar los datos en la df original, deben respetar la advertencia. El punto es que los usuarios necesitan entender lo que están haciendo.

En caso de advertencia debido a la asignación de indexación encadenada, la solución correcta es evitar asignar valores a una copia producida por df[cond1][cond2] , pero usar la vista producida por df.loc[cond1, cond2] lugar.

Más ejemplos de configuración con advertencia / error de copia y soluciones se muestran en los documentos: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy


EDITAR:

Después de nuestro intercambio de comentarios y de leer un poco (incluso encontré la respuesta de @Jeff ), puedo traer lechuzas a Atenas , pero en panda-docs existe este código de ejemplo:

A veces, una advertencia de SettingWithCopy surgirá en momentos en que no haya una indización encadenada obvia. ¡Estos son los errores que SettingWithCopy está diseñado para atrapar! Pandas probablemente está tratando de advertirte que has hecho esto:

def do_something(df): foo = df[[''bar'', ''baz'']] # Is foo a view? A copy? Nobody knows! # ... many lines here ... foo[''quux''] = value # We don''t know whether this will modify df or not! return foo

Que tal vez sea un problema fácil de evitar, para un usuario / desarrollador experimentado, pero los pandas no son solo para el experimentado ...

Aún así, es probable que no recibas una llamada telefónica a mitad de la noche de un domingo sobre este tema, pero puede dañar la integridad de tus datos en el largo plazo si no lo detectas temprano.
Además, como lo establece la ley de Murphy , la manipulación de datos más compleja y compleja que llevará a cabo estará en una copia que se descartará antes de que se use y que pasará horas intentando depurarla.

Nota: Todo lo que es hipotético porque la definición misma en los documentos es una hipótesis basada en la probabilidad de eventos (desafortunados) ... SettingWithCopy es una advertencia fácil de usar para los nuevos usuarios que advierte a los usuarios de un comportamiento potencialmente aleatorio y no deseado de su codigo

Existe este número desde el 2014.
El código que provoca la advertencia en este caso se ve así:

from pandas import DataFrame # create example dataframe: df = DataFrame ({''column1'':[''a'', ''a'', ''a''], ''column2'': [4,8,9] }) df # assign string to ''column1'': df[''column1''] = df[''column1''] + ''b'' df # it works just fine - no warnings #now remove one line from dataframe df: df = df [df[''column2'']!=8] df # adding string to ''column1'' gives warning: df[''column1''] = df[''column1''] + ''c'' df

Y jreback hace algunos comentarios al respecto:

De hecho, está configurando una copia.

Es probable que no te importe; Es principalmente para abordar situaciones como:

df[''foo''][0] = 123...

que establece la copia (y por lo tanto no es visible para el usuario)

Esta operación, hace que el df ahora apunte a una copia del original

df = df [df[''column2'']!=8]

Si no te importa el marco "original", entonces está bien

Si esperas que el

df[''column1''] = df[''columns''] + ''c''

en realidad establecería el marco original (ambos se llaman ''df'' aquí, lo cual es confuso), entonces se sorprenderá.

y

(esta advertencia es principalmente para los nuevos usuarios para evitar configurar la copia)

Finalmente concluye:

Normalmente, las copias no importan, excepto cuando intenta configurarlas de forma encadenada.

De lo anterior podemos sacar estas conclusiones :

  1. SettingWithCopyWarning tiene un significado y hay (como lo presenta jreback) situaciones en las que esta advertencia es importante y se pueden evitar las complicaciones.
  2. La advertencia es principalmente una "red de seguridad" para los usuarios más nuevos para que presten atención a lo que están haciendo y que puede causar un comportamiento inesperado en las operaciones encadenadas. Por lo tanto, un usuario más avanzado puede desactivar la advertencia (de la respuesta de jreback):

pd.set_option(''chained_assignement'',None)

o podrías hacer:

df.is_copy = False