Verschieben Sie Elemente in einem Numpy-Array

83

Gibt es nach dieser Frage vor Jahren eine kanonische "Shift" -Funktion in Numpy? Ich sehe nichts aus der Dokumentation .

Hier ist eine einfache Version von dem, wonach ich suche:

def shift(xs, n):
    if n >= 0:
        return np.r_[np.full(n, np.nan), xs[:-n]]
    else:
        return np.r_[xs[-n:], np.full(-n, np.nan)]

Dies zu verwenden ist wie folgt:

In [76]: xs
Out[76]: array([ 0.,  1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9.])

In [77]: shift(xs, 3)
Out[77]: array([ nan,  nan,  nan,   0.,   1.,   2.,   3.,   4.,   5.,   6.])

In [78]: shift(xs, -3)
Out[78]: array([  3.,   4.,   5.,   6.,   7.,   8.,   9.,  nan,  nan,  nan])

Diese Frage kam von meinem Versuch, gestern ein schnell rollendes Produkt zu schreiben . Ich brauchte einen Weg, um ein kumulatives Produkt zu "verschieben", und alles, was ich mir vorstellen konnte, war, die Logik zu replizieren np.roll().


Ist np.concatenate()also viel schneller als np.r_[]. Diese Version der Funktion bietet eine viel bessere Leistung:

def shift(xs, n):
    if n >= 0:
        return np.concatenate((np.full(n, np.nan), xs[:-n]))
    else:
        return np.concatenate((xs[-n:], np.full(-n, np.nan)))

Eine noch schnellere Version weist das Array einfach vorab zu:

def shift(xs, n):
    e = np.empty_like(xs)
    if n >= 0:
        e[:n] = np.nan
        e[n:] = xs[:-n]
    else:
        e[n:] = np.nan
        e[:n] = xs[-n:]
    return e
Chrisaycock
quelle
Ich frage mich, ob np.r_[np.full(n, np.nan), xs[:-n]]ich es durch eine np.r_[[np.nan]*n, xs[:-n]]andere Bedingung ersetzen könnte , ohne die Notwendigkeit vonnp.full
Zero
2
@ JohnGalt [np.nan]*nist eine einfache Python und wird daher langsamer sein als np.full(n, np.nan). Nicht für kleine n, aber es wird von np.r_ in ein numpy-Array umgewandelt, was den Vorteil zunichte macht.
Swenzel
@ Swenzel Nur zeitlich festgelegt und [np.nan]*nist schneller als np.full(n, np.nan)für n=[10,1000,10000]. Sie müssen überprüfen, ob np.r_ein Treffer erzielt wird.
Null
Wenn es um die Geschwindigkeit geht, spielt die Arraygröße eine große Rolle für den besten Algorithmus (unten ein Benchmark-Vergleich hinzugefügt). Heutzutage kann numba.njit auch verwendet werden, um die Verschiebung zu beschleunigen, wenn sie wiederholt aufgerufen wird.
np8

Antworten:

99

Nicht numpy, sondern scipy bietet genau die gewünschte Shift-Funktionalität.

import numpy as np
from scipy.ndimage.interpolation import shift

xs = np.array([ 0.,  1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9.])

shift(xs, 3, cval=np.NaN)

Wenn standardmäßig ein konstanter Wert von außerhalb des Arrays mit Wert eingegeben wird cval, setzen Sie hier auf nan. Dies ergibt die gewünschte Ausgabe,

array([ nan, nan, nan, 0., 1., 2., 3., 4., 5., 6.])

und die negative Verschiebung funktioniert ähnlich,

shift(xs, -3, cval=np.NaN)

Bietet Ausgabe

array([  3.,   4.,   5.,   6.,   7.,   8.,   9.,  nan,  nan,  nan])
Ed Smith
quelle
23
Die Scipy-Shift-Funktion ist WIRKLICH langsam. Ich habe meine eigene mit np.concatenate gerollt und es war viel schneller.
Gaefan
12
numpy.roll ist schneller. Pandas benutzt es auch. github.com/pandas-dev/pandas/blob/v0.19.2/pandas/core/…
fx-kirin
Habe gerade scipy.ndimage.interpolation.shift (scipy 1.4.1) gegen alle anderen auf dieser Seite aufgeführten Alternativen getestet (siehe meine Antwort unten), und dies ist die langsamste mögliche Lösung. Nur verwenden, wenn die Geschwindigkeit in Ihrer Anwendung keine Rolle spielt.
np8
70

Für diejenigen, die nur die schnellste Implementierung von Shift kopieren und einfügen möchten, gibt es einen Benchmark und eine Schlussfolgerung (siehe Ende). Außerdem führe ich den Parameter fill_value ein und behebe einige Fehler.

Benchmark

import numpy as np
import timeit

# enhanced from IronManMark20 version
def shift1(arr, num, fill_value=np.nan):
    arr = np.roll(arr,num)
    if num < 0:
        arr[num:] = fill_value
    elif num > 0:
        arr[:num] = fill_value
    return arr

# use np.roll and np.put by IronManMark20
def shift2(arr,num):
    arr=np.roll(arr,num)
    if num<0:
         np.put(arr,range(len(arr)+num,len(arr)),np.nan)
    elif num > 0:
         np.put(arr,range(num),np.nan)
    return arr

# use np.pad and slice by me.
def shift3(arr, num, fill_value=np.nan):
    l = len(arr)
    if num < 0:
        arr = np.pad(arr, (0, abs(num)), mode='constant', constant_values=(fill_value,))[:-num]
    elif num > 0:
        arr = np.pad(arr, (num, 0), mode='constant', constant_values=(fill_value,))[:-num]

    return arr

# use np.concatenate and np.full by chrisaycock
def shift4(arr, num, fill_value=np.nan):
    if num >= 0:
        return np.concatenate((np.full(num, fill_value), arr[:-num]))
    else:
        return np.concatenate((arr[-num:], np.full(-num, fill_value)))

# preallocate empty array and assign slice by chrisaycock
def shift5(arr, num, fill_value=np.nan):
    result = np.empty_like(arr)
    if num > 0:
        result[:num] = fill_value
        result[num:] = arr[:-num]
    elif num < 0:
        result[num:] = fill_value
        result[:num] = arr[-num:]
    else:
        result[:] = arr
    return result

arr = np.arange(2000).astype(float)

def benchmark_shift1():
    shift1(arr, 3)

def benchmark_shift2():
    shift2(arr, 3)

def benchmark_shift3():
    shift3(arr, 3)

def benchmark_shift4():
    shift4(arr, 3)

def benchmark_shift5():
    shift5(arr, 3)

benchmark_set = ['benchmark_shift1', 'benchmark_shift2', 'benchmark_shift3', 'benchmark_shift4', 'benchmark_shift5']

for x in benchmark_set:
    number = 10000
    t = timeit.timeit('%s()' % x, 'from __main__ import %s' % x, number=number)
    print '%s time: %f' % (x, t)

Benchmark-Ergebnis:

benchmark_shift1 time: 0.265238
benchmark_shift2 time: 0.285175
benchmark_shift3 time: 0.473890
benchmark_shift4 time: 0.099049
benchmark_shift5 time: 0.052836

Fazit

shift5 ist der Gewinner! Es ist die dritte Lösung von OP.

gzc
quelle
Danke für die Vergleiche. Irgendeine Idee, was der schnellste Weg ist, ohne ein neues Array zu verwenden?
FiReTiTi
2
In der letzten Klausel shift5ist es besser zu schreiben result[:] = arrstatt result = arr, Funktion Verhalten konsistent zu halten.
Avysk
2
Dies sollte als Antwort gewählt werden
wyx
@avysk Kommentar ist ziemlich wichtig - bitte aktualisieren Sie die shift5-Methode. Funktionen, die manchmal eine Kopie und manchmal eine Referenz zurückgeben, sind der Weg zur Hölle.
David
2
@ Josmoor98 Das liegt daran type(np.NAN) is float. Wenn Sie ein Integer-Array mit diesen Funktionen verschieben, müssen Sie einen Integer-Füllwert angeben.
GZC
8

Es gibt keine einzige Funktion, die das tut, was Sie wollen. Ihre Definition von Verschiebung unterscheidet sich geringfügig von der der meisten Menschen. Die Möglichkeiten zum Verschieben eines Arrays werden häufiger wiederholt:

>>>xs=np.array([1,2,3,4,5])
>>>shift(xs,3)
array([3,4,5,1,2])

Sie können jedoch mit zwei Funktionen tun, was Sie wollen.
Bedenken Sie a=np.array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9.]):

def shift2(arr,num):
    arr=np.roll(arr,num)
    if num<0:
         np.put(arr,range(len(arr)+num,len(arr)),np.nan)
    elif num > 0:
         np.put(arr,range(num),np.nan)
    return arr
>>>shift2(a,3)
[ nan  nan  nan   0.   1.   2.   3.   4.   5.   6.]
>>>shift2(a,-3)
[  3.   4.   5.   6.   7.   8.   9.  nan  nan  nan]

Nachdem ich cProfile für Ihre angegebene Funktion und den oben angegebenen Code ausgeführt habe, stellte ich fest, dass der von Ihnen bereitgestellte Code 42 Funktionsaufrufe ausführt, während shift214 Aufrufe getätigt werden, wenn arr positiv ist, und 16, wenn er negativ ist. Ich werde mit dem Timing experimentieren, um zu sehen, wie sich jedes mit realen Daten verhält.

IronManMark20
quelle
1
Hey, danke, dass du dir das angeschaut hast. Ich weiß von np.roll(); Ich habe die Technik in den Links in meiner Frage verwendet. Gibt es eine Chance für Ihre Implementierung, dass Ihre Funktion für negative Verschiebungswerte funktioniert?
Chrisaycock
Interessanterweise np.concatenate()ist viel schneller als np.r_[]. Ersteres wird schließlich np.roll()verwendet.
Chrisaycock
5

Sie können zuerst ndarrayin Seriesoder DataFramemit konvertieren pandas, dann können Sie die shiftMethode verwenden, wie Sie möchten.

Beispiel:

In [1]: from pandas import Series

In [2]: data = np.arange(10)

In [3]: data
Out[3]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

In [4]: data = Series(data)

In [5]: data
Out[5]: 
0    0
1    1
2    2
3    3
4    4
5    5
6    6
7    7
8    8
9    9
dtype: int64

In [6]: data = data.shift(3)

In [7]: data
Out[7]: 
0    NaN
1    NaN
2    NaN
3    0.0
4    1.0
5    2.0
6    3.0
7    4.0
8    5.0
9    6.0
dtype: float64

In [8]: data = data.values

In [9]: data
Out[9]: array([ nan,  nan,  nan,   0.,   1.,   2.,   3.,   4.,   5.,   6.])
JsonBruce
quelle
Großartig, viele Leute benutzen Pandas zusammen mit Numpy, und das ist sehr hilfreich!
VanDavv
5

Benchmarks & Einführung von Numba

1. Zusammenfassung

  • Die akzeptierte Antwort ( scipy.ndimage.interpolation.shift) ist die langsamste auf dieser Seite aufgeführte Lösung.
  • Numba (@ numba.njit) bietet eine gewisse Leistungssteigerung, wenn die Arraygröße kleiner als ~ 25.000 ist
  • "Jede Methode" ist gleich gut, wenn das Array groß ist (> 250.000).
  • Die schnellste Option hängt wirklich von
        (1) der Länge Ihrer Arrays
        (2) dem Schichtaufwand ab, den Sie ausführen müssen.
  • Unten sehen Sie das Bild der Zeitabläufe aller auf dieser Seite aufgeführten Methoden (11.07.2020) bei konstanter Verschiebung = 10. Wie Sie sehen können, verwenden einige Methoden bei kleinen Arraygrößen mehr als + 2000% der Zeit als die beste Methode.

Relative Timings, konstante Verschiebung (10), alle Methoden

2. Detaillierte Benchmarks mit den besten Optionen

  • Wählen Sie shift4_numba(unten definiert), wenn Sie einen guten Allrounder wollen

Relative Timings, beste Methoden (Benchmarks)

3. Code

3.1 shift4_numba

  • Guter Allrounder; max 20% wrt. zur besten Methode mit jeder Arraygröße
  • Beste Methode mit mittleren Arraygrößen: ~ 500 <N <20.000.
  • Vorsichtsmaßnahme: Numba Jit (Just-in-Time-Compiler) steigert die Leistung nur, wenn Sie die dekorierte Funktion mehrmals aufrufen. Der erste Anruf dauert normalerweise 3-4 mal länger als die nachfolgenden Anrufe.
import numba

@numba.njit
def shift4_numba(arr, num, fill_value=np.nan):
    if num >= 0:
        return np.concatenate((np.full(num, fill_value), arr[:-num]))
    else:
        return np.concatenate((arr[-num:], np.full(-num, fill_value)))

3.2. shift5_numba

  • Beste Option mit kleinen (N <= 300 .. 1500) Arraygrößen. Der Schwellenwert hängt vom erforderlichen Verschiebungsbetrag ab.
  • Gute Leistung bei jeder Arraygröße; max + 50% im Vergleich zur schnellsten Lösung.
  • Vorsichtsmaßnahme: Numba Jit (Just-in-Time-Compiler) steigert die Leistung nur, wenn Sie die dekorierte Funktion mehrmals aufrufen. Der erste Anruf dauert normalerweise 3-4 mal länger als die nachfolgenden Anrufe.
import numba

@numba.njit
def shift5_numba(arr, num, fill_value=np.nan):
    result = np.empty_like(arr)
    if num > 0:
        result[:num] = fill_value
        result[num:] = arr[:-num]
    elif num < 0:
        result[num:] = fill_value
        result[:num] = arr[-num:]
    else:
        result[:] = arr
    return result

3.3. shift5

  • Beste Methode mit Arraygrößen ~ 20.000 <N <250.000
  • Gleich wie shift5_numba, entfernen Sie einfach das @ numba.njit Dekorateur.

4 Anhang

4.1 Details zu den verwendeten Methoden

  • shift_scipy: scipy.ndimage.interpolation.shift(scipy 1.4.1) - Die Option aus der akzeptierten Antwort, die eindeutig die langsamste Alternative ist .
  • shift1: np.rollund out[:num] xnp.nanvon IronManMark20 & gzc
  • shift2: np.rollund np.putvon IronManMark20
  • shift3: np.padund slicevon gzc
  • shift4: np.concatenateund np.fullvon Chrisaycock
  • shift5: zweimal result[slice] = xmit chrisaycock
  • shift#_numba: @ numba .njit dekorierte Versionen der vorherigen.

Die shift2und shift3enthaltenen Funktionen, die von der aktuellen Nummer (0.50.1) nicht unterstützt wurden.

4.2 Andere Testergebnisse

4.2.1 Relative Timings, alle Methoden

4.2.2 Raw Timings, alle Methoden

4.2.3 Rohdaten, einige der besten Methoden

np8
quelle
4

Sie können dies auch mit Pandas tun:

Verwenden eines 2356 langen Arrays:

import numpy as np

xs = np.array([...])

Scipy verwenden:

from scipy.ndimage.interpolation import shift

%timeit shift(xs, 1, cval=np.nan)
# 956 µs ± 77.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Verwenden von Pandas:

import pandas as pd

%timeit pd.Series(xs).shift(1).values
# 377 µs ± 9.42 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

In diesem Beispiel war die Verwendung von Pandas etwa achtmal schneller als die von Scipy

Ran Aroussi
quelle
2
Die schnellste Methode ist die Vorabzuweisung, die ich am Ende meiner Frage veröffentlicht habe. Ihre SeriesTechnik hat uns auf meinem Computer gekostet, während mein Ansatz weniger als 4 uns gekostet hat.
Chrisaycock
0

Wenn Sie einen Einzeiler von numpy möchten und sich keine Sorgen um die Leistung machen, versuchen Sie Folgendes:

np.sum(np.diag(the_array,1),0)[:-1]

Erläuterung: np.diag(the_array,1)Erstellt eine Matrix mit Ihrem Array einmalig in der Diagonale, np.sum(...,0)summiert die Matrix spaltenweise und verwendet ...[:-1]die Elemente, die der Größe des ursprünglichen Arrays entsprechen würden. Das Herumspielen mit den Parametern 1und :-1als kann zu Verschiebungen in verschiedene Richtungen führen.

Nathan Chappell
quelle
-2

Eine Möglichkeit, dies zu tun, ohne den Code in Fälle zu verschütten

mit Array:

def shift(arr, dx, default_value):
    result = np.empty_like(arr)
    get_neg_or_none = lambda s: s if s < 0 else None
    get_pos_or_none = lambda s: s if s > 0 else None
    result[get_neg_or_none(dx): get_pos_or_none(dx)] = default_value
    result[get_pos_or_none(dx): get_neg_or_none(dx)] = arr[get_pos_or_none(-dx): get_neg_or_none(-dx)]     
    return result

Mit Matrix kann es so gemacht werden:

def shift(image, dx, dy, default_value):
    res = np.full_like(image, default_value)

    get_neg_or_none = lambda s: s if s < 0 else None
    get_pos_or_none = lambda s : s if s > 0 else None

    res[get_pos_or_none(-dy): get_neg_or_none(-dy), get_pos_or_none(-dx): get_neg_or_none(-dx)] = \
        image[get_pos_or_none(dy): get_neg_or_none(dy), get_pos_or_none(dx): get_neg_or_none(dx)]
    return res
Alon Gweta
quelle
Das ist weder sauber noch schnell.
Chrisaycock