Detectar se uma matriz NumPy contém, pelo menos, um valor numérico não-?
Pergunta
Eu preciso escrever uma função que irá detectar se a entrada contém pelo menos um valor que não é numérico. Se um valor não numérico é encontrado eu vou levantar um erro (porque o cálculo só deve retornar um valor numérico). O número de dimensões da matriz de entrada não é conhecido antecipadamente - a função deve dar o valor correcto, independentemente do ndim. Como uma complicação adicional a entrada poderia ser um único float ou numpy.float64
ou mesmo algo excêntrico como uma matriz de dimensão zero.
A maneira óbvia de resolver isso é escrever uma função recursiva que itera sobre cada objeto iterable na matriz até encontrar um não-iterabe. Ele vai aplicar a função numpy.isnan()
sobre cada objeto não-iterable. Se pelo menos um valor não numérico é encontrado, em seguida, a função retornará falso imediatamente. Caso contrário, se todos os valores na iterable são numéricos ele irá eventualmente retornar True.
Isso funciona muito bem, mas é muito lento e eu espero que NumPy tem uma muito melhor maneira de fazê-lo. O que é uma alternativa que é mais rápido e mais numpyish?
Aqui está a 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
Solução
Esta deve ser mais rápido do que a iteração e funcionará independentemente da forma.
numpy.isnan(myarray).any()
Edit: 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
Resultado:
0.11 s numpy.isnan(a).any()
3.75 s any(numpy.isnan(x) for x in a.flatten())
Bonus: ele funciona muito bem para não-matriz tipos Numpy:
>>> a = numpy.float64(42.)
>>> numpy.isnan(a).any()
False
>>> a = numpy.float64(numpy.nan)
>>> numpy.isnan(a).any()
True
Outras dicas
Se o infinito é um valor possível, gostaria de usar numpy.isfinite
numpy.isfinite(myarray).all()
Se os avalia acima para True
, então myarray
não contém, numpy.nan
, numpy.inf
ou -numpy.inf
valores.
numpy.nan
será OK com valores numpy.inf
, 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)
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 em comparações não foi consistente em versões anteriores.
(np.where(np.isnan(A)))[0].shape[0]
será maior do que 0
se A
contém, pelo menos, um elemento de nan
, A
poderia ser uma matriz n x m
.
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"
Pfft! Microssegundos! Nunca resolver um problema em microssegundos que podem ser resolvidos em nanossegundos.
Note que a resposta aceita:
- itera sobre todo os dados, independentemente de um nan é encontrado
- cria uma matriz temporária de tamanho N, que é redundante.
A melhor solução é retornar True imediatamente quando NAN é encontrada:
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 obras para a 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 precoce é de 3 ordens ou aceleração magnitude (em alguns casos). Nada mal para uma simples anotação.