Numpy: entrées uniques et NaN (Trac # 1514)

Créé le 19 oct. 2012  ·  14Commentaires  ·  Source: numpy/numpy

_Billet original http://projects.scipy.org/numpy/ticket/1514 le 18/06/2010 par l'utilisateur trac rspringuel, attribué à unknown._

Lorsque unique opère sur un tableau avec plusieurs entrées NaN, son retour inclut un NaN pour chaque entrée qui était NaN dans le tableau d'origine.

Exemples:
a = random.randint (5, taille = 100) .astype (float)

a [12] = nan #add une seule entrée nan
unique (a)
tableau ([0., 1., 2., 3., 4., NaN])
a [20] = nan # ajouter une seconde
unique (a)
tableau ([0., 1., 2., 3., 4., NaN, NaN])
a [13] = nan
unique (a) # et un troisième
tableau ([0., 1., 2., 3., 4., NaN, NaN, NaN])

Cela est probablement dû au fait que x == y est évalué à False si x et y sont tous deux NaN. Unique doit avoir "or (isnan (x) et isnan (y))" ajouté au conditionnel qui vérifie la présence d'une valeur dans les valeurs déjà identifiées. Je ne sais pas s'il s'agissait de vies uniques dans numpy et je n'ai pas pu la trouver quand je suis allée chercher, donc je ne peux pas faire le changement moi-même (ou même être sûr de la syntaxe exacte du conditionnel).

En outre, la fonction suivante peut être utilisée pour corriger le comportement.

def nanunique (x):
a = numpy.unique (x)
r = []
pour i dans un:
si je dans r ou (numpy.isnan (i) et numpy.any (numpy.isnan (r))):
continuer
autre:
r.append (i)
retourne numpy.array (r)

00 - Bug Other

Commentaire le plus utile

J'ai rencontré le même problème aujourd'hui. Le cœur de la routine np.unique est de calculer un masque sur un tableau trié démêlé dans numpy / lib / arraysetops.py pour trouver quand les valeurs changent dans ce tableau trié:

mask = np.empty(aux.shape, dtype=np.bool_)
mask[:1] = True
mask[1:] = aux[1:] != aux[:-1]

Cela pourrait être remplacé par quelque chose comme ce qui suit, qui est assez proche du commentaire de jaimefrio d'il y a environ 5 ans, mais évite l'appel argmin:

mask = np.empty(aux.shape, dtype=np.bool_)
mask[:1] = True
if (aux.shape[0] > 0 and isinstance(aux[-1], (float, np.float16,
                                              np.float32, np.float64))
    and np.isnan(aux[-1])):
    aux_firstnan = np.searchsorted(aux, np.nan, side='left')
    mask[1:aux_firstnan] = (aux[1:aux_firstnan] != aux[:aux_firstnan-1])
    mask[aux_firstnan] = True
    mask[aux_firstnan+1:] = False
else:
    mask[1:] = aux[1:] != aux[:-1]

En exécutant quelques expériences% timeit, j'ai observé une pénalité d'exécution d'au plus <10% si le tableau est grand et qu'il y a très peu de NaN (disons 10 NaN sur 1 million), et pour des tableaux aussi grands, il fonctionne en fait plus rapidement s'il y en a beaucoup de NaN.

D'un autre côté, si les tableaux sont petits (par exemple, 10 entrées), les performances affectent considérablement car la vérification du float et du NaN est relativement coûteuse et le temps d'exécution peut aller jusqu'à un multiple. Cela s'applique même s'il n'y a pas de NaN car c'est la vérification qui est lente.

Si le tableau a des NaN, il produit un résultat différent, combinant les NaN, ce qui est le point de tout. Donc, dans ce cas, il s'agit vraiment d'obtenir un résultat souhaité (tous les NaN combinés dans un seul groupe de valeurs) légèrement plus lentement que d'obtenir un résultat indésirable (chaque NaN dans son propre groupe de valeurs) légèrement plus rapidement.

Enfin, notez que ce correctif ne résoudrait pas la recherche de valeurs uniques impliquant des objets composés contenant des NaN, comme dans cet exemple:

a = np.array([[0,1],[np.nan, 1], [np.nan, 1]])
np.unique(a, axis=0)

qui reviendrait encore

array([[ 0.,  1.],
       [nan,  1.],
       [nan,  1.]])

Tous les 14 commentaires

_trac user rspringuel écrit le 18/06/2010_

Shoot, pour utiliser les blocs de code ci-dessus. Cela n'affecte vraiment que le code du correctif, je vais donc simplement le republier:

def nanunique(x):
    a = numpy.unique(x)
    r = []
    for i in a:
        if i in r or (numpy.isnan(i) and numpy.any(numpy.isnan(r))):
            continue
        else:
            r.append(i)
    return numpy.array(r)

Fixé.

Je vois toujours ce problème avec le dernier maître. Quel commit aurait dû le corriger? À moins que je ne manque quelque chose, je suggère de rouvrir ce numéro.

C'est facile à corriger pour les flottants, mais je ne vois pas de solution facile pour les dtypes complexes ou structurés. Mettra un PR rapide ensemble et nous pourrons discuter des options là-bas.

@jaimefrio je l'ai corrigé pour une utilisation unique

    if issubclass(aux.dtype.type, np.inexact):
        # nans always compare unequal, so encode as integers
        tmp = aux.searchsorted(aux)
    else:
        tmp = aux
    flag = np.concatenate(([True], tmp[1:] != tmp[:-1]))

mais il semble que toutes les autres opérations ont également des problèmes. Peut-être avons-nous besoin de nan_equal, nan_not_equal ufuncs, ou peut-être de quelque chose dans nanfuntions.

La recherche triée de aux pour elle-même est une astuce intelligente! Bien que la recherche de tri dans tout cela soit un peu inutile, idéalement, nous voudrions repérer la première entrée avec un nan, peut-être quelque chose du genre, après avoir créé aux et flag comme maintenant, en faisant :

if not aux[-1] == aux[-1]:
    nanidx = np.argmin(aux == aux)
    nanaux = aux[nanidx:].searchsorted(aux[nanidx:])
    flag[nanidx+1:] = nanaux[1:] != nanaux[:-1]

ou quelque chose de similaire après avoir corrigé toutes les erreurs par une que j'ai probablement introduites là-bas.

Cette dernière approche fonctionnerait pour les types flottants et complexes, mais échouerait pour les dtypes structurés avec des champs à virgule flottante. Mais je pense toujours que l'astuce de tri par recherche, même si elle fonctionnerait pour tous les types, est trop inutile. Quelques horaires:

In [10]: a = np.random.randn(1000)

In [11]: %timeit np.unique(a)
10000 loops, best of 3: 69.5 us per loop

In [12]: b = np.sort(a)

In [13]: %timeit b.searchsorted(b)
10000 loops, best of 3: 28.1 us per loop

Cela va être un coup de performance de 40%, ce qui peut être OK pour une fonction nanunique , mais probablement pas pour le cas général.

2019 appelé, le problème OP est toujours valide et le code est reproductible.

@jaimefrio pourquoi ne pouvons-nous pas avoir une option fausse par défaut?

Je veux dire, ce comportement est au mieux déroutant, et la performance n'est pas une excuse.

@ Demetrio92 alors que j'apprécie votre tentative de faire bouger ce problème, l'ironie / le sarcasme sur Internet peut être interprété différemment par différentes personnes, veuillez rester gentil. Pour certains d'entre nous, les performances sont très importantes et nous n'ajoutons pas de code qui ralentit les choses.

Le PR # 5487 peut être un meilleur endroit pour commenter ou faire des suggestions pour aller de l'avant.

Edit: fixer le numéro PR

Ce problème semble être ouvert depuis 8 ans, mais je veux juste ajouter un +1 pour que le comportement par défaut de numpy.unique soit correct plutôt que rapide. Cela a cassé mon code et je suis sûr que d'autres en souffriront. Nous pouvons avoir un comportement optionnel "fast = False" et document nan pour fast et nans. Je serais surpris si np.unique est très souvent le goulot d'étranglement des performances dans les applications à temps critique.

J'ai rencontré le même problème aujourd'hui. Le cœur de la routine np.unique est de calculer un masque sur un tableau trié démêlé dans numpy / lib / arraysetops.py pour trouver quand les valeurs changent dans ce tableau trié:

mask = np.empty(aux.shape, dtype=np.bool_)
mask[:1] = True
mask[1:] = aux[1:] != aux[:-1]

Cela pourrait être remplacé par quelque chose comme ce qui suit, qui est assez proche du commentaire de jaimefrio d'il y a environ 5 ans, mais évite l'appel argmin:

mask = np.empty(aux.shape, dtype=np.bool_)
mask[:1] = True
if (aux.shape[0] > 0 and isinstance(aux[-1], (float, np.float16,
                                              np.float32, np.float64))
    and np.isnan(aux[-1])):
    aux_firstnan = np.searchsorted(aux, np.nan, side='left')
    mask[1:aux_firstnan] = (aux[1:aux_firstnan] != aux[:aux_firstnan-1])
    mask[aux_firstnan] = True
    mask[aux_firstnan+1:] = False
else:
    mask[1:] = aux[1:] != aux[:-1]

En exécutant quelques expériences% timeit, j'ai observé une pénalité d'exécution d'au plus <10% si le tableau est grand et qu'il y a très peu de NaN (disons 10 NaN sur 1 million), et pour des tableaux aussi grands, il fonctionne en fait plus rapidement s'il y en a beaucoup de NaN.

D'un autre côté, si les tableaux sont petits (par exemple, 10 entrées), les performances affectent considérablement car la vérification du float et du NaN est relativement coûteuse et le temps d'exécution peut aller jusqu'à un multiple. Cela s'applique même s'il n'y a pas de NaN car c'est la vérification qui est lente.

Si le tableau a des NaN, il produit un résultat différent, combinant les NaN, ce qui est le point de tout. Donc, dans ce cas, il s'agit vraiment d'obtenir un résultat souhaité (tous les NaN combinés dans un seul groupe de valeurs) légèrement plus lentement que d'obtenir un résultat indésirable (chaque NaN dans son propre groupe de valeurs) légèrement plus rapidement.

Enfin, notez que ce correctif ne résoudrait pas la recherche de valeurs uniques impliquant des objets composés contenant des NaN, comme dans cet exemple:

a = np.array([[0,1],[np.nan, 1], [np.nan, 1]])
np.unique(a, axis=0)

qui reviendrait encore

array([[ 0.,  1.],
       [nan,  1.],
       [nan,  1.]])

"Si le tableau a des NaN, alors il produit un résultat différent, combinant les NaN, ce qui est le point de tout."

+1

Une fonction qui renvoie une liste contenant des éléments répétés, _ par exemple une liste avec plus de 1 NaN, ne doit pas être appelée "unique". Si des éléments répétés dans le cas de NaN sont souhaités, il ne doit s'agir que d'un cas spécial désactivé par défaut, par exemple numpy.unique(..., keep_NaN=False) .

@ufmayer soumet un PR!

+1
Je soutiendrais également le retour de NaN une seule fois

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

manuels picture manuels  ·  3Commentaires

dcsaba89 picture dcsaba89  ·  3Commentaires

dmvianna picture dmvianna  ·  4Commentaires

navytux picture navytux  ·  4Commentaires

marcocaccin picture marcocaccin  ·  4Commentaires