Numpy: Eckkoffer reduzieren (Trac # 236)

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

_Ursprüngliches Ticket http://projects.scipy.org/numpy/ticket/236 am 07.08.2006 vom Trac-Benutzer martin_wiechert, zugewiesen an unknown._

.reduceat behandelt wiederholte Indizes nicht korrekt. Wenn ein Index wiederholt wird, sollte das neutrale Element der Operation zurückgegeben werden. Im folgenden Beispiel wird [0, 10] und nicht [1, 10] erwartet.

In [1]:import numpy

In [2]:numpy.version.version
Out[2]:'1.0b1'

In [3]:a = numpy.arange (5)

In [4]:numpy.add.reduceat (a, (1,1))
Out[4]:array([ 1, 10])
01 - Enhancement 23 - Wish List numpy.core

Hilfreichster Kommentar

Die Hauptmotivation für reduceat besteht darin, eine Schleife über reduce für maximale Geschwindigkeit zu vermeiden. Ich bin mir also nicht ganz sicher, ob ein Wrapper einer for-Schleife über reduce eine sehr nützliche Ergänzung zu Numpy wäre. Es würde gegen den Hauptzweck von reduceat verstoßen.

Darüber hinaus ist die Logik für reduceat Existenz und API als schneller vektorisierter Ersatz für eine Schleife über reduce sauber und nützlich. Ich würde es nicht ablehnen, sondern reparieren.

In Bezug reduceat Geschwindigkeit von reduceat :

n = 10000
arr = np.random.random(n)
inds = np.random.randint(0, n, n//10)
inds.sort()

%timeit out = np.add.reduceat(arr, inds)
10000 loops, best of 3: 42.1 µs per loop

%timeit out = piecewise_reduce(np.add, arr, inds)
100 loops, best of 3: 6.03 ms per loop

Dies ist ein Zeitunterschied von mehr als dem 100-fachen und zeigt, wie wichtig es ist, die Effizienz reduceat .

Zusammenfassend würde ich der Festlegung von reduceat Vorrang vor der Einführung neuer Funktionen einräumen.

Ein start_indices und end_indices , obwohl es in einigen Fällen nützlich ist, ist oft überflüssig und ich würde es als mögliche Ergänzung sehen, aber nicht als Korrektur für die aktuellen reduceat inkonsistent Verhalten.

Alle 49 Kommentare

_ @ teoliphant schrieb am 08.08.2006_

Leider folgt die Reduceat-Methode von NumPy möglicherweise dem Verhalten der Reduceat-Methode von Numeric für diesen Eckfall.

Es gibt keine Möglichkeit, das "Identitäts" -Element der Operation in Fällen von Indexgleichheit zurückzugeben. Das definierte Verhalten besteht darin, das vom ersten Index angegebene Element zurückzugeben, wenn das Slice eine leere Sequenz zurückgibt. Daher ist das dokumentierte und tatsächliche Verhalten von Reduceat in diesem Fall zu konstruieren

[a [1], add.reduce (a [1:])]

Dies ist eine Funktionsanforderung.

_trac user martin_wiechert schrieb am 08.08.2006_

siehe auch Ticket Nr. 835

Der Meilenstein wurde von @alberts am 12.05.2007 in 1.1 geändert

Der Meilenstein wurde von @cournape am 02.03.2009 in Unscheduled geändert

Ich denke, dies hängt eng mit # 835 zusammen: Wenn einer der Indizes len(a) , kann reduceat das Element nicht an diesem Index ausgeben, was erforderlich ist, wenn der Index len(a) erscheint oder wird am Ende der Indizes wiederholt.

Einige Lösungen:

  • eine Option auf reduceat , um keinen Wert in der Ausgabe festzulegen, bei dem end - start == 0
  • eine Option, um die Ausgabe auf einen bestimmten festen Wert zu setzen, wobei end - start == 0
  • Ein where -Parameter, wie in ufunc() , der maskiert, welche Ausgaben überhaupt berechnet werden sollen.

Wurde zu diesem Thema noch mehr nachgedacht? Ich wäre an der Option interessiert, die Ausgabe auf den Identitätswert (falls vorhanden) zu setzen, wobei end - start == 0 ist.

Ich unterstütze nachdrücklich die Änderung des reduceat -Verhaltens, wie in dieser langjährigen offenen Ausgabe vorgeschlagen. Es sieht aus wie ein klarer Fehler oder ein offensichtlicher Designfehler, der die Nützlichkeit dieses großartigen Numpy-Konstrukts behindert.

reduceat sollte sich für alle Indizes konsistent verhalten. Für jeden Index i sollte nämlich ufunc.reduceat(a, indices) ufunc.reduce(a[indices[i]:indices[i+1]]) .

Dies sollte auch für den Fall indices[i] == indices[i+1] . Ich kann keinen vernünftigen Grund erkennen, warum in diesem Fall reduceat a[indices[i]] anstelle von ufunc.reduce(a[indices[i]:indices[i+1]]) .

Siehe auch HIER einen ähnlichen Kommentar von Pandas Schöpfer Wes McKinney .

Wow, das ist in der Tat schrecklich und kaputt.
.
Wir würden eine Diskussion auf der Mailingliste brauchen, aber ich würde es zumindest tun
voll und ganz dafür, diese Ausgabe in der nächsten Version zu einer FutureWarning zu machen
und Beheben des Verhaltens einige Releases später. Wir würden jemanden brauchen, der das nimmt
Beginnen Sie mit der Diskussion und schreiben Sie den Patch. Vielleicht bist du das?

Vielen Dank für die unterstützende Antwort. Ich kann eine Diskussion beginnen, wenn dies hilft, bin aber leider nicht in der Lage, den C-Code zu patchen.

Was beabsichtigen Sie für Ufuncs ohne Identität wie np.maximum?

Für solche Funktionen sollte eine leere Reduzierung ein Fehler sein, wie es bereits ist
wenn Sie .reduce () anstelle von .reduceat () verwenden.

In der Tat sollte das Verhalten von der Konsistenz mit ufunc.reduce(a[indices[i]:indices[i+1]]) , was jeder Benutzer erwarten würde. Dies erfordert also keine neuen Entwurfsentscheidungen. Es sieht für mich wirklich wie eine langjährige Fehlerbehebung aus. Es sei denn, jemand kann das derzeitige inkonsistente Verhalten rechtfertigen.

@njsmith Ich kann mich nicht bei der Numpy-Liste anmelden. Ich habe meine Adresse hier https://mail.scipy.org/mailman/listinfo/numpy-discussion gesendet, aber ich erhalte nie eine "E-Mail mit der Bitte um Bestätigung". Nicht sicher, ob man spezielle Anforderungen zum Abonnieren benötigt ...

@divenex :

Eine Version von reduceat , die mit ufunc.reduce(a[indices[i]:indices[i+1]]) übereinstimmt, wäre wirklich sehr, sehr schön. Es wäre so viel nützlicher! Entweder ein Argument zur Auswahl des Verhaltens oder eine neue Funktion ( reduce_intervals ? reduce_segments ? ...?) Würde verhindern, dass die Rückwärtsinkompatibilität unterbrochen wird.

Ich wäre vielleicht versucht, np.ufunc.reduceat verwerfen - es scheint sinnvoller zu sein, eine Reihe von Start- und Endindizes angeben zu können, um Fälle zu vermeiden, in denen indices[i] > indices[i+1] . Außerdem deutet der Name at auf eine viel größere Ähnlichkeit mit at als tatsächlich existiert

Was ich als Ersatz vorschlagen würde, ist np.piecewise_reduce np.reducebins , möglicherweise Pure-Python, was im Grunde genommen Folgendes bewirkt:

def reducebins(func, arr, start=None, stop=None, axis=-1, out=None):
    """
    Compute (in the 1d case) `out[i] = func.reduce(arr[start[i]:stop[i]])`

    If only `start` is specified, this computes the same reduce at `reduceat` did:

        `out[i]  = func.reduce(arr[start[i]:start[i+1]])`
        `out[-1] = func.reduce(arr[start[-1]:])`

    If only `stop` is specified, this computes:

        `out[0] = func.reduce(arr[:stop[0]])`
        `out[i] = func.reduce(arr[stop[i-1]:stop[i]])`

    """
    # convert to 1d arrays
    if start is not None:
        start = np.array(start, copy=False, ndmin=1, dtype=np.intp)
        assert start.ndim == 1
    if stop is not None:
        stop = np.array(stop, copy=False, ndmin=1, dtype=np.intp)
        assert stop.ndim == 1

    # default arguments that do useful things
    if start is None and stop is None:
        raise ValueError('At least one of start and stop must be specified')
    elif stop is None:
        # start only means reduce from one index to the next, and the last to the end
        stop = np.empty_like(start)
        stop[:-1] = start[1:]
        stop[-1] = arr.shape[axis]
    elif start is None:
        # stop only means reduce from the start to the first index, and one index to the next
        start = np.empty_like(stop)
        start[1:] = stop[:-1]
        start[0] = 0
    else:
        # TODO: possibly confusing?
        start, stop = np.broadcast_arrays(start, stop)

    # allocate output - not clear how to do this safely for subclasses
    if not out:
        sh = list(arr.shape)
        sh[axis] = len(stop)
        sh = tuple(sh)
        out = np.empty(shape=sh)

    # below assumes axis=0 for brevity here
    for i, (si, ei) in enumerate(zip(start, stop)):
        func.reduce(arr[si:ei,...], out=out[i, ...], axis=axis)
    return out

Welches hat die schönen Eigenschaften, dass:

  • np.add.reduce(arr) ist dasselbe wie np.piecewise_reduce(np.add, arr, 0, len(arr))
  • np.add.reduceat(arr, inds) ist dasselbe wie np.piecewise_reduce(np.add, arr, inds)
  • np.add.accumulate(arr) ist dasselbe wie np.piecewise_reduce(np.add, arr, 0, np.arange(len(arr)))

Möchte dies nun die __array_ufunc__ -Maschinerie durchlaufen? Das meiste, was behandelt werden muss, sollte bereits durch func.reduce abgedeckt sein - das einzige Problem ist die np.empty -Linie, ein Problem, das np.concatenate teilt.

Das klingt für mich aus API-Sicht nach einer guten Lösung. Selbst wenn Sie nur zwei Indexsätze für reduceat angeben könnten, würde dies ausreichen. Aus Sicht der Implementierung? Nun, es ist nicht sehr schwer, das aktuelle PyUFunc_Reduceat zu ändern, um zwei Sätze von Inds zu unterstützen, wenn dies Vorteile bringt. Wenn wir wirklich den Vorteil sehen, den akkumulierten Anwendungsfall effizient zu unterstützen, wäre es auch nicht schwer, dies zu tun.

Marten schlug in einer ähnlichen Diskussion von ~ 1 etwas Ähnliches vor
vor einem Jahr erwähnte er aber auch die Möglichkeit, eine "Schritt" -Option hinzuzufügen:

http://numpy-discussion.10968.n7.nabble.com/Behavior-of-reduceat-td42667.html

Dinge, die ich an Ihrem Vorschlag mag (also +1, wenn jemand zählt):

  • Erstellen einer neuen Funktion, anstatt zu versuchen, die vorhandene zu retten
    einer.
  • Festlegen der Argumente für den Start- und Endindex und nicht spezifisch
    sie auf magische Weise aus einem mehrdimensionalen Array herauszufinden.
  • Die Standardeinstellungen für die None-Indizes sind sehr ordentlich.

Dinge, die ich für wichtig halte, um über diese neue Funktion nachzudenken:

  • Sollten wir "Schritt" zu einer Option machen? (Ich würde ja sagen)
  • Ist es sinnvoll, dass die Index-Arrays gesendet werden, oder müssen sie gesendet werden?
    1D sein?
  • Sollte dies eine np-Funktion oder eine Ufunc-Methode sein? (Ich glaube, ich bevorzuge es
    als Methode)

Und von der Fahrradabteilung mag ich es besser:

  • Geben Sie ihm einen denkwürdigeren Namen, aber ich habe keinen Vorschlag.
  • Verwenden Sie 'start' und 'stop' (und 'step', wenn wir uns dafür entscheiden) für
    Konsistenz mit np.arange und Pythons Slice.
  • Löschen der _indices aus den kwarg-Namen.

Jaime

Am Do, 13. April 2017 um 13:47 Uhr, Eric Wieser [email protected]
schrieb:

Ich wäre vielleicht versucht, np.ufunc.reduceat insgesamt abzulehnen - es
scheint sinnvoller zu sein, eine Reihe von Start- und Endindizes angeben zu können, um
Vermeiden Sie Fälle, in denen Indizes [i]> Indizes [i + 1] sind. Außerdem schlägt der Name a vor
viel größere Ähnlichkeit als tatsächlich vorhanden

Was ich als Ersatz vorschlagen würde, ist np.piecewise_reduce, was im Grunde genommen
tut:

defpiecewise_reduce (func, arr, start_indices = Keine, end_indices = Keine, Achse = -1, out = Keine):
Wenn start_indices None und end_indices None ist:
start_indices = np.array ([0], dtype = np.intp)
end_indices = np.array (arr.shape [Achse], dtype = np.intp)
elif end_indices ist None:
end_indices = np.empty_like (start_indices)
end_indices [: - 1] = start_indices [1:]
end_indices [-1] = arr.shape [Achse]
elif start_indices ist None:
start_indices = np.empty_like (end_indices)
start_indices [1:] = end_indices
end_indices [0] = 0
sonst:
assert len ​​(start_indices) == len (end_indices)

if not out:
    sh = list(arr.shape)
    sh[axis] = len(end_indices)
    out = np.empty(shape=sh)

# below assumes axis=0 for brevity here
for i, (si, ei) in enumerate(zip(start_indices, end_indices)):
    func.reduce(arr[si:ei,...], out=alloc[i, ...], axis=axis)
return out

Welches hat die schönen Eigenschaften, dass:

  • np.ufunc.reduce ist dasselbe wie np.piecewise_reduce (func, arr, 0,
    len (arr))
  • np.ufunc.accumulate ist dasselbe wie `np.piecewise_reduce (func, arr,
    np.zeros (len (arr)), np.arange (len (arr)))

Will dies nun durch die __array_ufunc__- Maschinerie gehen? Die meisten von
Was gehandhabt werden muss, sollte bereits von func.reduce abgedeckt werden - der
Das einzige Problem ist die Zeile np.empty, ein Problem, das np.concatenate verursacht
Anteile.

- -
Sie erhalten dies, weil Sie diesen Thread abonniert haben.
Antworte direkt auf diese E-Mail und sieh sie dir auf GitHub an
https://github.com/numpy/numpy/issues/834#issuecomment-293867746 oder stumm schalten
der Faden
https://github.com/notifications/unsubscribe-auth/ADMGdtjSCodONyu6gCpwofdBaJMCIKa-ks5rvgtrgaJpZM4ANcqc
.

- -
(__ /)
(Oo)
(> <) Este es Conejo. Copia a Conejo en tu firma y ayúdale en sus Flugzeuge
de dominación mundial.

Verwenden Sie 'Start' und 'Stop'

Getan

Sollten wir "Schritt" zu einer Option machen?

Scheint ein ziemlich enger Anwendungsfall zu sein

Ist es sinnvoll, dass die Index-Arrays gesendet werden, oder müssen sie 1D sein?

Aktualisiert. > 1d ist offensichtlich schlecht, aber ich denke, wir sollten 0d und Rundfunk zulassen, für Fälle wie akkumulieren.

Sollte dies eine np-Funktion oder eine Ufunc-Methode sein? (Ich glaube, ich bevorzuge es
als Methode)

Jede Ufunc-Methode ist eine weitere Sache, mit der __array_ufunc__ umgehen muss.

Die Hauptmotivation für reduceat besteht darin, eine Schleife über reduce für maximale Geschwindigkeit zu vermeiden. Ich bin mir also nicht ganz sicher, ob ein Wrapper einer for-Schleife über reduce eine sehr nützliche Ergänzung zu Numpy wäre. Es würde gegen den Hauptzweck von reduceat verstoßen.

Darüber hinaus ist die Logik für reduceat Existenz und API als schneller vektorisierter Ersatz für eine Schleife über reduce sauber und nützlich. Ich würde es nicht ablehnen, sondern reparieren.

In Bezug reduceat Geschwindigkeit von reduceat :

n = 10000
arr = np.random.random(n)
inds = np.random.randint(0, n, n//10)
inds.sort()

%timeit out = np.add.reduceat(arr, inds)
10000 loops, best of 3: 42.1 µs per loop

%timeit out = piecewise_reduce(np.add, arr, inds)
100 loops, best of 3: 6.03 ms per loop

Dies ist ein Zeitunterschied von mehr als dem 100-fachen und zeigt, wie wichtig es ist, die Effizienz reduceat .

Zusammenfassend würde ich der Festlegung von reduceat Vorrang vor der Einführung neuer Funktionen einräumen.

Ein start_indices und end_indices , obwohl es in einigen Fällen nützlich ist, ist oft überflüssig und ich würde es als mögliche Ergänzung sehen, aber nicht als Korrektur für die aktuellen reduceat inkonsistent Verhalten.

Ich glaube nicht, dass Start- und Stoppindizes von verschiedenen Arrays stammen dürfen
würde einen großen Unterschied für die Effizienz machen, wenn in der C implementiert.

Am 13. April 2017 um 23:40 schrieb divenex [email protected] :

Die Hauptmotivation für Reduce ist es, eine Schleife über Reduce für zu vermeiden
maximale Geschwindigkeit. Ich bin mir also nicht ganz sicher, ob es sich um einen Wrapper einer for-Schleife handelt
Reduzieren wäre eine sehr nützliche Ergänzung zu Numpy. Es würde dagegen gehen
Hauptzweck reduzieren.

Darüber hinaus ist die Logik zur Reduzierung der Existenz und API als schnell vektorisiert
Ersatz für eine Schleife über reduzieren, ist sauber und nützlich. ich würde nicht
veraltet es, sondern repariert es.

Betrachten wir in Bezug auf die Reduktionsgeschwindigkeit ein einfaches Beispiel, das jedoch ähnlich ist
Einige reale Fälle, die ich in meinem eigenen Code habe, in denen ich reduceat verwende:

n = 10000
arr = np.random.random (n)
inds = np.random.randint (0, n, n // 10)
inds.sort ()
% timeit out = np.add.reduceat (arr, inds) 10000 Schleifen, am besten 3: 42,1 µs pro Schleife
% timeit out = stückweise_reduzieren (np.add, arr, inds) 100 Schleifen, am besten 3: 6,03 ms pro Schleife

Dies ist ein Zeitunterschied von mehr als 100x und verdeutlicht die Bedeutung
zur Erhaltung der Reduktionseffizienz.

Zusammenfassend würde ich der Festsetzung von Reduzierungen Vorrang vor der Einführung neuer geben
Funktionen.

Start_indices und end_indices zu haben, obwohl dies in einigen Fällen nützlich ist, ist
oft überflüssig und ich würde es als mögliche Ergänzung sehen, aber nicht als Lösung
für das aktuelle inkonsistente Reduktionsverhalten.

- -
Sie erhalten dies, weil Sie kommentiert haben.
Antworte direkt auf diese E-Mail und sieh sie dir auf GitHub an
https://github.com/numpy/numpy/issues/834#issuecomment-293898215 oder stumm schalten
der Faden
https://github.com/notifications/unsubscribe-auth/AAEz6xPex0fo2y_MqVHbNP5YNkJ0CBJrks5rviW-gaJpZM4ANcqc
.

Dies ist ein Zeitunterschied von mehr als dem 100-fachen und zeigt, wie wichtig es ist, die Effizienz der Reduktion aufrechtzuerhalten.

Vielen Dank dafür - ich glaube, ich habe den Overhead, der mit der ersten Phase eines reduce -Anrufs verbunden ist, unterschätzt (das passiert nur einmal für reduceat ).

Kein Argument gegen eine freie Funktion, aber sicherlich ein Argument gegen die Implementierung in reiner Python

aber nicht als Korrektur für das aktuelle inkonsistente Reduktionsverhalten.

Das Problem ist, dass es schwierig ist, das Verhalten von Code zu ändern, der schon so lange existiert.


Eine weitere mögliche Erweiterung: Wenn indices[i] > indices[j] , berechnen Sie die Umkehrung:

    for i, (si, ei) in enumerate(zip(start, stop)):
        if si >= ei:
            func.reduce(arr[si:ei,...], out=out[i, ...], axis=axis)
        else:
            func.reduce(arr[ei:si,...], out=out[i, ...], axis=axis)
            func.inverse(func.identity, out[i, ...], out=out[i, ...])

Wo np.add.inverse = np.subtract , np.multiply.inverse = np.true_divide . Dies führt zu der schönen Eigenschaft, dass

func.reduce(func.reduceat(x, inds_from_0)) == func.reduce(x))

Zum Beispiel

a = [1, 2, 3, 4]
inds = [0, 3, 1]
result = np.add.reduceat(a, inds) # [6, -5, 9] == [(1 + 2 + 3), -(3 + 2), (2 + 3 + 4)]

Das Problem ist, dass es schwierig ist, das Verhalten von Code zu ändern, der schon so lange existiert.

Dies ist teilweise der Grund, warum ich im E-Mail-Thread vorgeschlagen habe, einem 2D-Array von Indizes mit einer zusätzlichen Dimension von 2 oder 3 eine besondere Bedeutung zu geben: Es wird dann (effektiv) als Stapel von Slices interpretiert. Aber mir ist klar, dass dies auch etwas chaotisch ist und man natürlich genauso gut eine reduce_by_slice , slicereduce oder reduceslice Methode haben könnte.

ps Ich denke, alles, was auf vielen Ufuncs funktioniert, sollte eine Methode sein, damit es durch __array_ufunc__ und überschrieben werden kann.

Eigentlich ist ein anderer Vorschlag, den ich für viel besser halte: Anstatt reduceat retten, warum nicht ein slice Argument hinzufügen (oder start , stop , step ) bis ufunc.reduce !? Wie @ eric-wieser bemerkte, bedeutet eine solche Implementierung, dass wir reduceat insgesamt einfach verwerfen können, wie es nur wäre

add.reduce(array, slice=slice(indices[:-1], indices[1:])

(wo wir jetzt frei sind, das Verhalten an das anzupassen, was für ein leeres Slice erwartet wird)

Hier würde man das Slice senden, wenn es 0-d wäre, und könnte sogar in Betracht ziehen, Tupel von Slices zu übergeben, wenn ein Tupel von Achsen verwendet würde.

BEARBEITEN: Die obigen slice(indices[:-1], indices[1:]) , um die Erweiterung auf ein Tupel von Slices zu ermöglichen ( slice kann beliebige Daten enthalten, dies würde also gut funktionieren).

Ich würde immer noch eine Lösung für reduceat , um daraus eine richtige 100% vektorisierte Version von reduce , der logischsten Designlösung. Um zu vermeiden, dass Code beschädigt wird (siehe unten), kann alternativ eine äquivalente Methode mit dem Namen reducebins erstellt werden, bei der es sich lediglich um eine korrigierte Version von reduceat . Tatsächlich stimme ich @ eric-wieser zu, dass die Benennung von reduceat mehr Verbindung zur at -Funktion vermittelt als es gibt.

Ich verstehe die Notwendigkeit, Code nicht zu brechen. Aber ich muss sagen, dass es mir schwer fällt, mir vorzustellen, dass viel Code vom alten Verhalten abhängt, da es einfach keinen logischen Sinn ergibt und ich es einfach einen langjährigen Fehler nennen würde. Ich würde erwarten, dass der Code mit reduceat nur dafür sorgte, dass indices nicht dupliziert wurden, um ein Unsinnsergebnis von reduceat zu vermeiden, oder die Ausgabe wie bei out[:-1] *= np.diff(indices) > 0 . Natürlich würde mich ein Benutzerfall interessieren, in dem das alte Verhalten / der Fehler wie beabsichtigt verwendet wurde.

Ich bin nicht ganz überzeugt von der @ mhvk slice -Lösung, da sie eine nicht standardmäßige Verwendung für das slice -Konstrukt einführt. Darüber hinaus würde dies nicht mit der aktuellen Entwurfsidee von reduce vereinbar sein, nämlich _ "die Dimension von a um eins zu reduzieren, indem ufunc entlang einer Achse angewendet wird." _

Ich sehe auch keinen überzeugenden Benutzerfall für die Indizes start und end . Tatsächlich sehe ich die nette Designlogik der aktuellen reduceat -Methode, die konzeptionell np.histogram ähnelt, wobei bins , die _ "die Bin-Kanten definiert" _ durch ersetzt werden indices , die auch die Bins-Kanten darstellen, jedoch eher im Indexbereich als im Wert. Und reduceat wendet eine Funktion auf die Elemente an, die in jedem Paar von Behälterkanten enthalten sind. Das Histogramm ist ein äußerst beliebtes Konstrukt, benötigt jedoch keine Option zum Übergeben von zwei Vektoren des linken und rechten Randes und enthält in Numpy keine Option. Aus dem gleichen Grund bezweifle ich, dass beide Kanten in reduceat oder deren Ersatz dringend benötigt werden.

Die Hauptmotivation für das Reduzieren ist das Vermeiden eines Loop-Over-Reduzierens für maximale Geschwindigkeit. Ich bin mir also nicht ganz sicher, ob ein Wrapper einer for-Schleife eine sehr nützliche Ergänzung zu Numpy wäre. Es würde gegen den Hauptzweck der Reduzierung verstoßen.

Ich stimme @divenex hier zu. Die Tatsache, dass für reduceat Indizes sortiert und überlappend sein müssen, ist eine vernünftige Einschränkung, um sicherzustellen, dass die Schleife mit einem einzigen Durchgang über die Daten cacheeffizient berechnet werden kann. Wenn Sie überlappende Bins möchten, gibt es mit ziemlicher Sicherheit bessere Möglichkeiten, die gewünschte Operation zu berechnen (z. B. Rolling Window Aggregations).

Ich stimme auch zu, dass die sauberste Lösung darin besteht, eine neue Methode wie reducebins mit einer festen API zu definieren (und reduceat verwerfen) und nicht zu versuchen, sie in reduce zu quetschen

Hallo allerseits,

Ich möchte die Diskussion, dass dies ein Fehler ist, auf den Punkt bringen. Dies ist ein dokumentiertes Verhalten der Dokumentzeichenfolge :

For i in ``range(len(indices))``, `reduceat` computes
``ufunc.reduce(a[indices[i]:indices[i+1]])``, which becomes the i-th
generalized "row" parallel to `axis` in the final result (i.e., in a
2-D array, for example, if `axis = 0`, it becomes the i-th row, but if
`axis = 1`, it becomes the i-th column).  There are three exceptions to this:

* when ``i = len(indices) - 1`` (so for the last index),
  ``indices[i+1] = a.shape[axis]``.
* if ``indices[i] >= indices[i + 1]``, the i-th generalized "row" is
  simply ``a[indices[i]]``.
* if ``indices[i] >= len(a)`` or ``indices[i] < 0``, an error is raised.

Daher lehne ich jeden Versuch ab, das Verhalten von reduceat zu ändern.

Eine schnelle Github-Suche zeigt viele, viele Verwendungsmöglichkeiten der Funktion. Sind sich alle hier sicher, dass sie alle nur streng steigende Indizes verwenden?

In Bezug auf das Verhalten einer neuen Funktion würde ich argumentieren, dass ohne separate Start / Stopp-Arrays die Funktionalität stark beeinträchtigt wird. Es gibt viele Situationen, in denen man Werte in überlappenden Fenstern messen möchte, die nicht regelmäßig angeordnet sind (daher würden rollende Fenster nicht funktionieren). Zum Beispiel Regionen von Interesse, die durch eine unabhängige Methode bestimmt wurden. Und @divenex hat gezeigt, dass der Leistungsunterschied gegenüber der Python-Iteration

Es gibt viele Situationen, in denen man Werte in überlappenden Fenstern messen möchte, die nicht regelmäßig angeordnet sind (daher würden rollende Fenster nicht funktionieren).

Ja, aber Sie möchten keine naive Schleife wie die von reduceat implementierte verwenden. Sie möchten eine eigene Rolling Window-Berechnung implementieren, in der Zwischenergebnisse auf irgendeine Weise gespeichert werden, damit sie in einem einzigen linearen Durchlauf über die Daten durchgeführt werden können. Aber jetzt sprechen wir über einen Algorithmus, der viel komplizierter ist als reduceat .

@shoyer Ich kann mir Fälle vorstellen, in denen sich nur einige der ROIs überschneiden. In solchen Fällen wäre das Schreiben eines benutzerdefinierten Algorithmus ein großer Overkill. Vergessen wir nicht, dass unsere Hauptnutzerbasis Wissenschaftler sind, die normalerweise wenig Zeit haben und eine "gut genug" -Lösung benötigen, nicht das absolute Optimum. Aufgrund der niedrigen konstanten Faktoren, die mit der Komplexität von np.reduceat , ist es schwierig oder unmöglich, mit reinem Python-Code eine bessere Lösung zu finden - meistens sind die einzigen Code-Benutzer bereit zu schreiben.

@jni Sicher, das Reduzieren in Gruppen mit beliebigen Starts und Stopps könnte nützlich sein. Aber es scheint mir eine bedeutende Erweiterung des Anwendungsbereichs zu sein und etwas, das besser für eine andere Methode geeignet ist als ein Ersatz für reduceat (den wir auf jeden Fall ablehnen möchten, auch wenn wir ihn nie entfernen).

Das Reduzieren in Gruppen mit beliebigen Starts und Stopps könnte nützlich sein. Aber es fühlt sich für mich nach einer deutlichen Erweiterung des Anwendungsbereichs an

Das scheint mir sehr trivial zu sein. Im Moment haben wir Code, der im Wesentlichen ind1 = indices[i], ind2 = indices[i + 1] . Das Ändern, um zwei verschiedene Arrays anstelle desselben zu verwenden, sollte sehr wenig Aufwand bedeuten.

Und das Single-Pass-Verhalten, wenn zusammenhängende Bereiche übergeben werden, sollte fast genauso schnell sein wie jetzt - der einzige Overhead ist ein weiteres Argument für den Nditer

Das scheint mir sehr trivial zu sein.

Genau. Darüber hinaus ist es eine Funktionalität, die Benutzer mit reduceat (indem sie jeden anderen Index verwenden), aber mit einer neuen Funktion verlieren würden, die keine Überlappung unterstützt.

Darüber hinaus könnte eine Zwei-Index-Form das alte (bizarre) Verhalten emulieren:

def reduceat(func, arr, inds):
    deprecation_warning()
    start = inds
    stops = zeros(inds.shape)
    stops[:-1] = start[1:]
    stops[-1] = len(arr)
    np.add(stops, 1, where=ends == starts, out=stops)  # reintroduce the "bug" that we would have to keep
    return reducebins(func, arr, starts, stops)

Das heißt, wir müssen nicht zwei sehr ähnliche Implementierungen warten

Ich bin nicht stark gegen die Indizes starts und stops für die neuen reducebins , obwohl ich immer noch kein offensichtliches Beispiel sehe, wo beide benötigt werden. Es fühlt sich an, als würde man np.histogram verallgemeinern, indem man Start- und Endkanten von bins hinzufügt ...

Letztendlich ist dies in Ordnung, solange die Hauptnutzung nicht betroffen ist und man immer noch reducebins(arr, indices) mit einem einzelnen Array von Indizes und ohne Geschwindigkeitsstrafe aufrufen kann.

Natürlich gibt es viele Situationen, in denen man nicht überlappende Bins bearbeiten muss, aber in diesem Fall würde ich im Allgemeinen erwarten, dass die Bins nicht nur durch Kantenpaare definiert werden. Eine verfügbare Funktion für diese Art von Szenario ist Scipys ndimage.labeled_comprehension und die zugehörigen Funktionen wie ndimage.sum und so weiter.

Dies scheint jedoch ganz anders zu sein als der Umfang von reducebins .

Was wäre also ein natürlicher Anwendungsfall für starts und stops in reducebins ?

Was wäre also ein natürlicher Anwendungsfall für Starts und Stopps in Reduktionsbehältern?

Auf andere Weise erreichbar, aber ein gleitender Durchschnitt der Länge k wäre reducebins(np,add, arr, arange(n-k), k + arange(n-k)) . Ich vermute, dass die Performance ohne Berücksichtigung der Kosten für die Allokation der Indizes mit einem as_strided -Ansatz vergleichbar wäre.

Einzigartig ist, dass reducebins einen gleitenden Durchschnitt unterschiedlicher Dauer zulässt, was mit as_strided nicht möglich ist

Ein weiterer Anwendungsfall - Unterscheidung zwischen dem Einfügen des Endes oder des Anfangs in das Ein-Argument-Formular.

Zum Beispiel:

a = np.arange(10)
reducebins(np.add, start=[2, 4, 6]) == [2 + 3, 4 + 5, 6 + 7 + 8 + 9]  # what `reduceat` does
reducebins(np.add, stop=[2, 4, 6])  == [0 + 1, 2 + 3, 4 + 5]          # also useful

Ein weiterer Anwendungsfall - Unterscheidung zwischen dem Einfügen des Endes oder des Anfangs in das Ein-Argument-Formular.

Ich verstehe das nicht ganz. Können Sie hier den Eingangstensor einfügen? Außerdem: Was wären die Standardwerte für start / stop ?

Wie auch immer, ich bin nicht stark gegen die einzelnen Argumente, aber es ist nicht so sauber von einem Ersatz. Ich würde gerne sagen können "Verwenden Sie nicht Reduceat, sondern Reducebins", aber das ist (etwas) schwieriger, wenn die Benutzeroberfläche anders aussieht.

Eigentlich habe ich gerade festgestellt, dass selbst eine Start / Stopp-Option nicht den Anwendungsfall von leeren Slices abdeckt, was für mich in der Vergangenheit nützlich war: Wenn meine Eigenschaften / Beschriftungen Zeilen in einer CSR-Sparse-Matrix entsprechen, und ich benutze die Werte von indptr , um die Reduktion durchzuführen. Mit reduceat kann ich die leeren Zeilen ignorieren. Jeder Ersatz erfordert eine zusätzliche Buchhaltung. Unabhängig davon, welchen Ersatz Sie sich einfallen lassen, lassen Sie bitte reduceat Nähe.

In [2]: A = np.random.random((4000, 4000))
In [3]: B = sparse.csr_matrix((A > 0.8) * A)
In [9]: %timeit np.add.reduceat(B.data, B.indptr[:-1]) * (np.diff(B.indptr) > 1)
1000 loops, best of 3: 1.81 ms per loop
In [12]: %timeit B.sum(axis=1).A
100 loops, best of 3: 1.95 ms per loop
In [16]: %timeit np.maximum.reduceat(B.data, B.indptr[:-1]) * (np.diff(B.indptr) > 0)
1000 loops, best of 3: 1.8 ms per loop
In [20]: %timeit B.max(axis=1).A
100 loops, best of 3: 2.12 ms per loop

Im Übrigen kann das Rätsel um leere Sequenzen auf die gleiche Weise gelöst werden wie Python : indem ein Anfangswert angegeben wird. Dies kann ein Skalar oder ein Array mit der gleichen Form wie indices .

Ja, ich stimme zu, dass der erste Fokus auf dem Lösen der leeren Scheiben liegen muss
Fall. Im Fall von Start = Ende können wir entweder eine Möglichkeit haben, die Ausgabe einzustellen
Element zur Identität oder um das Ausgabeelement nicht mit a zu ändern
angegebenes Array. Das Problem mit dem Strom ist, dass er überschrieben wird
mit irrelevanten Daten

Ich bin voll und ganz bei @shoyer über seinen letzten Kommentar.

Definieren wir einfach out=ufunc.reducebins(a, inds) als out[i]=ufunc.reduce(a[inds[i]:inds[i+1]]) für alle i außer dem letzten und verwerfen reduceat .

Aktuelle Anwendungsfälle für starts und ends Indizes scheinen natürlicher und wahrscheinlich effizienter mit alternativen Funktionen wie as_strided oder Convolutions implementiert zu werden.

@shoyer :

Ich verstehe das nicht ganz. Können Sie hier den Eingangstensor einfügen? Außerdem: Was wären die Standardwerte für Start / Stopp?

Mit der Eingabe aktualisiert. Die Standardwerte finden Sie in der Implementierung von reduce_bins in dem Kommentar, mit dem dies gestartet wurde . Ich habe dort auch einen Docstring hinzugefügt. Diese Implementierung ist vollständig, aber langsam (da es sich um Python handelt).

Aber das ist (etwas) schwieriger, wenn die Benutzeroberfläche anders aussieht.

Wenn nur eines der Argumente start übergeben wird, ist die Schnittstelle identisch (wobei die Identitätsfälle ignoriert werden, die wir ursprünglich beheben wollten). Diese drei Zeilen bedeuten dasselbe:

np.add.reduce_at(arr, inds)
reduce_bins(np.add, arr, inds)
reduce_bins(np.add, arr, start=inds)

(Die Unterscheidung zwischen Methode und Funktion ist mir nicht besonders wichtig, und ich kann keine neue Ufunc-Methode als Prototyp in Python definieren!)


@jni :

Eigentlich habe ich gerade festgestellt, dass selbst eine Start / Stopp-Option nicht den Anwendungsfall von leeren Slices abdeckt, was für mich in der Vergangenheit nützlich war

Du liegst falsch - genau so, wie es ufunc.reduceat bereits tut. Es ist auch einfach durch Übergeben von start[i] == end[i] .

Das Rätsel der leeren Sequenz kann gelöst werden, indem ein Anfangswert angegeben wird.

Ja, wir haben dies bereits behandelt, und ufunc.reduce tut dies bereits, indem es mit ufunc.identity gefüllt wird. Dies ist nicht schwer zu den vorhandenen ufunc.reduecat hinzuzufügen, insbesondere wenn # 8952 zusammengeführt wird. Aber wie Sie selbst sagten, ist das aktuelle Verhalten _dokumentiert_, daher sollten wir es wahrscheinlich nicht ändern.


@ Divenex

Definieren wir einfach out = ufunc.reducebins (a, inds) als out [i] = ufunc.reduce (a [inds [i]: inds [i + 1]]) für alle i außer dem letzten

Also len(out) == len(inds) - 1 ? Dies unterscheidet sich vom aktuellen Verhalten von reduceat , daher ist @shoyers Argument zum Umschalten hier stärker


Alle: Ich habe frühere Kommentare durchgesehen und zitierte E-Mail-Antworten entfernt, da diese Diskussion schwer lesbar waren

@ Eric-Wieser guter Punkt. In meinem obigen Satz habe ich gemeint, dass für den letzten Index das Verhalten von reducebins anders sein würde als im aktuellen reduceat . In diesem Fall bin ich mir jedoch nicht sicher, wie hoch der Wert sein soll, da der letzte Wert formal keinen Sinn ergibt.

Ohne Berücksichtigung von Kompatibilitätsproblemen sollte die Ausgabe von reducebins (in 1D) die Größe inds.size-1 , aus dem gleichen Grund, aus dem np.diff(a) die Größe a.size-1 und np.histogram(a, bins) hat die Größe bins.size-1 . Dies würde jedoch dem Wunsch widersprechen, einen Ersatz für reduceat .

Ich glaube nicht, dass es ein überzeugendes Argument gibt, dass a.size-1 die richtige Antwort ist - einschließlich Index 0 und / oder Index n scheint auch ein ziemlich vernünftiges Verhalten zu sein. Alle scheinen unter bestimmten Umständen praktisch zu sein, aber ich denke, es ist sehr wichtig, einen Ersatz zu haben.

Es gibt auch ein weiteres Argument für stop / start sich hier versteckt - es ermöglicht Ihnen, das diff -ähnliche Verhalten zu erstellen, wenn Sie es möchten, mit sehr geringen Kosten, während Sie das beibehalten reduceat Verhalten:

a = np.arange(10)
inds = [2, 4, 6]
reduce_bins(a, start=inds[:-1], stop=inds[1:])  #  [2 + 3, 4 + 5]

# or less efficiently:
reduce_at(a, inds)[:-1}
reduce_bins(a, start=inds)[:-1]
reduce_bins(a, stop=inds)[1:]

@ eric-wieser Ich wäre mit den erforderlichen Argumenten start und stop in Ordnung, aber ich mag es nicht, eines davon optional zu machen. Es ist nicht offensichtlich, dass die Bereitstellung von nur start out[i] = func.reduce(arr[start[i]:start[i+1]]) und nicht out[i] = func.reduce(arr[start[i]:]) , was ich vermutet hätte.

Meine bevorzugte API für reducebins ist wie reduceat jedoch ohne die verwirrenden "Ausnahmen", die in der Dokumentzeichenfolge angegeben sind . Nämlich nur:

Für i in range(len(indices)) berechnet reduceat ufunc.reduce(a[indices[i]:indices[i+1]]) , was im Endergebnis zur i-ten verallgemeinerten "Zeile" parallel zur Achse wird (dh in einem 2D-Array, wenn beispielsweise axis = 0 wird es die i-te Zeile, aber wenn Achse = 1 ist, wird es die i-te Spalte).

Ich könnte bei der dritten "Ausnahme", für die nicht negative Indizes ( 0 <= indices[i] <= a.shape[axis] ) erforderlich sind, in beide Richtungen gehen, was ich eher als Überprüfung der geistigen Gesundheit als als Ausnahme betrachte. Aber möglicherweise könnte man auch gehen - ich kann sehen, wie nützlich negative Indizes für jemanden sein könnten, und es ist nicht schwer zu rechnen, um solche Indizes zu normalisieren.

Das nicht automatische Hinzufügen eines Index am Ende bedeutet, dass das Ergebnis die Länge len(a)-1 , wie das Ergebnis von np.histogram .

@jni Können Sie bitte ein Beispiel dafür geben, was Sie tatsächlich aus Arrays berechnen möchten, die in dünn

Es ist nicht offensichtlich, dass nur start [i] = func.reduce (arr [start [i]: start [i + 1]]) und nicht out [i] = func.reduce (arr [start [i]) bedeutet. :]), was ich vermutet hätte.

Die Lesung, die ich anstrebte, lautet: "Jeder Behälter beginnt an diesen Positionen", mit der Folge, dass alle Behälter zusammenhängend sind, sofern nicht ausdrücklich anders angegeben. Vielleicht sollte ich versuchen, einen vollständigeren Docstring zu entwerfen. Ich denke, ich kann ein starkes Argument dafür sehen, dass es verboten ist, keines der beiden Argumente zu übergeben, also werde ich das aus meiner vorgeschlagenen Funktion entfernen.

was nicht negative Indizes erfordert (0 <= Indizes [i] <a.shape [Achse])

Beachten Sie, dass es hier auch einen Fehler gibt (# 835) - die Obergrenze sollte inklusive sein, da dies Slices sind.

Beachten Sie, dass es hier auch einen Fehler gibt - die Obergrenze sollte inklusive sein, da es sich um Slices handelt.

Behoben, danke.

Nicht in der reduceat -Funktion selbst, hast du nicht;)

Es stellt sich heraus, dass :\doc\neps\groupby_additions.rst einen (IMO minderwertigen) Vorschlag für eine reduceby -Funktion enthält.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen