Controllo rapido per NaN in NumPy


120

Sto cercando il modo più veloce per verificare la presenza di NaN ( np.nan) in un array NumPy X. np.isnan(X)è fuori questione, poiché crea un array booleano di forma X.shape, che è potenzialmente gigantesco.

Ho provato np.nan in X, ma sembra non funzionare perché np.nan != np.nan. Esiste un modo veloce ed efficiente per la memoria per farlo?

(A coloro che chiedono "quanto gigantesco": non posso dirlo. Questa è la convalida dell'input per il codice della libreria.)


la convalida dell'input dell'utente non funziona in questo scenario? Come controllare NaN prima dell'inserto
Woot4Moo

@ Woot4Moo: no, la libreria accetta array o scipy.sparsematrici NumPy come input.
Fred Foo

2
Se lo fai spesso , ho sentito parlare bene di Bottleneck ( pypi.python.org/pypi/Bottleneck )
matt

Risposte:


160

La soluzione di Ray è buona. Tuttavia, sulla mia macchina è circa 2,5 volte più veloce da usare numpy.sumal posto di 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 differenza min, sumnon richiede ramificazioni, che sull'hardware moderno tende ad essere piuttosto costoso. Questo è probabilmente il motivo per cui sumè più veloce.

modifica Il test sopra è stato eseguito con un singolo NaN al centro dell'array.

È interessante notare che minè più lento in presenza di NaN che in loro assenza. Sembra anche rallentare man mano che i NaN si avvicinano all'inizio dell'array. D'altra parte, sumil throughput di s sembra costante indipendentemente dal fatto che ci siano NaN e dove si trovano:

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

1
np.minè più veloce quando l'array non contiene NaN, che è il mio input previsto. Ma ho deciso di accettarlo comunque, perché catturainf e neginfpure.
Fred Foo

2
Questo cattura solo info-inf se l'input contiene entrambi e ha problemi se l'input contiene valori grandi ma finiti che traboccano quando vengono sommati.
user2357112 supporta Monica

4
min e max non devono essere ramificati per i dati in virgola mobile su chip x86 compatibili con sse. Quindi a partire da numpy 1.8 min non sarà più lento della somma, sul mio fenomeno amd è anche il 20% più veloce.
jtaylor

1
Sul mio Intel Core i5, con numpy 1.9.2 su OSX, np.sumè ancora circa il 30% più veloce di np.min.
Matthew Brett

np.isnan(x).any(0)è leggermente più veloce di np.sume np.minsulla mia macchina, anche se potrebbe esserci una cache indesiderata.
jsignell

28

Penso che np.isnan(np.min(X))dovresti fare quello che vuoi.


Hmmm ... questo è sempre O (n) quando è potrebbe essere O (1) (per alcuni array).
user48956

17

Anche se esiste una risposta accettata, mi piacerebbe dimostrare quanto segue (con Python 2.7.2 e Numpy 1.6.0 su 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

Pertanto, il modo davvero efficiente potrebbe dipendere in larga misura dal sistema operativo. Comunque dot(.)based sembra essere il più stabile.


1
Sospetto che dipenda non tanto dal sistema operativo, quanto dall'implementazione BLAS sottostante e dal compilatore C. Grazie, ma un prodotto punto è solo un po 'più probabile che trabocchi quando xcontiene valori grandi, e voglio anche controllare per inf.
Fred Foo

1
Bene, puoi sempre fare il punto prodotto con quelli e usarli isfinite(.). Volevo solo sottolineare l'enorme divario di prestazioni. Grazie
mangia il

Lo stesso sulla mia macchina.
kawing-chiu

1
Intelligente, no? Come suggerisce Fred Foo , qualsiasi aumento di efficienza dell'approccio basato sul prodotto dot è quasi certamente dovuto a un'installazione NumPy locale collegata a un'implementazione BLAS ottimizzata come ATLAS, MKL o OpenBLAS. Questo è il caso di Anaconda, ad esempio. Detto questo, questo prodotto dot sarà parallelizzato su tutti i core disponibili. Lo stesso non si può dire per gli approcci basati su min- o - sum, che sono limitati a un singolo core. Ergo, quel divario di prestazioni.
Cecil Curry

16

Ci sono due approcci generali qui:

  • Controlla ogni elemento dell'array nane prendilo any.
  • Applicare un'operazione cumulativa che preserva nan s (like sum) e controlla il suo risultato.

Sebbene il primo approccio sia sicuramente il più pulito, la pesante ottimizzazione di alcune delle operazioni cumulative (in particolare quelle eseguite in BLAS, come dot) può renderle abbastanza veloci. Si noti che dot, come alcune altre operazioni BLAS, sono multithread in determinate condizioni. Questo spiega la differenza di velocità tra macchine diverse.

inserisci qui la descrizione dell'immagine

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)",
)

4
  1. usa .any ()

    if numpy.isnan(myarray).any()

  2. numpy.isfinite forse meglio di isnan per il controllo

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


3

Se stai bene con permette di creare una funzione di cortocircuito veloce (si ferma non appena viene trovato un NaN):

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 non c'è, NaNla funzione potrebbe effettivamente essere più lenta di np.min, penso che sia perché np.minutilizza il multiprocessing per array di grandi dimensioni:

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

Ma nel caso in cui ci sia un NaN nell'array, specialmente se la sua posizione è su indici bassi, allora è molto più veloce:

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

Risultati simili possono essere ottenuti con Cython o un'estensione C, questi sono un po 'più complicati (o facilmente disponibili come bottleneck.anynan) ma alla fine fanno lo stesso della mia anynanfunzione.


1

Collegato a questo è la domanda su come trovare la prima occorrenza di NaN. Questo è il modo più veloce per gestire ciò di cui sono a conoscenza:

index = next((i for (i,n) in enumerate(iterable) if n!=n), None)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.