SettingWithCopyWarning
é um aviso que o Pandas pode gerar quando fazemos uma atribuição a um DataFrame. Isso pode acontecer quando usamos atribuições encadeadas ou quando usamos um DataFrame criado a partir de um recorte. É uma fonte comum de bugs no código do Pandas com a qual todos já nos deparamos antes. Pode ser difícil de depurar porque o aviso pode aparecer em um código que parece que deveria funcionar bem.
Entender o SettingWithCopyWarning
é importante porque ele sinaliza possíveis problemas com a manipulação de dados. Este aviso sugere que seu código pode não estar alterando os dados conforme o esperado, o que pode resultar em consequências não intencionais e bugs obscuros difíceis de rastrear.
Neste artigo, exploraremos o SettingWithCopyWarning
no pandas e como evitá-lo. Também discutiremos o futuro do Pandas e como a opção copy_on_write
mudará a forma como trabalhamos com DataFrames.
Visualizações e Cópias de DataFrame
Quando selecionamos um recorte de um DataFrame e o atribuímos a uma variável, podemos obter uma visualização ou uma cópia fresca do DataFrame.
Com uma visualização, a memória entre ambos os DataFrames é compartilhada. Isso significa que modificar um valor de uma célula presente em ambos os DataFrames modificará ambos.
Com uma cópia, uma nova memória é alocada, e um DataFrame independente com os mesmos valores do original é criado. Neste caso, ambos os DataFrames são entidades distintas, então modificar um valor em um deles não afeta o outro.
O Pandas tenta evitar criar uma cópia sempre que possível para otimizar o desempenho. No entanto, é impossível prever antecipadamente se obteremos uma visualização ou uma cópia. O SettingWithCopyWarning
é acionado sempre que atribuímos um valor a um DataFrame para o qual não está claro se é uma cópia ou uma visualização de outro DataFrame.
Compreendendo o SettingWithCopyWarning
com Dados Reais
Vamos usar o conjunto de dados do Kaggle Estes Dados Imobiliários de Londres 2024 para aprender como o SettingWithCopyWarning
ocorre e como corrigi-lo.
Este conjunto de dados contém dados imobiliários recentes de Londres. Aqui está uma visão geral das colunas presentes no conjunto de dados:
addedOn
: A data em que o anúncio foi adicionado.title
: O título do anúncio.descriptionHtml
: Uma descrição HTML do anúncio.propertyType
: O tipo de propriedade. O valor será"Não Especificado"
se o tipo não foi especificado.sizeSqFeetMax
: O tamanho máximo em pés quadrados.bedrooms
: O número de quartos.listingUpdatedReason
: Motivo da atualização do anúncio (por exemplo, novo anúncio, redução de preço).price
: O preço do anúncio em libras.
Exemplo com uma variável temporária explícita
Diga-nos que as propriedades com um tipo de propriedade não especificado são casas. Assim, queremos atualizar todas as linhas com propertyType
igual a "Not Specified"
para "House"
. Uma maneira de fazer isso é filtrar as linhas com um tipo de propriedade não especificado em uma variável temporária DataFrame e atualizar os valores da coluna propertyType
assim:
import pandas as pd dataset_name = "realestate_data_london_2024_nov.csv" df = pd.read_csv(dataset_name) # Obter todas as linhas com tipo de propriedade não especificado no_property_type = df[df["propertyType"] == "Not Specified"] # Atualizar o tipo de propriedade para "Casa" nessas linhas no_property_type["propertyType"] = "House"
A execução deste código fará com que o pandas produza o 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"
O motivo para isso é que o pandas não consegue saber se a DataFrame no_property_type
é uma visualização ou uma cópia de df
.
Isso é um problema porque o comportamento do código a seguir pode ser muito diferente dependendo se é uma visualização ou uma cópia.
Neste exemplo, nosso objetivo é modificar a DataFrame original. Isso só acontecerá se no_property_type
for uma visualização. Se o restante do nosso código assumir que df
foi modificado, pode estar errado porque não há como garantir que esse seja o caso. Devido a esse comportamento incerto, o Pandas lança o aviso para nos informar sobre esse fato.
Mesmo que nosso código seja executado corretamente porque obtivemos uma visualização, poderíamos obter uma cópia em execuções subsequentes, e o código não funcionará como pretendido. Portanto, é importante não ignorar este aviso e garantir que nosso código sempre fará o que queremos que ele faça.
Exemplo com uma variável temporária oculta
No exemplo anterior, fica claro que uma variável temporária está sendo usada porque estamos atribuindo explicitamente parte do DataFrame a uma variável chamada no_property_type
.
No entanto, em alguns casos, isso não é tão explícito. O exemplo mais comum em que ocorre o SettingWithCopyWarning
é com indexação encadeada. Suponha que substituímos as duas últimas linhas por uma única linha:
df[df["propertyType"] == "Not Specified"]["propertyType"] = "House"
À primeira vista, não parece que uma variável temporária está sendo criada. No entanto, executá-la resulta em um SettingWithCopyWarning
também.
A maneira como esse código é executado é:
df[df["propertyType"] == "Not Specified"]
é avaliado e armazenado temporariamente na memória.- O índice
["propertyType"]
desse local de memória temporário é acessado.
Acessos de índice são avaliados um a um e, portanto, a indexação encadeada resulta no mesmo aviso porque não sabemos se os resultados intermediários são visualizações ou cópias. O código acima é essencialmente o mesmo que fazer:
tmp = df[df["propertyType"] == "Not Specified"] tmp["propertyType"] = "House"
Este exemplo é frequentemente referido como indexação encadeada porque encadeamos acessos indexados usando []
. Primeiro, acessamos [df["propertyType"] == "Not Specified"]
e depois ["propertyType"]
.
Como Resolver o SettingWithCopyWarning
Vamos aprender como escrever nosso código para que não haja ambiguidade e o SettingWithCopyWarning
não seja acionado. Aprendemos que o aviso surge de uma ambiguidade sobre se um DataFrame é uma visualização ou uma cópia de outro DataFrame.
O jeito de consertar é garantir que cada DataFrame que criamos seja uma cópia se quisermos que seja uma cópia ou uma visualização se quisermos que seja uma visualização.
Modificar com segurança o DataFrame original com loc
Vamos consertar o código do exemplo acima onde queremos modificar o DataFrame original. Para evitar o uso de uma variável temporária, use a propriedade indexadora loc
.
df.loc[df["propertyType"] == "Not Specified", "propertyType"] = "House"
Com este código, estamos agindo diretamente no DataFrame original df
através da propriedade indexadora loc
, então não há necessidade de variáveis intermediárias. É isso que precisamos fazer quando queremos modificar diretamente o DataFrame original.
Isso pode parecer indexação encadeada à primeira vista porque ainda existem parâmetros, mas não é. O que define cada indexação são os colchetes []
.
Observe que usar loc
é seguro apenas se atribuirmos um valor diretamente, como fizemos acima. Se em vez disso usarmos uma variável temporária, caímos novamente no mesmo problema. Aqui estão dois exemplos de código que não resolvem o problema:
- Usando
loc
com uma variável temporária:
# Usar loc com uma variável temporária não resolve o problema no_property_type = df.loc[df["propertyType"] == "Not Specified"] no_property_type["propertyType"] = "House"
- Usando
loc
juntamente com um índice (o mesmo que indexação encadeada):
# Usar loc com indexação é o mesmo que indexação encadeada df.loc[df["propertyType"] == "Not Specified"]["propertyType"] = "House"
Ambos esses exemplos tendem a confundir as pessoas porque é um equívoco comum acreditar que enquanto há um loc
, estamos modificando os dados originais. Isso está incorreto. A única maneira de garantir que o valor está sendo atribuído ao DataFrame original é atribuí-lo diretamente usando um único loc
sem nenhuma indexação separada.
Trabalhando com segurança com uma cópia do DataFrame original com copy()
Quando queremos garantir que estamos operando em uma cópia do DataFrame, devemos usar o método .copy()
.
Digamos que nos pediram para analisar o preço por metro quadrado dos imóveis. Não queremos modificar os dados originais. O objetivo é criar um novo DataFrame com os resultados da análise para enviar a outra equipe.
O primeiro passo é filtrar algumas linhas e limpar os dados. Especificamente, precisamos:
- Remova as linhas em que
sizeSqFeetMax
não está definido. - Remova as linhas em que o
preço
é"POA"
(preço sob consulta). - Converta os preços para valores numéricos (no conjunto de dados original, os preços são strings com o seguinte formato:
"£25,000,000"
)
Podemos fazer os passos acima usando o seguinte código:
# 1. Filtrar todas as propriedades sem tamanho ou preço properties_with_size_and_price = df[df["sizeSqFeetMax"].notna() & (df["price"] != "POA")] # 2. Remover os caracteres £ e , das colunas de preço properties_with_size_and_price["price"] = properties_with_size_and_price["price"].str.replace("£", "", regex=False).str.replace(",", "", regex=False) # 3. Converter a coluna de preço para valores numéricos properties_with_size_and_price["price"] = pd.to_numeric(properties_with_size_and_price["price"])
Para calcular o preço por metro quadrado, criamos uma nova coluna cujos valores são o resultado da divisão da coluna preço
pela coluna sizeSqFeetMax
:
properties_with_size_and_price["pricePerSqFt"] = properties_with_size_and_price["price"] / properties_with_size_and_price["sizeSqFeetMax"]
Se executarmos este código, obteremos o SettingWithCopyWarning
novamente. Isso não deve ser uma surpresa, pois criamos e modificamos explicitamente uma variável temporária do DataFrame properties_with_size_and_price
.
Já que queremos trabalhar em uma cópia dos dados em vez do DataFrame original, podemos corrigir o problema garantindo que properties_with_size_and_price
seja uma cópia fresca do DataFrame e não uma visualização, utilizando o método .copy()
na primeira linha:
properties_with_size_and_price = df[df["sizeSqFeetMax"].notna() & (df["price"] != "POA")].copy()
Adicionando novas colunas com segurança
Criar novas colunas se comporta da mesma forma que atribuir valores. Sempre que for ambíguo se estamos trabalhando com uma cópia ou uma visualização, o pandas irá gerar um SettingWithCopyWarning
.
Se quisermos trabalhar com uma cópia dos dados, devemos copiá-la explicitamente usando o método .copy()
. Então, estaremos livres para atribuir uma nova coluna da maneira que quisermos. Fizemos isso quando criamos a coluna pricePerSqFt
no exemplo anterior.
Por outro lado, se quisermos modificar o DataFrame original, há dois casos a considerar.
- Se a nova coluna abranger todas as linhas, podemos modificar diretamente o DataFrame original. Isso não causará um aviso porque não estaremos selecionando um subconjunto das linhas. Por exemplo, poderíamos adicionar uma coluna
note
para cada linha onde o tipo de casa está faltando:
df["notes"] = df["propertyType"].apply(lambda house_type: "Missing house type" if house_type == "Not Specified" else "")
- Se a nova coluna definir apenas valores para um subconjunto das linhas, então podemos usar a propriedade indexadora
loc
. Por exemplo:
df.loc[df["propertyType"] == "Not Specified", "notes"] = "Missing house type"
Observe que, neste caso, o valor nas colunas que não foram selecionadas será indefinido, portanto, a primeira abordagem é preferida, pois nos permite especificar um valor para cada linha.
Aviso deConfiguraçãoComCópia
Erro no Pandas 3.0
Neste momento, Aviso deConfiguraçãoComCópia
é apenas um aviso, não um erro. Nosso código ainda é executado, e o Pandas simplesmente nos informa para termos cuidado.
De acordo com a documentação oficial do Pandas, SettingWithCopyWarning
não será mais utilizado a partir da versão 3.0 e será substituído por um erro real por padrão, impondo padrões de código mais rigorosos.
Para garantir que nosso código permaneça compatível com versões futuras do pandas, é recomendado já atualizá-lo para gerar um erro em vez de um aviso.
Isso é feito definindo a seguinte opção após importar o pandas:
import pandas as pd pd.options.mode.copy_on_write = True
Adicionar isso ao código existente garantirá que lidemos com cada atribuição ambígua em nosso código e certifique-se de que o código ainda funcione quando atualizarmos para o pandas 3.0.
Conclusão
A SettingWithCopyWarning
ocorre sempre que nosso código torna ambíguo se um valor que estamos modificando é uma visão ou uma cópia. Podemos corrigir isso sendo sempre explícitos sobre o que queremos:
- Se quisermos trabalhar com uma cópia, devemos copiá-la explicitamente usando o método
copy()
. - Se quisermos modificar o DataFrame original, devemos usar a propriedade indexadora
loc
e atribuir o valor diretamente ao acessar os dados, sem usar variáveis intermediárias.
Apesar de não ser um erro, não devemos ignorar esse aviso porque pode levar a resultados inesperados. Além disso, a partir do Pandas 3.0, isso se tornará um erro por padrão, então devemos proteger nosso código para o futuro ativando o Copy-on-Write em nosso código atual usando pd.options.mode.copy_on_write = True
. Isso garantirá que o código permaneça funcional para versões futuras do Pandas.
Source:
https://www.datacamp.com/tutorial/settingwithcopywarning-pandas