Verificação rápida de NaN no NumPy

120

Estou procurando a maneira mais rápida de verificar a ocorrência de NaN ( np.nan) em uma matriz NumPy X. np.isnan(X)está fora de questão, uma vez que cria uma matriz booleana de forma X.shape, que é potencialmente gigantesca.

Eu tentei np.nan in X, mas isso parece não funcionar porque np.nan != np.nan. Existe uma maneira rápida e eficiente de memória de fazer isso?

(Para aqueles que perguntariam "quão gigantesco": não sei dizer. Esta é a validação de entrada para o código da biblioteca.)

Fred Foo
fonte
validar a entrada do usuário não funciona neste cenário? Como na verificação de NaN antes da inserção
Woot4Moo
@ Woot4Moo: não, a biblioteca recebe matrizes ou scipy.sparsematrizes NumPy como entrada.
Fred Foo
2
Se você está fazendo muito isso, já ouvi coisas boas sobre Bottleneck ( pypi.python.org/pypi/Bottleneck )
matt

Respostas:

160

A solução de Ray é boa. No entanto, na minha máquina, é cerca de 2,5 vezes mais rápido de usar numpy.sumno lugar de numpy.min:

In [13]: %timeit np.isnan(np.min(x))
1000 loops, best of 3: 244 us per loop

In [14]: %timeit np.isnan(np.sum(x))
10000 loops, best of 3: 97.3 us per loop

Ao contrário min, sumnão requer ramificação, o que em hardware moderno tende a ser muito caro. Este é provavelmente o motivo pelo qual sumé mais rápido.

editar O teste acima foi executado com um único NaN bem no meio do array.

É interessante notar que miné mais lento na presença de NaNs do que em sua ausência. Ele também parece ficar mais lento conforme os NaNs se aproximam do início do array. Por outro lado, suma taxa de transferência de parece constante, independentemente de haver NaNs e de onde eles estão localizados:

In [40]: x = np.random.rand(100000)

In [41]: %timeit np.isnan(np.min(x))
10000 loops, best of 3: 153 us per loop

In [42]: %timeit np.isnan(np.sum(x))
10000 loops, best of 3: 95.9 us per loop

In [43]: x[50000] = np.nan

In [44]: %timeit np.isnan(np.min(x))
1000 loops, best of 3: 239 us per loop

In [45]: %timeit np.isnan(np.sum(x))
10000 loops, best of 3: 95.8 us per loop

In [46]: x[0] = np.nan

In [47]: %timeit np.isnan(np.min(x))
1000 loops, best of 3: 326 us per loop

In [48]: %timeit np.isnan(np.sum(x))
10000 loops, best of 3: 95.9 us per loop
NPE
fonte
1
np.miné mais rápido quando a matriz não contém NaNs, que é minha entrada esperada. Mas decidi aceitar este de qualquer maneira, porque pega infe neginftambém.
Fred Foo
2
Isso apenas captura infou -infse a entrada contiver ambos, e terá problemas se a entrada contiver valores grandes, mas finitos, que transbordam quando somados.
user2357112 suporta Monica
4
min e max não precisam se ramificar para dados de ponto flutuante em chips x86 com capacidade para sse. Portanto, a partir do numpy 1.8 min não será mais lento do que a soma, no meu fenômeno AMD é até 20% mais rápido.
jtaylor de
1
No meu Intel Core i5, com numpy 1.9.2 no OSX, np.sumainda é cerca de 30% mais rápido do que o np.min.
Matthew Brett
np.isnan(x).any(0)é um pouco mais rápido do que np.sume np.minna minha máquina, embora possa haver algum cache indesejado.
jsignell
28

Eu acho que np.isnan(np.min(X))deveria fazer o que você quiser.

Raio
fonte
Hmmm ... isso é sempre O (n) quando poderia ser O (1) (para alguns arrays).
user48956
17

Mesmo que haja uma resposta aceita, gostaria de demonstrar o seguinte (com Python 2.7.2 e Numpy 1.6.0 no Vista):

In []: x= rand(1e5)
In []: %timeit isnan(x.min())
10000 loops, best of 3: 200 us per loop
In []: %timeit isnan(x.sum())
10000 loops, best of 3: 169 us per loop
In []: %timeit isnan(dot(x, x))
10000 loops, best of 3: 134 us per loop

In []: x[5e4]= NaN
In []: %timeit isnan(x.min())
100 loops, best of 3: 4.47 ms per loop
In []: %timeit isnan(x.sum())
100 loops, best of 3: 6.44 ms per loop
In []: %timeit isnan(dot(x, x))
10000 loops, best of 3: 138 us per loop

Portanto, a maneira realmente eficiente pode depender muito do sistema operacional. De qualquer forma, dot(.)parece ser o mais estável.

comer
fonte
1
Suspeito que não dependa tanto do SO, mas da implementação do BLAS e do compilador C subjacentes. Obrigado, mas um produto escalar tem um pouco mais de probabilidade de estourar quando xcontém grandes valores, e também quero verificar se há inf.
Fred Foo
1
Bem, você sempre pode fazer o produto escalar com alguns e usar isfinite(.). Eu só queria apontar a enorme lacuna de desempenho. Obrigado
coma de
O mesmo na minha máquina.
kawing-chiu
1
Inteligente, não? Como sugere Fred Foo , quaisquer ganhos de eficiência da abordagem baseada em produto escalar são quase certamente graças a uma instalação NumPy local vinculada a uma implementação BLAS otimizada como ATLAS, MKL ou OpenBLAS. Este é o caso do Anaconda, por exemplo. Dado isso, este produto escalar será paralelizado em todos os núcleos disponíveis. O mesmo não pode ser dito para as abordagens baseadas em min- ou - sum, que funcionam confinadas a um único núcleo. Portanto, essa lacuna de desempenho.
Cecil Curry
16

Existem duas abordagens gerais aqui:

  • Verifique cada item da matriz nane pegue any.
  • Aplique alguma operação cumulativa que preserve nans (como sum) e verifique seu resultado.

Embora a primeira abordagem seja certamente a mais limpa, a otimização pesada de algumas das operações cumulativas (particularmente as que são executadas no BLAS, como dot) pode torná-las bastante rápidas. Observe que dot, como algumas outras operações BLAS, são multithread sob certas condições. Isso explica a diferença de velocidade entre diferentes máquinas.

insira a descrição da imagem aqui

import numpy
import perfplot


def min(a):
    return numpy.isnan(numpy.min(a))


def sum(a):
    return numpy.isnan(numpy.sum(a))


def dot(a):
    return numpy.isnan(numpy.dot(a, a))


def any(a):
    return numpy.any(numpy.isnan(a))


def einsum(a):
    return numpy.isnan(numpy.einsum("i->", a))


perfplot.show(
    setup=lambda n: numpy.random.rand(n),
    kernels=[min, sum, dot, any, einsum],
    n_range=[2 ** k for k in range(20)],
    logx=True,
    logy=True,
    xlabel="len(a)",
)
Nico Schlömer
fonte
4
  1. use .any ()

    if numpy.isnan(myarray).any()

  2. numpy.isfinite talvez seja melhor do que isnan para verificar

    if not np.isfinite(prop).all()

quem quer
fonte
3

Se você está confortável com permite criar uma função de curto-circuito rápido (para assim que um NaN for encontrado):

import numba as nb
import math

@nb.njit
def anynan(array):
    array = array.ravel()
    for i in range(array.size):
        if math.isnan(array[i]):
            return True
    return False

Se não houver nenhuma, NaNa função pode realmente ser mais lenta do que np.min, acho que é porque np.minusa multiprocessamento para grandes matrizes:

import numpy as np
array = np.random.random(2000000)

%timeit anynan(array)          # 100 loops, best of 3: 2.21 ms per loop
%timeit np.isnan(array.sum())  # 100 loops, best of 3: 4.45 ms per loop
%timeit np.isnan(array.min())  # 1000 loops, best of 3: 1.64 ms per loop

Mas, caso haja um NaN na matriz, especialmente se sua posição estiver em índices baixos, é muito mais rápido:

array = np.random.random(2000000)
array[100] = np.nan

%timeit anynan(array)          # 1000000 loops, best of 3: 1.93 µs per loop
%timeit np.isnan(array.sum())  # 100 loops, best of 3: 4.57 ms per loop
%timeit np.isnan(array.min())  # 1000 loops, best of 3: 1.65 ms per loop

Resultados semelhantes podem ser alcançados com Cython ou uma extensão C, eles são um pouco mais complicados (ou facilmente disponíveis como bottleneck.anynan), mas no final das contas fazem o mesmo que minha anynanfunção.

MSeifert
fonte
1

Relacionado a isso está a questão de como encontrar a primeira ocorrência de NaN. Esta é a maneira mais rápida de lidar com isso que eu conheço:

index = next((i for (i,n) in enumerate(iterable) if n!=n), None)
vitiral
fonte