fundo
Acabei de atualizar meus Pandas de 0.11 para 0.13.0rc1. Agora, o aplicativo está lançando muitos novos avisos. Um deles assim:
E:\FinReporter\FM_EXT.py:449: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_index,col_indexer] = value instead
quote_df['TVol'] = quote_df['TVol']/TVOL_SCALE
Eu quero saber o que exatamente isso significa? Preciso mudar alguma coisa?
Como devo suspender o aviso se eu insistir em usar quote_df['TVol'] = quote_df['TVol']/TVOL_SCALE
?
A função que gera erros
def _decode_stock_quote(list_of_150_stk_str):
"""decode the webpage and return dataframe"""
from cStringIO import StringIO
str_of_all = "".join(list_of_150_stk_str)
quote_df = pd.read_csv(StringIO(str_of_all), sep=',', names=list('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefg')) #dtype={'A': object, 'B': object, 'C': np.float64}
quote_df.rename(columns={'A':'STK', 'B':'TOpen', 'C':'TPCLOSE', 'D':'TPrice', 'E':'THigh', 'F':'TLow', 'I':'TVol', 'J':'TAmt', 'e':'TDate', 'f':'TTime'}, inplace=True)
quote_df = quote_df.ix[:,[0,3,2,1,4,5,8,9,30,31]]
quote_df['TClose'] = quote_df['TPrice']
quote_df['RT'] = 100 * (quote_df['TPrice']/quote_df['TPCLOSE'] - 1)
quote_df['TVol'] = quote_df['TVol']/TVOL_SCALE
quote_df['TAmt'] = quote_df['TAmt']/TAMT_SCALE
quote_df['STK_ID'] = quote_df['STK'].str.slice(13,19)
quote_df['STK_Name'] = quote_df['STK'].str.slice(21,30)#.decode('gb2312')
quote_df['TDate'] = quote_df.TDate.map(lambda x: x[0:4]+x[5:7]+x[8:10])
return quote_df
Mais mensagens de erro
E:\FinReporter\FM_EXT.py:449: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_index,col_indexer] = value instead
quote_df['TVol'] = quote_df['TVol']/TVOL_SCALE
E:\FinReporter\FM_EXT.py:450: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_index,col_indexer] = value instead
quote_df['TAmt'] = quote_df['TAmt']/TAMT_SCALE
E:\FinReporter\FM_EXT.py:453: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_index,col_indexer] = value instead
quote_df['TDate'] = quote_df.TDate.map(lambda x: x[0:4]+x[5:7]+x[8:10])
python
pandas
dataframe
chained-assignment
Grande erro
fonte
fonte
df.set_value
, docs aqui - pandas.pydata.org/pandas-docs/stable/generated/...df.set_value
foi preterido. Pandas agora recomenda o uso.at[]
ou.iat[]
em seu lugar. docs aqui pandas.pydata.org/pandas-docs/stable/generated/…option_context
aqui: pandas.pydata.org/pandas-docs/stable/user_guide/options.html , use aswith pd.option_context("mode.chained_assignment", None): [...]
Respostas:
O
SettingWithCopyWarning
foi criado para sinalizar atribuições "encadeadas" potencialmente confusas, como as seguintes, que nem sempre funcionam como o esperado, principalmente quando a primeira seleção retorna uma cópia . [consulte GH5390 e GH5597 para discussão em segundo plano.]O aviso oferece uma sugestão para reescrever da seguinte maneira:
No entanto, isso não se adequa ao seu uso, o que equivale a:
Embora esteja claro que você não se importa com as gravações voltando ao quadro original (já que você está substituindo a referência a ele), infelizmente esse padrão não pode ser diferenciado do primeiro exemplo de atribuição encadeada. Daí o aviso (falso positivo). O potencial de falsos positivos é abordado nos documentos sobre indexação , se você quiser ler mais. Você pode desativar com segurança esse novo aviso com a seguinte atribuição.
fonte
.ix
, aprimorados.iloc
etc.) podem definitivamente ser vistos como "o caminho principal" sem avisar a todos incessantemente sobre outros caminhos. Em vez disso, deixe-os serem adultos e, se quiserem fazer tarefas encadeadas, que assim seja. Meus dois centavos de qualquer maneira. Vemos comentários desapontados dos desenvolvedores do Pandas aqui frequentemente, quando tarefas encadeadas funcionarão para resolver um problema, mas não seriam consideradas a maneira "principal" de fazer isso.pd.options.mode.chained_assignment = None
resultou em meu código sendo executado aproximadamente 6 vezes mais rápido. Alguém mais teve resultados semelhantes?Este post é destinado a leitores que,
Configuração
Qual é o
SettingWithCopyWarning
?Para saber como lidar com esse aviso, é importante entender o que significa e por que ele é gerado em primeiro lugar.
Ao filtrar DataFrames, é possível dividir / indexar um quadro para retornar uma visualização ou uma cópia , dependendo do layout interno e de vários detalhes de implementação. Uma "visão" é, como o termo sugere, uma visão dos dados originais; portanto, modificar a visão pode modificar o objeto original. Por outro lado, uma "cópia" é uma replicação de dados do original e a modificação da cópia não afeta o original.
Conforme mencionado por outras respostas, o
SettingWithCopyWarning
foi criado para sinalizar operações de "atribuição encadeada". Consideredf
na configuração acima. Suponha que você queira selecionar todos os valores na coluna "B", onde os valores na coluna "A" são> 5. O Pandas permite fazer isso de maneiras diferentes, algumas mais corretas que outras. Por exemplo,E,
Eles retornam o mesmo resultado, portanto, se você estiver apenas lendo esses valores, não fará diferença. Então qual é o problema? O problema com a atribuição encadeada é que geralmente é difícil prever se uma exibição ou uma cópia é retornada; portanto , isso se torna um problema quando você está tentando atribuir valores novamente. Para desenvolver o exemplo anterior, considere como esse código é executado pelo intérprete:
Com uma única
__setitem__
chamada paradf
. OTOH, considere este código:Agora, dependendo do
__getitem__
retorno de uma exibição ou cópia, a__setitem__
operação pode não funcionar .Em geral, você deve usar
loc
para atribuição baseada em etiqueta e atribuição baseadailoc
em número inteiro / posicional, pois as especificações garantem que elas sempre operem no original. Além disso, para definir uma única célula, você deve usarat
eiat
.Mais pode ser encontrado na documentação .
Apenas me diga como suprimir o aviso!
Considere uma operação simples na coluna "A" de
df
. Selecionar "A" e dividir por 2 aumentará o aviso, mas a operação funcionará.Existem algumas maneiras de silenciar diretamente esse aviso:
Faça um
deepcopy
Mudança
pd.options.mode.chained_assignment
pode ser definida como
None
,"warn"
ou"raise"
."warn"
é o padrão.None
suprimirá o aviso por completo e"raise"
emitirá umSettingWithCopyError
, impedindo a operação.@ Peter Cotton nos comentários, apresentou uma boa maneira de alterar o modo de maneira não invasiva (modificada a partir desta essência ) usando um gerenciador de contexto, para definir o modo apenas pelo tempo necessário e redefini-lo para o estado original quando terminar.
O uso é o seguinte:
Ou, para aumentar a exceção
O "Problema XY": O que estou fazendo de errado?
Na maioria das vezes, os usuários tentam procurar maneiras de suprimir essa exceção sem entender completamente por que ela foi criada. Este é um bom exemplo de um problema XY , em que os usuários tentam resolver um problema "Y" que é na verdade um sintoma de um problema profundamente enraizado "X". As perguntas serão levantadas com base em problemas comuns que encontram esse aviso e as soluções serão apresentadas.
Maneira errada de fazer isso:
Caminho certo usando
loc
:Você pode usar qualquer um dos seguintes métodos para fazer isso.
Na verdade, isso provavelmente ocorre por causa do código mais alto no seu pipeline. Você criou a
df2
partir de algo maior, como? Nesse caso, a indexação booleana retornará uma exibição, e
df2
fará referência ao original. O que você precisa fazer é atribuirdf2
a uma cópia :Isso ocorre porque
df2
deve ter sido criado como uma visualização de alguma outra operação de fatiamento, comoA solução aqui é que quer fazer um
copy()
dosdf
, ou o usoloc
, como antes.fonte
Em geral, o objetivo
SettingWithCopyWarning
é mostrar aos usuários (e especialmente aos novos) que eles podem estar operando em uma cópia e não no original como pensam. Não são falsos positivos (IOW, se você sabe o que está fazendo que poderia ser ok ). Uma possibilidade é simplesmente desativar o aviso (por padrão avisar ) como sugerido pelo @Garrett.Aqui está outra opção:
Você pode definir o
is_copy
sinalizador comoFalse
, que efetivamente desativará a verificação, para esse objeto :Se você copiar explicitamente, nenhum aviso adicional ocorrerá:
O código que o OP está mostrando acima, apesar de legítimo, e provavelmente algo que eu também faço, é tecnicamente um caso para esse aviso, e não um falso positivo. Outra maneira de não receber o aviso seria fazer a operação de seleção via
reindex
, por exemplo,Ou,
fonte
0.16
eu ver muitos mais falsos positivos, o problema com falsos positivos é que se aprende a ignorá-lo, mesmo que às vezes seja legítimo.undefined
comportamento. Se alguma coisa deve gerar um erro então (para evitar armadilhas vistas emC
), uma vez queapi
é congelado, o comportamento atual do aviso faz sentido para compatibilidade com versões anteriores. E vou fazê-los jogar para pegá-los como erros no meu código de produção (warnings.filterwarnings('error', r'SettingWithCopyWarning
). Além disso, a sugestão de uso.loc
às vezes também não ajuda (se estiver em um grupo).Aviso de cópia do dataframe do Pandas
Quando você vai fazer algo assim:
pandas.ix
nesse caso, retorna um novo quadro de dados independente.Quaisquer valores que você decidir alterar nesse quadro de dados não mudarão o quadro de dados original.
É sobre isso que os pandas tentam avisá-lo.
Por que
.ix
é uma má ideiaO
.ix
objeto tenta fazer mais de uma coisa e, para quem leu alguma coisa sobre código limpo, esse é um cheiro forte.Dado esse quadro de dados:
Dois comportamentos:
Comportamento um:
dfcopy
agora é um quadro de dados independente. Mudando isso não vai mudardf
Comportamento dois: isso altera o quadro de dados original.
Use em
.loc
vez dissoOs desenvolvedores do pandas reconheceram que o
.ix
objeto era bastante fedorento [especulativamente] e, assim, criaram dois novos objetos que ajudam na acessão e atribuição de dados. (O outro ser.iloc
).loc
é mais rápido, porque não tenta criar uma cópia dos dados..loc
destina-se a modificar o quadro de dados existente no local, o que é mais eficiente em memória..loc
é previsível, tem um comportamento.A solução
O que você está fazendo no seu exemplo de código é carregar um arquivo grande com muitas colunas e modificá-lo para que seja menor.
A
pd.read_csv
função pode ajudá-lo com muito disso e também agilizar o carregamento do arquivo.Então, ao invés de fazer isso
Faça isso
Isso lerá apenas as colunas das quais você está interessado e as nomeie adequadamente. Não há necessidade de usar o
.ix
objeto maligno para fazer coisas mágicas.fonte
.iloc
. Estes são os dois métodos principais para indexar estruturas de dados de pandas. Leia mais na documentação.Aqui eu respondo a pergunta diretamente. Como lidar com isso?
Faça um
.copy(deep=False)
depois de cortar. Consulte pandas.DataFrame.copy .Espere, uma fatia não retorna uma cópia? Afinal, é isso que a mensagem de aviso está tentando dizer? Leia a resposta longa:
Isso dá um aviso:
Isto não:
Ambos
df0
edf1
sãoDataFrame
objetos, mas alguma coisa sobre eles é diferente que permite pandas para imprimir o aviso. Vamos descobrir o que é isso.Usando sua ferramenta diff de escolha, você verá que, além de alguns endereços, a única diferença material é esta:
O método que decide se deve avisar é
DataFrame._check_setitem_copy
quais verificações_is_copy
. Então aqui está você. Faça umcopy
para que seu DataFrame não seja_is_copy
.O aviso está sugerindo o uso
.loc
, mas se você usar.loc
em um quadro que_is_copy
, você ainda receberá o mesmo aviso. Enganador? Sim. Irritante? Pode apostar. Útil? Potencialmente, quando a atribuição encadeada é usada. Mas ele não pode detectar corretamente a atribuição da cadeia e imprime o aviso indiscriminadamente.fonte
Este tópico é realmente confuso com os pandas. Felizmente, ele tem uma solução relativamente simples.
O problema é que nem sempre é claro se as operações de filtragem de dados (por exemplo, loc) retornam uma cópia ou uma exibição do DataFrame. O uso posterior desse DataFrame filtrado pode, portanto, ser confuso.
A solução simples é (a menos que você precise trabalhar com conjuntos muito grandes de dados):
Sempre que você precisar atualizar algum valor, sempre copie implicitamente o DataFrame antes da atribuição.
fonte
Para remover qualquer dúvida, minha solução foi fazer uma cópia profunda da fatia em vez de uma cópia regular. Isso pode não ser aplicável, dependendo do seu contexto (restrições de memória / tamanho da fatia, potencial de degradação do desempenho - especialmente se a cópia ocorrer em um loop como ocorreu comigo, etc ...)
Para ficar claro, aqui está o aviso que recebi:
Ilustração
Eu tinha dúvidas de que o aviso foi lançado por causa de uma coluna que eu estava soltando em uma cópia da fatia. Embora não esteja tecnicamente tentando definir um valor na cópia da fatia, isso ainda era uma modificação da cópia da fatia. Abaixo estão os passos (simplificados) que tomei para confirmar a suspeita, espero que ajude aqueles de nós que estão tentando entender o aviso.
Exemplo 1: soltar uma coluna no original afeta a cópia
Já sabíamos disso, mas este é um lembrete saudável. NÃO é disso que trata o aviso.
É possível evitar alterações feitas no df1 para afetar o df2
Exemplo 2: soltar uma coluna na cópia pode afetar o original
Na verdade, isso ilustra o aviso.
É possível evitar alterações feitas no df2 para afetar o df1
Felicidades!
fonte
Isso deve funcionar:
fonte
Alguns podem querer simplesmente suprimir o aviso:
fonte
Se você atribuiu a fatia a uma variável e deseja definir usando a variável como a seguir:
E você não deseja usar a solução Jeffs porque sua condição de computação
df2
é longa ou por algum outro motivo, você pode usar o seguinte:df2.index.tolist()
retorna os índices de todas as entradas no df2, que serão usadas para definir a coluna B no quadro de dados original.fonte
Para mim, esse problema ocorreu em um exemplo a seguir> simplificado. E eu também fui capaz de resolvê-lo (espero que com uma solução correta):
código antigo com aviso:
Isso imprimiu o aviso para a linha
old_row[field] = new_row[field]
Como as linhas no método update_row são realmente do tipo
Series
, substituí a linha por:ou seja, método para acessar / pesquisas para a
Series
. Mesmo que ambos funcionem bem e o resultado seja o mesmo, dessa forma não preciso desativar os avisos (= mantenha-os para outros problemas de indexação de cadeia em outro lugar).Espero que isso ajude alguém.
fonte
Você poderia evitar todo o problema como este, acredito:
Usando Atribuir. Na documentação : atribua novas colunas a um DataFrame, retornando um novo objeto (uma cópia) com todas as colunas originais, além das novas.
Veja o artigo de Tom Augspurger sobre encadeamento de métodos em pandas: https://tomaugspurger.github.io/method-chaining
fonte
Pergunta / observação para iniciantes de acompanhamento
Talvez um esclarecimento para outros iniciantes como eu (eu sou do R, que parece funcionar de maneira um pouco diferente sob o capô). O seguinte código funcional e de aparência inofensiva continuava produzindo o aviso SettingWithCopy, e eu não conseguia entender o porquê. Eu tinha lido e entendido o emitido com "indexação em cadeia", mas meu código não contém nenhum:
Mas depois, tarde demais, observei onde a função plot () é chamada:
Portanto, "df" não é um quadro de dados, mas um objeto que lembra de alguma forma que ele foi criado pela indexação de um quadro de dados (o que é uma visão?) Que faria a linha no plot ()
equivalente a
que é uma indexação encadeada. Eu entendi direito?
De qualquer forma,
consertou.
fonte
Como essa pergunta já está totalmente explicada e discutida nas respostas existentes, fornecerei uma
pandas
abordagem clara ao gerenciador de contexto usandopandas.option_context
(links para documentos e exemplo ) - não há absolutamente nenhuma necessidade de criar uma classe personalizada com todos os métodos de dunder e outros sinos e assobios.Primeiro, o próprio código de gerenciador de contexto:
Então um exemplo:
Vale a pena notar que as duas abordagens não são modificadas
a
, o que é um pouco surpreendente para mim, e mesmo uma cópia rasa de df.copy(deep=False)
impediria que esse aviso fosse acionado (tanto quanto eu entendo, a cópia rasa deve pelo menos modificara
também, mas não épandas
mágica.).fonte
Eu estava recebendo esse problema
.apply()
ao atribuir um novo quadro de dados a partir de um quadro de dados preexistente no qual usei o.query()
método. Por exemplo:Retornaria esse erro. A correção que parece resolver o erro nesse caso é alterando isso para:
No entanto, isso NÃO é eficiente, especialmente ao usar quadros de dados grandes, devido à necessidade de fazer uma nova cópia.
Se você estiver usando o
.apply()
método para gerar uma nova coluna e seus valores, uma correção que resolve o erro e é mais eficiente é adicionar.reset_index(drop=True)
:fonte