Détecter si un tableau NumPy contient au moins une valeur non numérique?

103

J'ai besoin d'écrire une fonction qui détectera si l'entrée contient au moins une valeur non numérique. Si une valeur non numérique est trouvée, je soulèverai une erreur (car le calcul ne doit renvoyer qu'une valeur numérique). Le nombre de dimensions du tableau d'entrée n'est pas connu à l'avance - la fonction doit donner la valeur correcte indépendamment de ndim. Comme complication supplémentaire, l'entrée peut être un flottant unique numpy.float64ou même quelque chose de bizarre comme un tableau à zéro dimension.

La manière évidente de résoudre ce problème est d'écrire une fonction récursive qui itère sur chaque objet itérable dans le tableau jusqu'à ce qu'elle trouve un non-itérabe. Il appliquera la numpy.isnan()fonction sur chaque objet non itérable. Si au moins une valeur non numérique est trouvée, la fonction retournera immédiatement False. Sinon, si toutes les valeurs de l'itérable sont numériques, il renverra finalement True.

Cela fonctionne très bien, mais c'est assez lent et je pense que NumPy a une bien meilleure façon de le faire. Quelle est une alternative plus rapide et plus insensée?

Voici ma maquette:

def contains_nan( myarray ):
    """
    @param myarray : An n-dimensional array or a single float
    @type myarray : numpy.ndarray, numpy.array, float
    @returns: bool
    Returns true if myarray is numeric or only contains numeric values.
    Returns false if at least one non-numeric value exists
    Not-A-Number is given by the numpy.isnan() function.
    """
    return True
Salim Fadhley
la source
3
Votre description contains_nansemble suspecte: "Renvoie false s'il existe au moins une valeur non numérique". Je me serais attendu contains_nanà revenir Truesi le tableau contient NaN.
Samuel Tardieu
Qu'en est-il des entrées telles que array(['None', 'None'], dtype=object)? Une telle entrée devrait-elle simplement soulever une exception?
Finn Årup Nielsen
NE PAS utiliser float('nan') in x. Ça ne marche pas.
Charlie Parker

Réponses:

183

Cela devrait être plus rapide que l'itération et fonctionnera quelle que soit la forme.

numpy.isnan(myarray).any()

Edit: 30x plus rapide:

import timeit
s = 'import numpy;a = numpy.arange(10000.).reshape((100,100));a[10,10]=numpy.nan'
ms = [
    'numpy.isnan(a).any()',
    'any(numpy.isnan(x) for x in a.flatten())']
for m in ms:
    print "  %.2f s" % timeit.Timer(m, s).timeit(1000), m

Résultats:

  0.11 s numpy.isnan(a).any()
  3.75 s any(numpy.isnan(x) for x in a.flatten())

Bonus: cela fonctionne bien pour les types NumPy non-array:

>>> a = numpy.float64(42.)
>>> numpy.isnan(a).any()
False
>>> a = numpy.float64(numpy.nan)
>>> numpy.isnan(a).any()
True
Paul
la source
1
avec numpy 1.7, la version flatten () n'est que deux fois plus rapide que la première
Christian Geier
Pourquoi quelque chose comme ça float('nan') in xne marche pas? Je l'ai essayé et python revient Falsex = [1,2,3,float('nan')].
Charlie Parker
1
@CharlieParker la même raison pour laquelle float ('nan') == float ('nan') retournera False. NaN n'est pas égal à NaN. Ici plus d'infos: stackoverflow.com/questions/10034149/…
Muppet
1
@mab: C'est parce que l'appel numpy.anyà une genexp renvoie simplement genexp; vous ne faites pas réellement le calcul que vous pensez être. Ne numpy.anyfaites jamais appel à un genexp.
user2357112 prend en charge Monica
Dans un scénario de débogage réel, je recommanderais également de regarder au np.isfinitelieu de np.isnandétecter les débordements numériques, l'instabilité, etc.
Ben Usman
18

Si l'infini est une valeur possible, j'utiliserais numpy.isfinite

numpy.isfinite(myarray).all()

Si la valeur ci-dessus est évaluée à True, alors myarrayne contient aucune valeur numpy.nan, numpy.infou -numpy.inf.

numpy.nansera OK avec les numpy.infvaleurs, par exemple:

In [11]: import numpy as np

In [12]: b = np.array([[4, np.inf],[np.nan, -np.inf]])

In [13]: np.isnan(b)
Out[13]: 
array([[False, False],
       [ True, False]], dtype=bool)

In [14]: np.isfinite(b)
Out[14]: 
array([[ True, False],
       [False, False]], dtype=bool)
Akavall
la source
Pourquoi quelque chose comme ça float('nan') in xne marche pas? Je l'ai essayé et python revient Falsex = [1,2,3,float('nan')].
Charlie Parker
1
@CharlieParker car deux nans ne sont pas considérés comme égaux l'un à l'autre. Essayez float('nan') == float('nan').
Akavall
intéressant. Pourquoi ne sont-ils pas considérés comme égaux?
Charlie Parker
1
@CharlieParker, je ne pense pas que je pourrais donner une très bonne réponse ici. C'est peut-être ce que vous cherchez: stackoverflow.com/questions/1565164/…
Akavall
4

Pfft! Microsecondes! Ne résolvez jamais un problème en microsecondes qui peut être résolu en nanosecondes.

Notez que la réponse acceptée:

  • itère sur l'ensemble des données, que l'on trouve ou non un nan
  • crée un tableau temporaire de taille N, qui est redondant.

Une meilleure solution consiste à renvoyer True immédiatement lorsque NAN est trouvé:

import numba
import numpy as np

NAN = float("nan")

@numba.njit(nogil=True)
def _any_nans(a):
    for x in a:
        if np.isnan(x): return True
    return False

@numba.jit
def any_nans(a):
    if not a.dtype.kind=='f': return False
    return _any_nans(a.flat)

array1M = np.random.rand(1000000)
assert any_nans(array1M)==False
%timeit any_nans(array1M)  # 573us

array1M[0] = NAN
assert any_nans(array1M)==True
%timeit any_nans(array1M)  # 774ns  (!nanoseconds)

et fonctionne pour n-dimensions:

array1M_nd = array1M.reshape((len(array1M)/2, 2))
assert any_nans(array1M_nd)==True
%timeit any_nans(array1M_nd)  # 774ns

Comparez cela à la solution native numpy:

def any_nans(a):
    if not a.dtype.kind=='f': return False
    return np.isnan(a).any()

array1M = np.random.rand(1000000)
assert any_nans(array1M)==False
%timeit any_nans(array1M)  # 456us

array1M[0] = NAN
assert any_nans(array1M)==True
%timeit any_nans(array1M)  # 470us

%timeit np.isnan(array1M).any()  # 532us

La méthode de sortie anticipée est une accélération de 3 ordres ou de magnitude (dans certains cas). Pas trop minable pour une simple annotation.

user48956
la source
3

Avec numpy 1.3 ou svn, vous pouvez le faire

In [1]: a = arange(10000.).reshape(100,100)

In [3]: isnan(a.max())
Out[3]: False

In [4]: a[50,50] = nan

In [5]: isnan(a.max())
Out[5]: True

In [6]: timeit isnan(a.max())
10000 loops, best of 3: 66.3 µs per loop

Le traitement des nans dans les comparaisons n'était pas cohérent dans les versions antérieures.


la source
Pourquoi quelque chose comme ça float('nan') in xne marche pas? Je l'ai essayé et python revient Falsex = [1,2,3,float('nan')].
Charlie Parker
@CharlieParker ... parce que la comparaison avec NAN ne fait pas ce que vous attendez. NAN est traité comme un NULL logique (= ne sait pas). float("nan")==float("nan")donner False(bien que cela soit possible, il devrait probablement retourner NAN ou None). De même, la bizarrerie avec NAN et boolen NULL est vraie dans de nombreux langages, y compris SQL (où NULL = NULL n'est jamais vrai).
user48956
2

(np.where(np.isnan(A)))[0].shape[0]sera plus grand que 0si Acontient au moins un élément de nan, Apourrait être une n x mmatrice.

Exemple:

import numpy as np

A = np.array([1,2,4,np.nan])

if (np.where(np.isnan(A)))[0].shape[0]: 
    print "A contains nan"
else:
    print "A does not contain nan"
Ting On Chan
la source