139 votes

Vérification rapide des NaN dans NumPy

Je cherche le moyen le plus rapide de vérifier l'occurrence de NaN ( np.nan ) dans un tableau NumPy X . np.isnan(X) est hors de question, puisqu'il construit un tableau booléen de forme X.shape ce qui est potentiellement gigantesque.

J'ai essayé np.nan in X mais cela ne semble pas fonctionner car np.nan != np.nan . Existe-t-il un moyen rapide et peu gourmand en mémoire d'effectuer cette opération ?

(Pour ceux qui demanderaient "à quel point c'est gigantesque" : Je ne peux pas le dire. Il s'agit d'une validation d'entrée pour le code de la bibliothèque).

0 votes

La validation de l'entrée de l'utilisateur ne fonctionne-t-elle pas dans ce scénario ? Comme dans le cas d'une vérification de NaN avant l'insertion.

0 votes

@Woot4Moo : non, la bibliothèque prend les tableaux NumPy ou scipy.sparse en tant que données d'entrée.

2 votes

Si vous faites cela souvent, j'ai entendu de bonnes choses à propos de Bottleneck ( pypi.python.org/pypi/Bottleneck )

176voto

NPE Points 169956

La solution de Ray est bonne. Cependant, sur ma machine, il est environ 2,5 fois plus rapide d'utiliser numpy.sum à la place 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

Contrairement à min , sum ne nécessite pas de branchement, ce qui, sur le matériel moderne, tend à être assez coûteux. C'est probablement la raison pour laquelle sum est plus rapide.

éditer Le test ci-dessus a été effectué avec un seul NaN au milieu du tableau.

Il est intéressant de noter que min est plus lente en présence de NaNs qu'en leur absence. Elle semble également plus lente lorsque les NaN se rapprochent du début du tableau. D'autre part, sum semble constant, qu'il y ait ou non des NaN et quel que soit l'endroit où ils se trouvent :

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 votes

np.min est plus rapide lorsque le tableau ne contient pas de NaN, ce qui est l'entrée attendue. Mais j'ai décidé d'accepter celle-ci quand même, parce qu'elle attrape inf y neginf également.

2 votes

Cela ne permet d'attraper que les inf o -inf si l'entrée contient les deux, et il a des problèmes si l'entrée contient des valeurs grandes mais finies qui débordent lorsqu'elles sont additionnées.

4 votes

Min et max n'ont pas besoin d'être branchés pour les données en virgule flottante sur les puces x86 compatibles avec sse. Donc à partir de numpy 1.8 min ne sera pas plus lent que sum, sur mon amd phenom c'est même 20% plus rapide.

32voto

Ray Points 2125

Je pense que np.isnan(np.min(X)) doit faire ce que vous voulez.

0 votes

Hmmm... c'est toujours O(n) alors que cela pourrait être O(1) (pour certains tableaux).

26voto

Nico Points 893

Il existe deux approches générales :

  • Vérifier chaque élément du tableau pour nan et prendre any .
  • Appliquer une opération cumulative qui préserve nan (comme les sum ) et vérifier son résultat.

Bien que la première approche soit certainement la plus propre, l'optimisation lourde de certaines des opérations cumulatives (en particulier celles qui sont exécutées en BLAS, comme le dot ) peut les fabriquer assez rapidement. Notez que les dot comme d'autres opérations BLAS, sont multithreadées sous certaines conditions. Cela explique la différence de vitesse entre différentes machines.

enter image description here

import numpy as np
import perfplot

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

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

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

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

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

b = perfplot.bench(
    setup=np.random.rand,
    kernels=[min, sum, dot, any, einsum],
    n_range=[2 ** k for k in range(25)],
    xlabel="len(a)",
)
b.save("out.png")
b.show()

17voto

eat Points 4573

Même s'il existe une réponse acceptée, j'aimerais démontrer ce qui suit (avec Python 2.7.2 et Numpy 1.6.0 sur 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

Ainsi, la méthode la plus efficace peut dépendre fortement du système d'exploitation. Quoi qu'il en soit dot(.) semble être la plus stable.

1 votes

Je pense que cela ne dépend pas tant du système d'exploitation que de l'implémentation BLAS et du compilateur C sous-jacents. Merci, mais un produit de points est juste un peu plus susceptible de déborder lorsque x contient de grandes valeurs, et je veux aussi vérifier la présence d'inf.

1 votes

Vous pouvez toujours faire le produit point avec les uns et utiliser isfinite(.) . Je voulais simplement souligner l'énorme écart de performance. Je vous remercie.

0 votes

Même chose sur ma machine.

7voto

MSeifert Points 6307

Si vous êtes à l'aise avec numba elle permet de créer une fonction de court-circuit rapide (qui s'arrête dès qu'un NaN est trouvé) :

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

S'il n'y a pas de NaN la fonction pourrait en fait être plus lente que np.min Je pense que c'est parce que np.min utilise le multiprocessing pour les grands tableaux :

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

Mais dans le cas où il y a un NaN dans le tableau, surtout si sa position est à des indices faibles, c'est beaucoup plus rapide :

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

Des résultats similaires peuvent être obtenus avec Cython ou une extension C, qui sont un peu plus compliqués (ou facilement disponibles en tant que bottleneck.anynan ) mais en fin de compte faire la même chose que mon anynan fonction.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X