Comprobación rápida de NaN en NumPy

120

Estoy buscando la forma más rápida de verificar la aparición de NaN ( np.nan) en una matriz NumPy X. np.isnan(X)está fuera de discusión, ya que crea una matriz booleana de formas X.shape, que es potencialmente gigantesca.

Lo intenté np.nan in X, pero eso parece no funcionar porque np.nan != np.nan. ¿Existe alguna forma rápida y eficiente en memoria de hacer esto?

(Para aquellos que preguntan "cuán gigantesco": no puedo decirlo. Esta es la validación de entrada para el código de la biblioteca).

Fred Foo
fuente
¿Validar la entrada del usuario no funciona en este escenario? Como en la verificación de NaN antes de la inserción
Woot4Moo
@ Woot4Moo: no, la biblioteca toma matrices o scipy.sparsematrices NumPy como entrada.
Fred Foo
2
Si estás haciendo esto mucho, he oído cosas buenas acerca de Cuello de botella ( pypi.python.org/pypi/Bottleneck )
Matt

Respuestas:

160

La solución de Ray es buena. Sin embargo, en mi máquina es aproximadamente 2.5 veces más rápido de usar numpy.sumen 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

A diferencia min, sumno requiere ramificación, que en el hardware moderno tiende a ser bastante caro. Esta es probablemente la razón por la que sumes más rápido.

editar La prueba anterior se realizó con un solo NaN justo en el medio de la matriz.

Es interesante notar que mines más lento en presencia de NaN que en su ausencia. También parece volverse más lento a medida que los NaN se acercan al inicio de la matriz. Por otro lado, sumel rendimiento parece constante independientemente de si hay NaN y dónde se encuentran:

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
fuente
1
np.mines más rápido cuando la matriz no contiene NaN, que es mi entrada esperada. Pero he decidido aceptar este de todos modos, porque atrapa infy neginftambién.
Fred Foo
2
Esto solo detecta info -infsi la entrada contiene ambos, y tiene problemas si la entrada contiene valores grandes pero finitos que se desbordan cuando se suman.
user2357112 apoya a Monica el
4
min y max no necesitan ramificarse para datos de punto flotante en chips x86 con capacidad sse. Entonces, a partir de 1.8 min numpy no será más lento que sum, en mi fenómeno amd es incluso un 20% más rápido.
jtaylor
1
En mi Intel Core i5, con numpy 1.9.2 en OSX, np.sumsigue siendo un 30% más rápido que np.min.
Matthew Brett
np.isnan(x).any(0)es un poco más rápido que np.sumy np.minen mi máquina, aunque puede haber algún almacenamiento en caché no deseado.
jsignell
28

Creo que np.isnan(np.min(X))debería hacer lo que quiera.

Rayo
fuente
Hmmm ... esto es siempre O (n) cuando podría ser O (1) (para algunas matrices).
user48956
17

Incluso si existe una respuesta aceptada, me gustaría demostrar lo siguiente (con Python 2.7.2 y Numpy 1.6.0 en 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

Por lo tanto, la forma realmente eficiente podría depender en gran medida del sistema operativo. De todos modos el dot(.)basado parece ser el más estable.

comer
fuente
1
Sospecho que depende no tanto del sistema operativo, como de la implementación BLAS subyacente y el compilador C. Gracias, pero un producto escalar es un poco más probable que se desborde cuando xcontiene valores grandes, y también quiero verificar inf.
Fred Foo
1
Bueno, siempre puedes hacer el producto escalar con unos y usar isfinite(.). Solo quería señalar la enorme brecha de rendimiento. Gracias
comer el
Lo mismo en mi máquina.
kawing-chiu
1
Inteligente, ¿no? Como sugiere Fred Foo , cualquier ganancia de eficiencia del enfoque basado en productos punto se debe casi con certeza a una instalación local de NumPy vinculada a una implementación BLAS optimizada como ATLAS, MKL u OpenBLAS. Este es el caso de Anaconda, por ejemplo. Dado eso, este producto escalar se paralelizará en todos los núcleos disponibles. El mismo no puede decirse de los min- o sumenfoques basados, que se ejecutan confinadas a un solo núcleo. Ergo, esa brecha de desempeño.
Cecil Curry
16

Aquí hay dos enfoques generales:

  • Compruebe cada elemento de la matriz nany tómelo any.
  • Aplique alguna operación acumulativa que conserve nans (like sum) y verifique su resultado.

Si bien el primer enfoque es ciertamente el más limpio, la gran optimización de algunas de las operaciones acumulativas (particularmente las que se ejecutan en BLAS, como dot) puede hacerlas bastante rápidas. Tenga en cuenta que dot, al igual que algunas otras operaciones BLAS, son multiproceso en determinadas condiciones. Esto explica la diferencia de velocidad entre diferentes máquinas.

ingrese la descripción de la imagen aquí

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
fuente
4
  1. usar cualquier()

    if numpy.isnan(myarray).any()

  2. numpy.isfinite quizás mejor que isnan para comprobar

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

woso
fuente
3

Si te sientes cómodo con permite crear un cortocircuito rápido (se detiene tan pronto como se encuentra un NaN) función:

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

Si no hay, NaNla función podría ser más lenta que np.min, creo que se debe a que np.minusa multiprocesamiento para matrices grandes:

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

Pero en caso de que haya un NaN en la matriz, especialmente si su posición está en índices bajos, entonces es mucho más 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

Se pueden lograr resultados similares con Cython o una extensión de C, estos son un poco más complicados (o fácilmente disponibles como bottleneck.anynan) pero en última instancia hacen lo mismo que mi anynanfunción.

MSeifert
fuente
1

Relacionado con esto está la cuestión de cómo encontrar la primera aparición de NaN. Esta es la forma más rápida de manejar eso que conozco:

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