Numpy: eindeutige und NaN-Einträge (Trac # 1514)

Erstellt am 19. Okt. 2012  ·  14Kommentare  ·  Quelle: numpy/numpy

_Ursprüngliches Ticket http://projects.scipy.org/numpy/ticket/1514 am 18.06.2010 vom Trac-Benutzer rspringuel, zugewiesen an unknown._

Wenn unique ein Array mit mehreren NaN-Einträgen bearbeitet, enthält seine Rückgabe eine NaN für jeden Eintrag, der NaN im ursprünglichen Array war.

Beispiele:
a = random.randint (5, size = 100) .astype (float)

a [12] = nan #add einen einzelnen nan Eintrag
einzigartig (a)
Array ([0., 1., 2., 3., 4., NaN])
a [20] = nan #add eine Sekunde
einzigartig (a)
Array ([0., 1., 2., 3., 4., NaN, NaN])
a [13] = nan
einzigartig (a) #und ein Drittel
Array ([0., 1., 2., 3., 4., NaN, NaN, NaN])

Dies ist wahrscheinlich auf die Tatsache zurückzuführen, dass x == y als False ausgewertet wird, wenn sowohl x als auch y NaN sind. Für Unique muss "oder (isnan (x) und isnan (y))" zu der Bedingung hinzugefügt werden, die prüft, ob in den bereits identifizierten Werten ein Wert vorhanden ist. Ich weiß nicht, ob es sich um ein einzigartiges Leben in Numpy handelt, und ich konnte es nicht finden, als ich auf die Suche ging, daher kann ich die Änderung nicht selbst vornehmen (oder sogar sicher sein, wie die genaue Syntax der Bedingung lauten sollte).

Die folgende Funktion kann auch verwendet werden, um das Verhalten zu patchen.

def nanunique (x):
a = numpy.unique (x)
r = []
für ich in einem:
wenn ich in r oder (numpy.isnan (i) und numpy.any (numpy.isnan (r))):
fortsetzen
sonst:
r.append (i)
return numpy.array (r)

00 - Bug Other

Hilfreichster Kommentar

Ich bin heute auf dasselbe Problem gestoßen. Der Kern der Routine np.unique besteht darin, eine Maske in einem entschlüsselten sortierten Array in numpy / lib / arraysetops.py zu berechnen, um festzustellen, wann sich die Werte in diesem sortierten Array ändern:

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

Dies könnte durch etwas wie das Folgende ersetzt werden, das ziemlich genau dem Kommentar von Jaimefrio von vor ungefähr 5 Jahren entspricht, aber den Argmin-Aufruf vermeidet:

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]

Beim Ausführen einiger% zeitlicher Experimente habe ich eine Laufzeitstrafe von höchstens <10% beobachtet, wenn das Array groß ist und nur sehr wenige NaN vorhanden sind (z. B. 10 NaN von 1 Million), und bei so großen Arrays läuft es tatsächlich schneller, wenn es viele gibt von NaN.

Wenn die Arrays hingegen klein sind (z. B. 10 Einträge), tritt ein erheblicher Leistungseinbruch auf, da die Überprüfung auf Float und NaN relativ teuer ist und die Laufzeit bis zu einem Vielfachen betragen kann. Dies gilt auch dann, wenn kein NaN vorhanden ist, da die Prüfung langsam ist.

Wenn das Array NaNs hat, erzeugt es ein anderes Ergebnis, indem es die NaNs kombiniert, worum es geht. In diesem Fall geht es also wirklich darum, ein gewünschtes Ergebnis (alle NaN in einer einzigen Wertegruppe zusammengefasst) etwas langsamer zu erhalten, während ein unerwünschtes Ergebnis (jedes NaN in seiner eigenen Wertgruppe) etwas schneller erzielt wird.

Beachten Sie schließlich, dass mit diesem Patch das Auffinden eindeutiger Werte für zusammengesetzte Objekte, die NaNs enthalten, nicht behoben werden kann, wie in diesem Beispiel:

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

was immer noch zurückkehren würde

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

Alle 14 Kommentare

_trac user rspringuel schrieb am 2010-06-18_

Schießen Sie, um die obigen Codeblöcke zu verwenden. Dies wirkt sich nur wirklich auf den Patch-Over-Code aus, daher werde ich Folgendes erneut veröffentlichen:

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)

Fest.

Ich sehe dieses Problem immer noch mit dem neuesten Master. Welches Commit hätte das beheben sollen? Sofern mir nichts fehlt, würde ich vorschlagen, diese Ausgabe erneut zu öffnen.

Dies ist für Floats leicht zu beheben, aber ich sehe keinen einfachen Ausweg für komplexe oder strukturierte D-Typen. Wir werden eine kurze PR zusammenstellen und wir können die Optionen dort besprechen.

@jaimefrio Ich habe es für die einmalige Verwendung behoben

    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]))

aber es sieht so aus, als hätten alle anderen Operationen ebenfalls Probleme. Vielleicht brauchen wir nan_equal, nan_not_equal ufuncs oder vielleicht etwas in Nanofunktionen.

Die Suche nach aux für sich selbst ist ein kluger Trick! Obwohl das Sortieren von _all_ davon ein wenig verschwenderisch ist, möchten wir im Idealfall den ersten Eintrag mit einer Nan erkennen, vielleicht etwas in der Art von, nachdem wir aux und flag wie jetzt gemacht haben ::

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

oder etwas Ähnliches, nachdem ich alle Fehler durch einen Fehler korrigiert habe, den ich wahrscheinlich dort eingeführt habe.

Dieser letzte Ansatz von mir würde für Gleitkomma- und komplexe Typen funktionieren, aber für strukturierte D-Typen mit Gleitkommafeldern fehlschlagen. Aber ich denke immer noch, dass der Suchsortierungstrick zu verschwenderisch ist, obwohl er für alle Typen funktionieren würde. Einige Timings:

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

Dies wird ein 40% iger Leistungseinbruch sein, der für eine nanunique -Funktion in Ordnung sein kann, aber wahrscheinlich nicht für den allgemeinen Fall.

2019 aufgerufen, ist das OP-Problem weiterhin gültig und der Code ist reproduzierbar.

@jaimefrio warum können wir nicht eine Option haben, die standardmäßig falsch ist?

Ich meine, dieses Verhalten ist bestenfalls verwirrend und Leistung ist keine Entschuldigung.

@ Demetrio92 Obwohl ich Ihren Versuch, dieses Problem in Gang zu bringen, sehr schätze, kann Ironie / Sarkasmus im Internet von verschiedenen Personen unterschiedlich interpretiert werden. Bitte halten Sie es freundlich. Für einige von uns ist die Leistung sehr wichtig und wir fügen nicht beiläufig Code hinzu, der die Dinge verlangsamt.

PR # 5487 ist möglicherweise ein besserer Ort, um Kommentare abzugeben oder Vorschläge zu machen, wie Sie vorankommen können.

Bearbeiten: PR-Nummer korrigieren

Dieses Problem scheint 8 Jahre lang offen zu sein, aber ich möchte mich nur mit +1 anmelden, um das Standardverhalten für numpy.unique korrekt und nicht schnell festzulegen. Dies hat meinen Code gebrochen und ich bin sicher, dass andere darunter leiden / leiden werden. Wir können ein optionales "fast = False" haben und das Nan-Verhalten für Fast und Nans dokumentieren. Es würde mich wundern, wenn np.unique sehr oft der Leistungsengpass in zeitkritischen Anwendungen ist.

Ich bin heute auf dasselbe Problem gestoßen. Der Kern der Routine np.unique besteht darin, eine Maske in einem entschlüsselten sortierten Array in numpy / lib / arraysetops.py zu berechnen, um festzustellen, wann sich die Werte in diesem sortierten Array ändern:

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

Dies könnte durch etwas wie das Folgende ersetzt werden, das ziemlich genau dem Kommentar von Jaimefrio von vor ungefähr 5 Jahren entspricht, aber den Argmin-Aufruf vermeidet:

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]

Beim Ausführen einiger% zeitlicher Experimente habe ich eine Laufzeitstrafe von höchstens <10% beobachtet, wenn das Array groß ist und nur sehr wenige NaN vorhanden sind (z. B. 10 NaN von 1 Million), und bei so großen Arrays läuft es tatsächlich schneller, wenn es viele gibt von NaN.

Wenn die Arrays hingegen klein sind (z. B. 10 Einträge), tritt ein erheblicher Leistungseinbruch auf, da die Überprüfung auf Float und NaN relativ teuer ist und die Laufzeit bis zu einem Vielfachen betragen kann. Dies gilt auch dann, wenn kein NaN vorhanden ist, da die Prüfung langsam ist.

Wenn das Array NaNs hat, erzeugt es ein anderes Ergebnis, indem es die NaNs kombiniert, worum es geht. In diesem Fall geht es also wirklich darum, ein gewünschtes Ergebnis (alle NaN in einer einzigen Wertegruppe zusammengefasst) etwas langsamer zu erhalten, während ein unerwünschtes Ergebnis (jedes NaN in seiner eigenen Wertgruppe) etwas schneller erzielt wird.

Beachten Sie schließlich, dass mit diesem Patch das Auffinden eindeutiger Werte für zusammengesetzte Objekte, die NaNs enthalten, nicht behoben werden kann, wie in diesem Beispiel:

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

was immer noch zurückkehren würde

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

"Wenn das Array NaNs hat, erzeugt es ein anderes Ergebnis, indem es die NaNs kombiniert, worum es geht."

+1

Eine Funktion, die eine Liste mit wiederholten Elementen zurückgibt, z. B. eine Liste mit mehr als 1 NaN, sollte nicht als "eindeutig" bezeichnet werden. Wenn wiederholte Elemente im Fall von NaN gewünscht werden, sollte dies nur ein Sonderfall sein, der standardmäßig deaktiviert ist, z. B. numpy.unique(..., keep_NaN=False) .

@ufmayer PR einreichen!

+1
Ich würde es auch unterstützen, NaN nur einmal zurückzugeben

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen