Control rápido para NaN en NumPy


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

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

(A aquellos que preguntarían "cuán gigantesco": No puedo decirlo. Esta es la validación de entrada para el código de la biblioteca.)

Author: Fred Foo, 2011-07-18

7 answers

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.sum en 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, sum no requiere ramificación, que en el hardware moderno tiende a ser bastante caro. Esta es probablemente la razón por la que sum es más rápido.

Edit La prueba anterior se realizó con una sola NaN justo en el medio de la matriz.

Es interesante notar que min es más lento en presencia de NaNs que en su ausencia. También parece ser más lento a medida que los NAN se acercan al inicio de la matriz. Por otro lado, el rendimiento de sum 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
 126
Author: NPE,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-01-17 18:34:41

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

 22
Author: Ray,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-07-18 17:17:18

Incluso 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 dot(.) basado parece ser el más estable.

 17
Author: eat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-07-18 21:19:21

Hay dos enfoques generales aquí:

  • Compruebe cada elemento de matriz para nan y tome any.
  • Aplique alguna operación acumulativa que preserve nan s (como sum) y verifique su resultado.

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

introduzca 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)'
    )
 5
Author: Nico Schlömer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-10 12:04:13

Si te sientes cómodo con numba permite crear un cortocircuito rápido (se detiene tan pronto como se encuentra una 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 NaN la función podría ser en realidad más lenta que np.min, creo que eso es porque np.min usa multiprocesamiento para arreglos 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 una 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 a C extensión, estos son un poco más complicado (o fácilmente disponible como bottleneck.anynan) pero en última instancia hacer lo mismo que mi función anynan.

 2
Author: MSeifert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-02 11:23:32

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

index = next((i for (i,n) in enumerate(iterable) if n!=n), None)
 1
Author: vitiral,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-01 15:59:23
enter code here
  1. uso .any()

if numpy.isnan(myarray).any()

  1. numpy.isfinite tal vez mejor que isnan para comprobar

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

 1
Author: woso,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-09-06 05:39:30