FutureWarning: falha na comparação elemento a elemento; retornando escalar, mas no futuro executará a comparação elemento a elemento

108

Estou usando o Pandas 0.19.1no Python 3. Estou recebendo um aviso nessas linhas de código. Estou tentando obter uma lista que contém todos os números de linha onde string Peterestá presente na coluna Unnamed: 5.

df = pd.read_excel(xls_path)
myRows = df[df['Unnamed: 5'] == 'Peter'].index.tolist()

Produz um Aviso:

"\Python36\lib\site-packages\pandas\core\ops.py:792: FutureWarning: elementwise 
comparison failed; returning scalar, but in the future will perform 
elementwise comparison 
result = getattr(x, name)(y)"

O que é isso FutureWarning e devo ignorá-lo, pois parece funcionar.

Eric Leschinski
fonte

Respostas:

172

Este FutureWarning não é do Pandas, é do numpy e o bug também afeta matplotlib e outros. Veja como reproduzir o aviso mais próximo da origem do problema:

import numpy as np
print(np.__version__)   # Numpy version '1.12.0'
'x' in np.arange(5)       #Future warning thrown here

FutureWarning: elementwise comparison failed; returning scalar instead, but in the 
future will perform elementwise comparison
False

Outra maneira de reproduzir esse bug usando o operador double equals:

import numpy as np
np.arange(5) == np.arange(5).astype(str)    #FutureWarning thrown here

Um exemplo de Matplotlib afetado por este FutureWarning em sua implementação de plot de quiver: https://matplotlib.org/examples/pylab_examples/quiver_demo.html

O que está acontecendo aqui?

Há uma discordância entre o Numpy e o python nativo sobre o que deve acontecer quando você compara strings com os tipos numéricos de numpy. Observe que o operando esquerdo é turf de python, uma string primitiva, e a operação do meio é turf de python, mas o operando direito é turf de numpy. Você deve retornar um escalar de estilo Python ou um ndarray de estilo Numpy de booleano? Numpy diz ndarray de bool, os desenvolvedores Pythonic discordam. Impasse clássico.

Deve ser uma comparação elementar ou escalar se o item existir na matriz?

Se o seu código ou biblioteca estiver usando os operadores inou ==para comparar a string python com ndarrays numpy, eles não são compatíveis, então, se você tentar, ele retornará um escalar, mas apenas por enquanto. O Aviso indica que, no futuro, esse comportamento pode mudar, então seu código vomita no carpete se python / numpy decidir adotar o estilo Numpy.

Relatórios de bug enviados:

Numpy e Python estão em um impasse, por enquanto a operação retorna um escalar, mas no futuro isso pode mudar.

https://github.com/numpy/numpy/issues/6784

https://github.com/pandas-dev/pandas/issues/7830

Duas soluções alternativas:

Bloqueie sua versão de python e numpy, ignore os avisos e espere que o comportamento não mude ou converta os operandos esquerdo e direito de ==e inpara um tipo numpy ou tipo numérico python primitivo.

Suprima o aviso globalmente:

import warnings
import numpy as np
warnings.simplefilter(action='ignore', category=FutureWarning)
print('x' in np.arange(5))   #returns False, without Warning

Suprima o aviso linha a linha.

import warnings
import numpy as np

with warnings.catch_warnings():
    warnings.simplefilter(action='ignore', category=FutureWarning)
    print('x' in np.arange(2))   #returns False, warning is suppressed

print('x' in np.arange(10))   #returns False, Throws FutureWarning

Apenas suprima o aviso pelo nome, coloque um comentário alto ao lado dele mencionando a versão atual do python e numpy, dizendo que este código é frágil e requer essas versões e coloque um link para aqui. Chute a lata pela estrada.

TLDR: pandas são Jedi; numpysão as cabanas; e pythoné o império galáctico. https://youtu.be/OZczsiCfQQk?t=3

Eric Leschinski
fonte
1
Ugh. Portanto, se eu tiver alguma quantidade thing(que pode ou não ser um tipo entorpecido; não sei) e quero ver se thing == 'some string'e obter um boolresultado simples , o que devo fazer? np.atleast_1d(thing)[0] == 'some string'? Mas isso não é robusto para algum curinga colocando 'some string'o primeiro elemento de um array. Acho que tenho que testar o tipo de thingprimeiro e só fazer o ==teste se for uma string (ou não um objeto entorpecido).
EL_DON
1
Na verdade, este aviso futuro também é gerado sempre que você tenta comparar um numpy.ndarray com uma lista vazia. Por exemplo, a execução também np.array([1, 2]) == []gerará o aviso.
1313e
2
Eu acharia útil ver um exemplo de como fazer isso:or babysit your left and right operands to be from a common turf
HaPsantran
12
Este é um nível incrível de informação de qualidade sobre este assunto.
StephenBoesch
Então, eu me livraria do aviso neste código: df.loc [df.cName == '', 'cName'] = '10004'. Em outras palavras, qual é o equivalente pandas / numpy ao '' (string vazia) de python
Garet Jax
13

Eu recebo o mesmo erro quando tento definir a index_colleitura de um arquivo em um Pandadata-frame de:

df = pd.read_csv('my_file.tsv', sep='\t', header=0, index_col=['0'])  ## or same with the following
df = pd.read_csv('my_file.tsv', sep='\t', header=0, index_col=[0])

Nunca encontrei tal erro anteriormente. Ainda estou tentando descobrir a razão por trás disso (usando a explicação de @Eric Leschinski e outras).

De qualquer forma, a abordagem a seguir resolve o problema por enquanto, até que eu descubra o motivo:

df = pd.read_csv('my_file.tsv', sep='\t', header=0)  ## not setting the index_col
df.set_index(['0'], inplace=True)

Vou atualizar isso assim que descobrir o motivo de tal comportamento.

Dataman
fonte
Eu tenho o mesmo problema com read_csv(). Parece-me algo que pandasprecisa ser consertado.
Konstantin
1
Obrigado! Me poupou muito trabalho - eu acho. pd__version__: 0.22.0; np.__version__: 1.15.4
Markus Dutschke
1
O mesmo problema aqui, aparentemente alguma chamada numpy interna read_csvao usar o index_colparâmetro. Testei duas configurações com resultados diferentes: 1. numpy versão 1.19.2, Pandas versão 1.1.2: FutureWarning: falha na comparação elementwise ... 2. numpy versão 1.19.2, Pandas versão 1.1.3: TypeError: ufunc ' isnan 'não suportado ...
Carlos
9

Minha experiência com a mesma mensagem de aviso foi causada por TypeError.

TypeError: comparação de tipo inválido

Então, você pode querer verificar o tipo de dados do Unnamed: 5

for x in df['Unnamed: 5']:
  print(type(x))  # are they 'str' ?

Aqui está como posso replicar a mensagem de aviso:

import pandas as pd
import numpy as np
df = pd.DataFrame(np.random.randn(3, 2), columns=['num1', 'num2'])
df['num3'] = 3
df.loc[df['num3'] == '3', 'num3'] = 4  # TypeError and the Warning
df.loc[df['num3'] == 3, 'num3'] = 4  # No Error

Espero que ajude.

yhd.leung
fonte
1
Seu código tem muitas partes móveis desnecessárias para ilustrar o aviso. O Pandas agrada você com aquele TypeError extra, mas isso é controle de danos do Pandas. O aviso de origem é uma discordância entre o Numpy e o Python e ocorre na avaliação de df['num3'] == '3'.
Eric Leschinski
1
df.loc[df['num3'] == 3, 'num3'] = 4 # No ErrorEssa parte me ajuda. Obrigado
jameslem
9

Impossível superar a resposta incrivelmente detalhada de Eric Leschinski, mas aqui está uma rápida solução alternativa para a pergunta original que eu acho que ainda não foi mencionada - coloque a string em uma lista e use em .isinvez de==

Por exemplo:

import pandas as pd
import numpy as np

df = pd.DataFrame({"Name": ["Peter", "Joe"], "Number": [1, 2]})

# Raises warning using == to compare different types:
df.loc[df["Number"] == "2", "Number"]

# No warning using .isin:
df.loc[df["Number"].isin(["2"]), "Number"]
Toby Petty
fonte
Gostaria de saber se eu poderia fazer o mesmo com esta sintaxe -> if "-" em dfN ['Drate']. unique ()
lone_coder
3

Uma solução rápida para isso é usar numpy.core.defchararray. Também enfrentei a mesma mensagem de aviso e consegui resolvê-la usando o módulo acima.

import numpy.core.defchararray as npd
resultdataset = npd.equal(dataset1, dataset2)
Jeet23
fonte
2

A resposta de Eric explica de forma útil que o problema vem da comparação de uma série Pandas (contendo uma matriz NumPy) com uma string Python. Infelizmente, suas duas soluções alternativas apenas suprimem o aviso.

Para escrever código que não causa o aviso em primeiro lugar, compare explicitamente sua string com cada elemento da Série e obtenha um bool separado para cada um. Por exemplo, você pode usar mape uma função anônima.

myRows = df[df['Unnamed: 5'].map( lambda x: x == 'Peter' )].index.tolist()
Nathan
fonte
1

Se seus arrays não forem muito grandes ou se você não tiver muitos deles, você pode conseguir forçar o lado esquerdo de ==a ser uma string:

myRows = df[str(df['Unnamed: 5']) == 'Peter'].index.tolist()

Mas isso é cerca de 1,5 vezes mais lento se df['Unnamed: 5']for uma string, 25-30 vezes mais lento se df['Unnamed: 5']for uma pequena matriz numpy (comprimento = 10) e 150-160 vezes mais lento se for uma matriz numpy com comprimento 100 (tempos em média acima de 500 tentativas) .

a = linspace(0, 5, 10)
b = linspace(0, 50, 100)
n = 500
string1 = 'Peter'
string2 = 'blargh'
times_a = zeros(n)
times_str_a = zeros(n)
times_s = zeros(n)
times_str_s = zeros(n)
times_b = zeros(n)
times_str_b = zeros(n)
for i in range(n):
    t0 = time.time()
    tmp1 = a == string1
    t1 = time.time()
    tmp2 = str(a) == string1
    t2 = time.time()
    tmp3 = string2 == string1
    t3 = time.time()
    tmp4 = str(string2) == string1
    t4 = time.time()
    tmp5 = b == string1
    t5 = time.time()
    tmp6 = str(b) == string1
    t6 = time.time()
    times_a[i] = t1 - t0
    times_str_a[i] = t2 - t1
    times_s[i] = t3 - t2
    times_str_s[i] = t4 - t3
    times_b[i] = t5 - t4
    times_str_b[i] = t6 - t5
print('Small array:')
print('Time to compare without str conversion: {} s. With str conversion: {} s'.format(mean(times_a), mean(times_str_a)))
print('Ratio of time with/without string conversion: {}'.format(mean(times_str_a)/mean(times_a)))

print('\nBig array')
print('Time to compare without str conversion: {} s. With str conversion: {} s'.format(mean(times_b), mean(times_str_b)))
print(mean(times_str_b)/mean(times_b))

print('\nString')
print('Time to compare without str conversion: {} s. With str conversion: {} s'.format(mean(times_s), mean(times_str_s)))
print('Ratio of time with/without string conversion: {}'.format(mean(times_str_s)/mean(times_s)))

Resultado:

Small array:
Time to compare without str conversion: 6.58464431763e-06 s. With str conversion: 0.000173756599426 s
Ratio of time with/without string conversion: 26.3881526541

Big array
Time to compare without str conversion: 5.44309616089e-06 s. With str conversion: 0.000870866775513 s
159.99474375821288

String
Time to compare without str conversion: 5.89370727539e-07 s. With str conversion: 8.30173492432e-07 s
Ratio of time with/without string conversion: 1.40857605178
EL_DON
fonte
1
Colocar o prefixo à esquerda de ==com strfoi uma boa solução para mim que mal prejudicou o desempenho em 1,5 milhão de linhas que não ficarão maiores que isso no futuro.
David Erickson
1

No meu caso, o aviso ocorreu apenas por causa do tipo regular de indexação booleana - porque a série tinha apenas np.nan. Demonstração (pandas 1.0.3):

>>> import pandas as pd
>>> import numpy as np
>>> pd.Series([np.nan, 'Hi']) == 'Hi'
0    False
1     True
>>> pd.Series([np.nan, np.nan]) == 'Hi'
~/anaconda3/envs/ms3/lib/python3.7/site-packages/pandas/core/ops/array_ops.py:255: FutureWarning: elementwise comparison failed; returning scalar instead, but in the future will perform elementwise comparison
  res_values = method(rvalues)
0    False
1    False

Acho que com o pandas 1.0 eles realmente querem que você use o novo 'string'tipo de dados que permite pd.NAvalores:

>>> pd.Series([pd.NA, pd.NA]) == 'Hi'
0    False
1    False
>>> pd.Series([np.nan, np.nan], dtype='string') == 'Hi'
0    <NA>
1    <NA>
>>> (pd.Series([np.nan, np.nan], dtype='string') == 'Hi').fillna(False)
0    False
1    False

Não adoro quando eles mexem nas funcionalidades do dia a dia, como a indexação booleana.

Jeyes Unterwegs
fonte
0

Recebi este aviso porque pensei que minha coluna continha strings nulas, mas, ao verificar, ela continha np.nan!

if df['column'] == '':

Mudar minha coluna para strings vazias ajudou :)

intotecho
fonte
0

Eu comparei alguns dos métodos possíveis para fazer isso, incluindo pandas, vários métodos numpy e um método de compreensão de lista.

Primeiro, vamos começar com uma linha de base:

>>> import numpy as np
>>> import operator
>>> import pandas as pd

>>> x = [1, 2, 1, 2]
>>> %time count = np.sum(np.equal(1, x))
>>> print("Count {} using numpy equal with ints".format(count))
CPU times: user 52 µs, sys: 0 ns, total: 52 µs
Wall time: 56 µs
Count 2 using numpy equal with ints

Portanto, nossa linha de base é que a contagem deve estar correta 2e devemos levar cerca de 50 us.

Agora, tentamos o método ingênuo:

>>> x = ['s', 'b', 's', 'b']
>>> %time count = np.sum(np.equal('s', x))
>>> print("Count {} using numpy equal".format(count))
CPU times: user 145 µs, sys: 24 µs, total: 169 µs
Wall time: 158 µs
Count NotImplemented using numpy equal
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/ipykernel_launcher.py:1: FutureWarning: elementwise comparison failed; returning scalar instead, but in the future will perform elementwise comparison
  """Entry point for launching an IPython kernel.

E aqui, obtemos a resposta errada ( NotImplemented != 2), leva muito tempo e lança o aviso.

Então, vamos tentar outro método ingênuo:

>>> %time count = np.sum(x == 's')
>>> print("Count {} using ==".format(count))
CPU times: user 46 µs, sys: 1 µs, total: 47 µs
Wall time: 50.1 µs
Count 0 using ==

Novamente, a resposta errada ( 0 != 2). Isso é ainda mais insidioso porque não há avisos subsequentes (0 pode ser transmitido como2 ).

Agora, vamos tentar uma compreensão de lista:

>>> %time count = np.sum([operator.eq(_x, 's') for _x in x])
>>> print("Count {} using list comprehension".format(count))
CPU times: user 55 µs, sys: 1 µs, total: 56 µs
Wall time: 60.3 µs
Count 2 using list comprehension

Temos a resposta certa aqui, e é muito rápido!

Outra possibilidade pandas:

>>> y = pd.Series(x)
>>> %time count = np.sum(y == 's')
>>> print("Count {} using pandas ==".format(count))
CPU times: user 453 µs, sys: 31 µs, total: 484 µs
Wall time: 463 µs
Count 2 using pandas ==

Lento, mas correto!

E, finalmente, a opção que vou usar: converter a numpymatriz para o objecttipo:

>>> x = np.array(['s', 'b', 's', 'b']).astype(object)
>>> %time count = np.sum(np.equal('s', x))
>>> print("Count {} using numpy equal".format(count))
CPU times: user 50 µs, sys: 1 µs, total: 51 µs
Wall time: 55.1 µs
Count 2 using numpy equal

Rápido e correto!

ahagen
fonte
Então IIUC, para consertar 'x' in np.arange(5), você sugere simplesmente fazer 'x' in np.arange(5).astype(object)(ou similarmente :) 'x' == np.arange(5).astype(object). Direito? IMHO, esta é a solução alternativa mais elegante mostrada aqui, por isso estou confuso com a falta de votos positivos. Talvez editar sua resposta para começar com o resultado final e, em seguida, passar para a análise de desempenho agradável?
Oren Milman
Obrigado @Oren, vou tentar isso e ver onde isso me leva.
ahagen
0

Eu tinha este código que estava causando o erro:

for t in dfObj['time']:
  if type(t) == str:
    the_date = dateutil.parser.parse(t)
    loc_dt_int = int(the_date.timestamp())
    dfObj.loc[t == dfObj.time, 'time'] = loc_dt_int

Eu mudei para isto:

for t in dfObj['time']:
  try:
    the_date = dateutil.parser.parse(t)
    loc_dt_int = int(the_date.timestamp())
    dfObj.loc[t == dfObj.time, 'time'] = loc_dt_int
  except Exception as e:
    print(e)
    continue

para evitar a comparação, que está lançando o aviso - como afirmado acima. Eu só tive que evitar a exceção por causa do dfObj.locloop for, talvez haja uma maneira de dizer a ele para não verificar as linhas que já foram alteradas.

ewizard
fonte