Detectar se uma matriz NumPy contém pelo menos um valor não numérico?

103

Eu preciso escrever uma função que detectará se a entrada contém pelo menos um valor que não é numérico. Se um valor não numérico for encontrado, levantarei um erro (porque o cálculo deve retornar apenas um valor numérico). O número de dimensões da matriz de entrada não é conhecido com antecedência - a função deve fornecer o valor correto independentemente de ndim. Como complicação extra, a entrada pode ser um único float numpy.float64ou mesmo algo estranho como um array de dimensão zero.

A maneira óbvia de resolver isso é escrever uma função recursiva que itera sobre cada objeto iterável na matriz até encontrar um não iterável. Vai aplicar onumpy.isnan() função a todos os objetos não iteráveis. Se pelo menos um valor não numérico for encontrado, a função retornará False imediatamente. Caso contrário, se todos os valores no iterável forem numéricos, ele eventualmente retornará True.

Isso funciona muito bem, mas é muito lento e espero que o NumPy tenha uma maneira muito melhor de fazer isso. Qual alternativa é mais rápida e entorpecente?

Aqui está minha maquete:

def contains_nan( myarray ):
    """
    @param myarray : An n-dimensional array or a single float
    @type myarray : numpy.ndarray, numpy.array, float
    @returns: bool
    Returns true if myarray is numeric or only contains numeric values.
    Returns false if at least one non-numeric value exists
    Not-A-Number is given by the numpy.isnan() function.
    """
    return True
Salim Fadhley
fonte
3
Sua descrição para contains_nanparece suspeita: "Retorna falso se houver pelo menos um valor não numérico". Eu teria esperado contains_nanretornar Truese a matriz contivesse NaN.
Samuel Tardieu
E quanto a entradas como array(['None', 'None'], dtype=object)? Essa entrada deve apenas gerar uma exceção?
Finn Årup Nielsen
NÃO use float('nan') in x. Não funciona.
Charlie Parker

Respostas:

183

Isso deve ser mais rápido do que iterar e funcionará independentemente da forma.

numpy.isnan(myarray).any()

Editar: 30x mais rápido:

import timeit
s = 'import numpy;a = numpy.arange(10000.).reshape((100,100));a[10,10]=numpy.nan'
ms = [
    'numpy.isnan(a).any()',
    'any(numpy.isnan(x) for x in a.flatten())']
for m in ms:
    print "  %.2f s" % timeit.Timer(m, s).timeit(1000), m

Resultados:

  0.11 s numpy.isnan(a).any()
  3.75 s any(numpy.isnan(x) for x in a.flatten())

Bônus: funciona bem para tipos NumPy sem matriz:

>>> a = numpy.float64(42.)
>>> numpy.isnan(a).any()
False
>>> a = numpy.float64(numpy.nan)
>>> numpy.isnan(a).any()
True
Paulo
fonte
1
com numpy 1.7, a versão flatten () é apenas duas vezes mais rápida que a primeira
Christian Geier,
Por que algo como float('nan') in xnão funciona? Eu tentei e python retorna Falseonde x = [1,2,3,float('nan')].
Charlie Parker
1
@CharlieParker pela mesma razão porque float ('nan') == float ('nan') retornará False. NaN não é igual a NaN. Mais informações aqui: stackoverflow.com/questions/10034149/…
Muppet
1
@mab: Isso ocorre porque chamar numpy.anyum genexp apenas retorna o genexp; você não está realmente fazendo o cálculo que pensa que está. Nunca chame numpy.anyum genexp.
user2357112 suporta Monica
No cenário de depuração real, eu também recomendaria olhar em np.isfinitevez de np.isnandetectar estouros numéricos, instabilidade, etc.
Ben Usman
18

Se infinito for um valor possível, eu usaria numpy.isfinite

numpy.isfinite(myarray).all()

Se o resultado acima for True, então myarraynão contém nenhum valor numpy.nan, numpy.infou -numpy.inf.

numpy.nanestará OK com numpy.infvalores, por exemplo:

In [11]: import numpy as np

In [12]: b = np.array([[4, np.inf],[np.nan, -np.inf]])

In [13]: np.isnan(b)
Out[13]: 
array([[False, False],
       [ True, False]], dtype=bool)

In [14]: np.isfinite(b)
Out[14]: 
array([[ True, False],
       [False, False]], dtype=bool)
Akavall
fonte
Por que algo como float('nan') in xnão funciona? Eu tentei e python retorna Falseonde x = [1,2,3,float('nan')].
Charlie Parker
1
@CharlieParker porque dois nans não são considerados iguais um ao outro. Experimente float('nan') == float('nan').
Akavall
interessante. Por que eles não são considerados iguais?
Charlie Parker
1
@CharlieParker, não acho que poderia dar uma resposta muito boa aqui. Talvez seja isso que você está procurando: stackoverflow.com/questions/1565164/…
Akavall
4

Pfft! Microssegundos! Nunca resolva um problema em microssegundos que pode ser resolvido em nanossegundos.

Observe que a resposta aceita:

  • itera sobre todos os dados, independentemente de um nan ser encontrado
  • cria uma matriz temporária de tamanho N, que é redundante.

Uma solução melhor é retornar True imediatamente quando NAN for encontrado:

import numba
import numpy as np

NAN = float("nan")

@numba.njit(nogil=True)
def _any_nans(a):
    for x in a:
        if np.isnan(x): return True
    return False

@numba.jit
def any_nans(a):
    if not a.dtype.kind=='f': return False
    return _any_nans(a.flat)

array1M = np.random.rand(1000000)
assert any_nans(array1M)==False
%timeit any_nans(array1M)  # 573us

array1M[0] = NAN
assert any_nans(array1M)==True
%timeit any_nans(array1M)  # 774ns  (!nanoseconds)

e funciona para n-dimensões:

array1M_nd = array1M.reshape((len(array1M)/2, 2))
assert any_nans(array1M_nd)==True
%timeit any_nans(array1M_nd)  # 774ns

Compare isso com a solução nativa numpy:

def any_nans(a):
    if not a.dtype.kind=='f': return False
    return np.isnan(a).any()

array1M = np.random.rand(1000000)
assert any_nans(array1M)==False
%timeit any_nans(array1M)  # 456us

array1M[0] = NAN
assert any_nans(array1M)==True
%timeit any_nans(array1M)  # 470us

%timeit np.isnan(array1M).any()  # 532us

O método de saída antecipada é de 3 ordens ou aumento de magnitude (em alguns casos). Nada mal para uma anotação simples.

usuário 48956
fonte
3

Com numpy 1.3 ou svn você pode fazer isso

In [1]: a = arange(10000.).reshape(100,100)

In [3]: isnan(a.max())
Out[3]: False

In [4]: a[50,50] = nan

In [5]: isnan(a.max())
Out[5]: True

In [6]: timeit isnan(a.max())
10000 loops, best of 3: 66.3 µs per loop

O tratamento de nans nas comparações não era consistente nas versões anteriores.


fonte
Por que algo como float('nan') in xnão funciona? Eu tentei e python retorna Falseonde x = [1,2,3,float('nan')].
Charlie Parker
@CharlieParker ... porque a comparação com NAN não faz o que você espera. NAN é tratado como um NULL lógico (= não sei). float("nan")==float("nan")dar False(embora seja viável provavelmente deva retornar NAN ou Nenhum). Da mesma forma, estranheza com NAN e boolen NULL é verdadeira em muitas linguagens, incluindo SQL (onde NULL = NULL nunca é verdadeiro).
user48956
2

(np.where(np.isnan(A)))[0].shape[0]será maior do que 0se Acontiver pelo menos um elemento de nan, Apode ser umn x m matriz.

Exemplo:

import numpy as np

A = np.array([1,2,4,np.nan])

if (np.where(np.isnan(A)))[0].shape[0]: 
    print "A contains nan"
else:
    print "A does not contain nan"
Ting On Chan
fonte