curso
SettingWithCopyWarning in Pandas: Cómo solucionar esta advertencia
SettingWithCopyWarning
es una advertencia que Pandas puede lanzar cuando hacemos una asignación a un DataFrame. Esto puede ocurrir cuando utilizamos asignaciones encadenadas o cuando utilizamos un DataFrame creado a partir de una rebanada. Es una fuente común de errores en el código Pandas a la que todos nos hemos enfrentado alguna vez. Puede ser difícil de depurar porque la advertencia puede aparecer en código que parece que debería funcionar bien.
Comprender la SettingWithCopyWarning
es importante porque señala posibles problemas de manipulación de datos. Esta advertencia sugiere que es posible que tu código no esté alterando los datos según lo previsto, lo que puede provocar consecuencias no deseadas y fallos oscuros difíciles de rastrear.
En este artículo, exploraremos el SettingWithCopyWarning
en pandas y cómo evitarlo. También hablaremos del futuro de Pandas y de cómo la opción copy_on_write
cambiará nuestra forma de trabajar con DataFrames.
Vistas y copias de marcos de datos
Cuando seleccionamos un trozo de un Marco de Datos y lo asignamos a una variable, podemos obtener una vista o una copia nueva del Marco de Datos.
Con una vista, la memoria entre ambos DataFrames es compartida. Esto significa que modificar un valor de una celda que esté presente en ambos DataFrames modificará los dos.
Con una copia, se asigna nueva memoria y se crea un DataFrame independiente con los mismos valores que el original. En este caso, ambos DataFrames son entidades distintas, por lo que modificar un valor en uno de ellos no afecta al otro.
Pandas intenta evitar crear una copia siempre que puede para optimizar el rendimiento. Sin embargo, es imposible predecir de antemano si obtendremos una vista o una copia. El SettingWithCopyWarning
aparece siempre que asignamos un valor a un DataFrame para el que no está claro si es una copia o una vista de otro DataFrame.
Comprender la SettingWithCopyWarning
con datos reales
Utilizaremos este conjunto de datos Kaggle de Datos Inmobiliarios Londres 2024 para aprender cómo se produce el SettingWithCopyWarning
y cómo solucionarlo.
Este conjunto de datos contiene datos inmobiliarios recientes de Londres. Aquí tienes un resumen de las columnas presentes en el conjunto de datos:
addedOn
: La fecha en la que se añadió el anuncio.title
: El título del anuncio.descriptionHtml
: Una descripción HTML del anuncio.propertyType
: El tipo de propiedad. El valor será"Not Specified"
si no se especificó el tipo.sizeSqFeetMax
: El tamaño máximo en metros cuadrados.bedrooms
: El número de habitaciones.listingUpdatedReason
: Motivo de la actualización del anuncio (por ejemplo, nuevo anuncio, reducción de precio).price
: El precio del anuncio en libras.
Ejemplo con una variable temporal explícita
Digamos que nos dicen que las propiedades con un tipo de propiedad no especificado son casas. Por tanto, queremos actualizar todas las filas con propertyType
igual a "Not Specified"
a "House"
. Una forma de hacerlo es filtrar las filas con un tipo de propiedad no especificado en una variable temporal DataFrame y actualizar los valores de la columna propertyType
de esta forma:
import pandas as pd
dataset_name = "realestate_data_london_2024_nov.csv"
df = pd.read_csv(dataset_name)
# Obtain all rows with unspecified property type
no_property_type = df[df["propertyType"] == "Not Specified"]
# Update the property type to “House” on those rows
no_property_type["propertyType"] = "House"
La ejecución de este código hará que pandas produzca el SettingWithCopyWarning
:
SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
no_property_type["propertyType"] = "House"
El motivo es que pandas no puede saber si el no_property_type
DataFrame es una vista o una copia de df
.
Esto es un problema porque el comportamiento del código siguiente puede ser muy diferente según se trate de una vista o de una copia.
En este ejemplo, nuestro objetivo es modificar el DataFrame original. Esto sólo ocurrirá si no_property_type
es una vista. Si el resto de nuestro código asume que df
se ha modificado, puede equivocarse porque no hay forma de garantizar que sea así. Debido a este comportamiento incierto, Pandas lanza la advertencia para avisarnos de ese hecho.
Aunque nuestro código se ejecute correctamente porque hemos obtenido una vista, podríamos obtener una copia en ejecuciones posteriores, y el código no funcionaría como estaba previsto. Por tanto, es importante no ignorar esta advertencia y asegurarnos de que nuestro código siempre hará lo que queremos que haga.
Ejemplo con una variable temporal oculta
En el ejemplo anterior, está claro que se está utilizando una variable temporal porque estamos asignando explícitamente parte del DataFrame a una variable llamada no_property_type
.
Sin embargo, en algunos casos, esto no es tan explícito. El ejemplo más común en el que se produce SettingWithCopyWarning
es con la indexación encadenada. Supongamos que sustituimos las dos últimas líneas por una sola:
df[df["propertyType"] == "Not Specified"]["propertyType"] = "House"
A primera vista, no parece que se esté creando una variable temporal. Sin embargo, al ejecutarlo también se obtiene un SettingWithCopyWarning
.
La forma en que se ejecuta este código es
df[df["propertyType"] == "Not Specified"]
se evalúa y se almacena temporalmente en la memoria.- Se accede al índice
["propertyType"]
de esa posición de memoria temporal.
Los accesos al índice se evalúan uno a uno y, por tanto, la indexación encadenada da lugar a la misma advertencia, porque no sabemos si los resultados intermedios son vistas o copias. El código anterior es esencialmente lo mismo que hacer:
tmp = df[df["propertyType"] == "Not Specified"]
tmp["propertyType"] = "House"
Este ejemplo suele denominarse indexación encadenada porque encadenamos los accesos indexados utilizando []
. Primero accedemos a [df["propertyType"] == "Not Specified"]
y después a ["propertyType"]
.
Cómo resolver el SettingWithCopyWarning
Aprendamos a escribir nuestro código para que no haya ambigüedad y no se active el SettingWithCopyWarning
. Aprendimos que la advertencia surge de una ambigüedad sobre si un DataFrame es una vista o una copia de otro DataFrame.
La forma de solucionarlo es asegurarnos de que cada DataFrame que creamos es una copia si queremos que sea una copia o una vista si queremos que sea una vista.
Modificar con seguridad el DataFrame original con loc
Arreglemos el código del ejemplo anterior en el que queremos modificar el DataFrame original. Para evitar utilizar una variable temporal, utiliza la propiedad loc
indexador.
df.loc[df["propertyType"] == "Not Specified", "propertyType"] = "House"
Con este código, estamos actuando directamente sobre el DataFrame original df
a través de la propiedad indexadora loc
, por lo que no hay necesidad de variables intermedias. Esto es lo que tenemos que hacer cuando queremos modificar directamente el DataFrame original.
A primera vista, esto puede parecer una indexación encadenada, porque sigue habiendo parámetros, pero no lo es. Lo que define cada indexación son los corchetes []
.
Ten en cuenta que utilizar loc
sólo es seguro si asignamos directamente un valor, como hicimos antes. Si en su lugar utilizamos una variable temporal, volvemos a caer en el mismo problema. Aquí tienes dos ejemplos de código que no solucionan el problema:
- Utilizando
loc
con una variable temporal:
# Using loc plus temporary variable doesn’t fix the issue
no_property_type = df.loc[df["propertyType"] == "Not Specified"]
no_property_type["propertyType"] = "House"
- Utilizando
loc
junto con un índice (igual que la indexación encadenada):
# Using loc plus indexing is the same as chained indexing
df.loc[df["propertyType"] == "Not Specified"]["propertyType"] = "House"
Estos dos ejemplos tienden a confundir a la gente porque es un error común pensar que mientras haya un loc
, estamos modificando los datos originales. Esto es incorrecto. La única forma de asegurarse de que el valor se está asignando al DataFrame original es asignarlo directamente utilizando un único loc
sin ninguna indexación separada.
Trabajar con seguridad con una copia del DataFrame original con copy()
Cuando queramos asegurarnos de que estamos operando sobre una copia del DataFrame, debemos utilizar el método .copy()
.
Digamos que nos piden que analicemos el precio por pie cuadrado de las propiedades. No queremos modificar los datos originales. El objetivo es crear un nuevo DataFrame con los resultados del análisis para enviarlo a otro equipo.
El primer paso es filtrar algunas filas y limpiar los datos. Concretamente, necesitamos
- Elimina las filas en las que
sizeSqFeetMax
no esté definido. - Elimina las filas en las que el
price
sea"POA"
(precio a consultar). - Convierte los precios en valores numéricos (en el conjunto de datos original, los precios son cadenas con el siguiente formato:
"£25,000,000"
)
Podemos realizar los pasos anteriores utilizando el siguiente código:
# 1. Filter out all properties without a size or a price
properties_with_size_and_price = df[df["sizeSqFeetMax"].notna() & (df["price"] != "POA")]
# 2. Remove the £ and , characters from the price columns
properties_with_size_and_price["price"] = properties_with_size_and_price["price"].str.replace("£", "", regex=False).str.replace(",", "", regex=False)
# 3. Convert the price column to numeric values
properties_with_size_and_price["price"] = pd.to_numeric(properties_with_size_and_price["price"])
Para calcular el precio por pie cuadrado, creamos una nueva columna cuyos valores son el resultado de dividir la columna price
por la columna sizeSqFeetMax
:
properties_with_size_and_price["pricePerSqFt"] = properties_with_size_and_price["price"] / properties_with_size_and_price["sizeSqFeetMax"]
Si ejecutamos este código, obtendremos de nuevo la dirección SettingWithCopyWarning
. Esto no debería sorprenderte porque hemos creado y modificado explícitamente una variable temporal del DataFrame properties_with_size_and_price
.
Como queremos trabajar con una copia de los datos y no con el DataFrame original, podemos solucionar el problema asegurándonos de que properties_with_size_and_price
es una copia fresca del DataFrame y no una vista utilizando el método .copy()
en la primera línea:
properties_with_size_and_price = df[df["sizeSqFeetMax"].notna() & (df["price"] != "POA")].copy()
Añadir nuevas columnas con seguridad
La creación de nuevas columnas se comporta del mismo modo que la asignación de valores. Siempre que resulte ambiguo si estamos trabajando con una copia o con una vista, pandas lanzará un mensaje SettingWithCopyWarning
.
Si queremos trabajar con una copia de los datos, debemos copiarlos explícitamente utilizando el método .copy()
. Luego, somos libres de asignar una nueva columna de la forma que queramos. Lo hicimos al crear la columna pricePerSqFt
en el ejemplo anterior.
Por otra parte, si queremos modificar el DataFrame original hay dos casos a considerar.
- Si la nueva columna abarca todas las filas, podemos modificar directamente el DataFrame original. Esto no provocará una advertencia porque no estaremos seleccionando un subconjunto de las filas. Por ejemplo, podríamos añadir una columna
note
para cada fila en la que falte el tipo de casa:
df["notes"] = df["propertyType"].apply(lambda house_type: "Missing house type" if house_type == "Not Specified" else "")
- Si la nueva columna sólo define valores para un subconjunto de filas, podemos utilizar la propiedad
loc
indexador. Por ejemplo:
df.loc[df["propertyType"] == "Not Specified", "notes"] = "Missing house type"
Ten en cuenta que, en este caso, el valor de las columnas que no se seleccionaron será indefinido, por lo que es preferible el primer enfoque, ya que nos permite especificar un valor para cada fila.
SettingWithCopyWarning
Error en Pandas 3.0
Ahora mismo, SettingWithCopyWarning
es sólo una advertencia, no un error. Nuestro código se sigue ejecutando, y Pandas simplemente nos informa de que tengamos cuidado.
Según la documentación oficial de PandasSettingWithCopyWarning
dejará de utilizarse a partir de la versión 3.0 y será sustituido por un verdadero error por defecto, aplicando normas de código más estrictas.
Para asegurarnos de que nuestro código sigue siendo compatible con futuras versiones de pandas, se recomienda actualizarlo ya para que lance un error en lugar de una advertencia.
Esto se hace configurando la siguiente opción después de importar pandas:
import pandas as pd
pd.options.mode.copy_on_write = True
Añadiendo esto al código existente nos aseguraremos de que tratamos cada asignación ambigua en nuestro código y nos aseguraremos de que el código sigue funcionando cuando actualicemos a pandas 3.0.
Conclusión
El SettingWithCopyWarning
se produce siempre que nuestro código hace que sea ambiguo si un valor que estamos modificando es una vista o una copia. Podemos solucionarlo siendo siempre explícitos sobre lo que queremos:
- Si queremos trabajar con una copia, debemos copiarla explícitamente utilizando el método
copy()
. - Si queremos modificar el DataFrame original, debemos utilizar la propiedad indexadora
loc
y asignar el valor directamente al acceder a los datos, sin utilizar variables intermedias.
A pesar de no ser un error, no debemos ignorar esta advertencia porque puede dar lugar a resultados inesperados. Además, a partir de Pandas 3.0, se convertirá en un error por defecto, por lo que deberíamos preparar nuestro código para el futuro activando Copy-on-Write en nuestro código actual mediante pd.options.mode.copy_on_write = True
. Esto garantizará que el código siga siendo funcional para futuras versiones de Pandas.
Preguntas frecuentes
¿Por qué recibo SettingWithCopyWarning aunque utilice .loc?
Utilizar loc
es siempre seguro y no provocará ninguna advertencia ni comportamientos inesperados, siempre que df
no se haya creado a partir de otro DataFrame. Por ejemplo, si df
es el DataFrame que se creó al leer el conjunto de datos, entonces es seguro. Sin embargo, si es el resultado de un cálculo intermedio, sólo es seguro si el DataFrame se copió utilizando el método copy()
.
¿Cómo suprimir SettingWithCopyWarning?
Podemos utilizar el paquete warnings
y añadir el código:
import warnings
warnings.simplefilter(action='ignore', category=pd.errors.SettingWithCopyWarning)
Sin embargo, se recomienda encarecidamente no suprimir ni ignorar esta advertencia, ya que significa que tu código podría no estar haciendo lo que crees que está haciendo.
¿Cómo ignorar SettingWithCopyWarning?
Podemos ignorar la advertencia temporalmente utilizando el paquete warnings
y envolviendo nuestro código con:
import warnings
with warnings.catch_warnings():
warnings.simplefilter("ignore", category=pd.errors.SettingWithCopyWarning)
# Our code goes here
Sin embargo, se recomienda encarecidamente no suprimir ni ignorar esta advertencia, ya que significa que tu código podría no estar haciendo lo que crees que está haciendo.
¿Cómo arreglo SettingWithCopyWarning al añadir una nueva columna?
La corrección de la advertencia al añadir una columna se realiza del mismo modo que al asignar valores. Asegúrate de copiar primero el DataFrame si pretendes trabajar con una copia de los datos o utilizar loc
.
He seguido esta guía y sigo recibiendo el aviso SettingWithCopyWarning. ¿Cómo puedo solucionarlo?
En las versiones más recientes de pandas, seguir las prácticas de este tutorial no debería provocar ninguna advertencia.
Si utilizas una versión anterior, te aconsejamos que la actualices a la última versión estable. Se sabe que las versiones antiguas tienen errores en torno a esta advertencia y que la activan cuando no hay ningún problema con el código.
Si la versión de pandas está actualizada, es probable que el problema subyacente sea un caso de indexación encadenada no evidente. Revisa detenidamente cómo se creó el Marco de datos.
¡Aprende Pandas con estos cursos!
curso
Data Manipulation with pandas
curso
Analyzing Marketing Campaigns with pandas
tutorial
Python Copy List: Lo que debes saber
Allan Ouko
7 min
tutorial
Tutorial de Pandas: DataFrames en Python
tutorial
Tutorial de pandas en Python: la guía definitiva para principiantes
tutorial
Tutorial pandas read_csv(): importación de datos
tutorial
Tutorial seleccionar columnas con Python
DataCamp Team
7 min
tutorial
Tutorial de unión de DataFrames en pandas
DataCamp Team
19 min