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

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

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)

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.


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.


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"
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.