Gleitender Durchschnitt oder laufender Mittelwert

192

Gibt es eine SciPy-Funktion oder eine NumPy-Funktion oder ein Modul für Python, die den laufenden Mittelwert eines 1D-Arrays in einem bestimmten Fenster berechnet?

Shejo284
quelle

Antworten:

24

Für eine kurze, schnelle Lösung, die das Ganze in einer Schleife ohne Abhängigkeiten erledigt, funktioniert der folgende Code hervorragend.

mylist = [1, 2, 3, 4, 5, 6, 7]
N = 3
cumsum, moving_aves = [0], []

for i, x in enumerate(mylist, 1):
    cumsum.append(cumsum[i-1] + x)
    if i>=N:
        moving_ave = (cumsum[i] - cumsum[i-N])/N
        #can do stuff with moving_ave here
        moving_aves.append(moving_ave)
Aikude
quelle
44
Schnell?! Diese Lösung ist um Größenordnungen langsamer als die Lösungen mit Numpy.
Bart
3
Obwohl diese native Lösung cool ist, hat das OP nach einer Numpy / Scipy-Funktion gefragt - vermutlich werden diese erheblich schneller sein.
Demis
254

UPD: Alleo und jasaarim haben effizientere Lösungen vorgeschlagen .


Sie können dafür verwenden np.convolve:

np.convolve(x, np.ones((N,))/N, mode='valid')

Erläuterung

Der laufende Mittelwert ist ein Fall der mathematischen Operation der Faltung . Für den laufenden Mittelwert schieben Sie ein Fenster entlang der Eingabe und berechnen den Mittelwert des Fensterinhalts. Für diskrete 1D-Signale ist die Faltung dasselbe, außer dass Sie anstelle des Mittelwerts eine beliebige lineare Kombination berechnen, dh jedes Element mit einem entsprechenden Koeffizienten multiplizieren und die Ergebnisse addieren. Diese Koeffizienten, einer für jede Position in dem Fenster, sind manchmal die Faltung genannt Kernel . Das arithmetische Mittel der N-Werte ist (x_1 + x_2 + ... + x_N) / Nalso der entsprechende Kernel (1/N, 1/N, ..., 1/N), und genau das erhalten wir, wenn wir es verwenden np.ones((N,))/N.

Kanten

Das modeArgument von np.convolvegibt an, wie mit den Kanten umgegangen werden soll. Ich habe den validModus hier gewählt, weil ich denke, dass die meisten Leute erwarten, dass das Laufen so funktioniert, aber Sie haben möglicherweise andere Prioritäten. Hier ist ein Diagramm, das den Unterschied zwischen den Modi veranschaulicht:

import numpy as np
import matplotlib.pyplot as plt
modes = ['full', 'same', 'valid']
for m in modes:
    plt.plot(np.convolve(np.ones((200,)), np.ones((50,))/50, mode=m));
plt.axis([-10, 251, -.1, 1.1]);
plt.legend(modes, loc='lower center');
plt.show()

Laufende mittlere Faltungsmodi

Lapis
quelle
4
Ich mag diese Lösung, weil sie sauber (eine Zeile) und relativ effizient (Arbeit in Numpy) ist. Alleos "Effiziente Lösung" numpy.cumsumist jedoch komplexer.
Ulrich Stern
2
@denfromufa, ich glaube, die Dokumentation deckt die Implementierung gut genug ab und verweist auch auf Wikipedia, das die Mathematik erklärt. Denken Sie angesichts des Fokus der Frage, dass diese Antwort diese kopieren muss?
Lapis
@lapis Die Verwendung von Convolve für den gleitenden Durchschnitt ist ziemlich ungewöhnlich und nicht offensichtlich. Hier ist die beste visuelle Erklärung, die ich gefunden habe: matlabtricks.com/post-11/moving-average-by-convolution
denfromufa
Für das Plotten und verwandte Aufgaben wäre es hilfreich, es mit den Werten Keine zu füllen. Mein (nicht so schöner, aber kurzer) Vorschlag: `` `def move_average (x, N, fill = True): np.concatenate zurückgeben ([x für x in [[None] * (N // 2 + N% 2) * fill, np.convolve (x, np.ones ((N,)) / N, mode = 'valid'), [None] * (N // 2) * fill,] if len (x)]) ` `` Code sieht in SO-Kommentaren so hässlich aus xD Ich wollte keine weitere Antwort hinzufügen, da es so viele gab, aber Sie könnten ihn einfach kopieren und in Ihre IDE einfügen.
Chaoste
143

Effiziente Lösung

Faltung ist viel besser als einfacher Ansatz, aber (ich denke) sie verwendet FFT und ist daher ziemlich langsam. Speziell für die Berechnung des laufenden Mittelwerts funktioniert der folgende Ansatz jedoch einwandfrei

def running_mean(x, N):
    cumsum = numpy.cumsum(numpy.insert(x, 0, 0)) 
    return (cumsum[N:] - cumsum[:-N]) / float(N)

Der zu überprüfende Code

In[3]: x = numpy.random.random(100000)
In[4]: N = 1000
In[5]: %timeit result1 = numpy.convolve(x, numpy.ones((N,))/N, mode='valid')
10 loops, best of 3: 41.4 ms per loop
In[6]: %timeit result2 = running_mean(x, N)
1000 loops, best of 3: 1.04 ms per loop

Beachten Sie das numpy.allclose(result1, result2)istTrue , sind zwei Verfahren äquivalent. Je größer N, desto größer der Zeitunterschied.

Warnung: Obwohl Cumsum schneller ist, tritt ein erhöhter Gleitkommafehler auf, der dazu führen kann, dass Ihre Ergebnisse ungültig / falsch / inakzeptabel sind

Die Kommentare wiesen hier auf dieses Problem mit Gleitkommafehlern hin, aber ich mache es hier in der Antwort deutlicher. .

# demonstrate loss of precision with only 100,000 points
np.random.seed(42)
x = np.random.randn(100000)+1e6
y1 = running_mean_convolve(x, 10)
y2 = running_mean_cumsum(x, 10)
assert np.allclose(y1, y2, rtol=1e-12, atol=0)
  • Je mehr Punkte Sie sammeln, desto größer ist der Gleitkommafehler (1e5 Punkte sind also erkennbar, 1e6 Punkte sind wichtiger, mehr als 1e6 und Sie möchten möglicherweise die Akkumulatoren zurücksetzen).
  • Sie können mit betrügen np.longdouble aber Ihr Gleitkommafehler wird für eine relativ große Anzahl von Punkten immer noch signifikant (um> 1e5, hängt jedoch von Ihren Daten ab).
  • Sie können den Fehler zeichnen und sehen, wie er relativ schnell zunimmt
  • die Faltungslösung ist langsamer, weist jedoch keinen Gleitkomma-Genauigkeitsverlust auf
  • Die uniform_filter1d-Lösung ist schneller als diese Cumsum-Lösung UND weist diesen Gleitkomma-Genauigkeitsverlust nicht auf
Alleo
quelle
3
Schöne Lösung! Meine Vermutung ist numpy.convolveO (mn); In den Dokumenten wird erwähnt, dass scipy.signal.fftconvolveFFT verwendet wird.
Ulrich Stern
3
Diese Methode behandelt nicht die Kanten des Arrays, oder?
JoVe
6
Gute Lösung, aber beachten Sie, dass es bei großen Arrays zu numerischen Fehlern kommen kann, da Sie gegen Ende des Arrays möglicherweise zwei große Zahlen subtrahieren, um ein kleines Ergebnis zu erhalten.
Bas Swinckels
1
Dies verwendet eine Ganzzahldivision anstelle einer Floatdivision: running_mean([1,2,3], 2)gibt array([1, 2]). Ersetzen xdurch [float(value) for value in x]macht den Trick.
ChrisW
4
Die numerische Stabilität dieser Lösung kann zu einem Problem werden, wenn sie xSchwimmer enthält. Beispiel: running_mean(np.arange(int(1e7))[::-1] + 0.2, 1)[-1] - 0.2kehrt zurück, 0.003125während man erwartet 0.0. Weitere Informationen: en.wikipedia.org/wiki/Loss_of_significance
Mailand
79

Update: Das folgende Beispiel zeigt die alte pandas.rolling_meanFunktion, die in neueren Versionen von Pandas entfernt wurde. Ein modernes Äquivalent des folgenden Funktionsaufrufs wäre

In [8]: pd.Series(x).rolling(window=N).mean().iloc[N-1:].values
Out[8]: 
array([ 0.49815397,  0.49844183,  0.49840518, ...,  0.49488191,
        0.49456679,  0.49427121])

pandas ist dafür besser geeignet als NumPy oder SciPy. Seine Funktion rolling_mean erledigt die Arbeit bequem. Es gibt auch ein NumPy-Array zurück, wenn die Eingabe ein Array ist.

Es ist schwer, die rolling_meanLeistung mit einer benutzerdefinierten reinen Python-Implementierung zu übertreffen . Hier ist ein Beispiel für eine Leistung gegenüber zwei der vorgeschlagenen Lösungen:

In [1]: import numpy as np

In [2]: import pandas as pd

In [3]: def running_mean(x, N):
   ...:     cumsum = np.cumsum(np.insert(x, 0, 0)) 
   ...:     return (cumsum[N:] - cumsum[:-N]) / N
   ...:

In [4]: x = np.random.random(100000)

In [5]: N = 1000

In [6]: %timeit np.convolve(x, np.ones((N,))/N, mode='valid')
10 loops, best of 3: 172 ms per loop

In [7]: %timeit running_mean(x, N)
100 loops, best of 3: 6.72 ms per loop

In [8]: %timeit pd.rolling_mean(x, N)[N-1:]
100 loops, best of 3: 4.74 ms per loop

In [9]: np.allclose(pd.rolling_mean(x, N)[N-1:], running_mean(x, N))
Out[9]: True

Es gibt auch gute Möglichkeiten, mit den Kantenwerten umzugehen.

jasaarim
quelle
6
Das Pandas Rolling_Mean ist ein gutes Werkzeug für den Job, wurde jedoch für Ndarrays veraltet. In zukünftigen Pandas-Versionen wird es nur in Pandas-Serien funktionieren. Wohin wenden wir uns jetzt für Nicht-Pandas-Array-Daten?
Mike
5
@Mike rolling_mean () ist veraltet, aber jetzt können Sie rolling und mean separat verwenden: df.rolling(windowsize).mean()funktioniert jetzt stattdessen (sehr schnell, könnte ich hinzufügen). für 6000 Zeilenreihe %timeit test1.rolling(20).mean()zurück 1000 Schlaufen, am besten von 3: 1,16 ms pro Loop
Vlox
5
@Vlox df.rolling()funktioniert gut genug, das Problem ist, dass selbst dieses Formular ndarrays in Zukunft nicht mehr unterstützt. Um es zu verwenden, müssen wir zuerst unsere Daten in einen Pandas-Datenrahmen laden. Ich würde gerne sehen, dass diese Funktion entweder numpyoder hinzugefügt wird scipy.signal.
Mike
1
@ Mike stimme voll und ganz zu. Ich kämpfe insbesondere darum, die Geschwindigkeit von pandas .ewm (). Mean () für meine eigenen Arrays zu erreichen (anstatt sie zuerst in eine df laden zu müssen). Ich meine, es ist großartig, dass es schnell ist, aber es fühlt sich einfach etwas klobig an, zu oft in Datenrahmen hinein- und herauszukommen.
Vlox
6
%timeit bottleneck.move_mean(x, N)ist 3 bis 15 mal schneller als die Cumsum- und Pandas-Methoden auf meinem PC. Schauen Sie sich ihren Benchmark in der README des Repos an .
Mab
50

Sie können einen laufenden Mittelwert berechnen mit:

import numpy as np

def runningMean(x, N):
    y = np.zeros((len(x),))
    for ctr in range(len(x)):
         y[ctr] = np.sum(x[ctr:(ctr+N)])
    return y/N

Aber es ist langsam.

Glücklicherweise beinhaltet Numpy eine Faltung der wir die Dinge beschleunigen können. Der laufende Mittelwert entspricht der Faltung xmit einem Vektor, der Nlang ist und bei dem alle Mitglieder gleich sind 1/N. Die numpy-Implementierung von convolve enthält den Starttransienten, sodass Sie die ersten N-1-Punkte entfernen müssen:

def runningMeanFast(x, N):
    return np.convolve(x, np.ones((N,))/N)[(N-1):]

Auf meinem Computer ist die schnelle Version 20 bis 30 Mal schneller, abhängig von der Länge des Eingabevektors und der Größe des Mittelungsfensters.

Beachten Sie, dass Convolve einen 'same'Modus enthält, der das vorübergehende Startproblem zu beheben scheint, ihn jedoch zwischen Anfang und Ende aufteilt.

mtrw
quelle
Beachten Sie, dass das Entfernen der ersten N-1-Punkte in den letzten Punkten immer noch einen Randeffekt hinterlässt. Eine einfachere Möglichkeit, das Problem zu lösen, ist die Verwendung, mode='valid'bei convolveder keine Nachbearbeitung erforderlich ist.
Lapis
1
@Psycho - mode='valid'entfernt den Übergang von beiden Enden, richtig? Wenn len(x)=10und N=4, für einen laufenden Mittelwert würde ich 10 Ergebnisse wollen, aber valid7 zurückgeben.
mtrw
1
Es entfernt den Übergang vom Ende und der Anfang hat keinen. Nun, ich denke, es ist eine Frage der Prioritäten, ich brauche nicht die gleiche Anzahl von Ergebnissen auf Kosten einer Steigung gegen Null, die nicht in den Daten enthalten ist. Übrigens, hier ist ein Befehl, um den Unterschied zwischen den Modi modes = ('full', 'same', 'valid'); [plot(convolve(ones((200,)), ones((50,))/50, mode=m)) for m in modes]; axis([-10, 251, -.1, 1.1]); legend(modes, loc='lower center')anzuzeigen : (mit importiertem Pyplot und Numpy).
Lapis
runningMeanHabe ich Nebeneffekt der Mittelung mit Nullen, wenn Sie das Array mit x[ctr:(ctr+N)]für die rechte Seite des Arrays verlassen.
Mrgloom
runningMeanFasthaben auch dieses Randeffektproblem.
Mrgloom
21

oder Modul für Python, das berechnet

Bei meinen Tests bei Tradewave.net gewinnt TA-lib immer:

import talib as ta
import numpy as np
import pandas as pd
import scipy
from scipy import signal
import time as t

PAIR = info.primary_pair
PERIOD = 30

def initialize():
    storage.reset()
    storage.elapsed = storage.get('elapsed', [0,0,0,0,0,0])

def cumsum_sma(array, period):
    ret = np.cumsum(array, dtype=float)
    ret[period:] = ret[period:] - ret[:-period]
    return ret[period - 1:] / period

def pandas_sma(array, period):
    return pd.rolling_mean(array, period)

def api_sma(array, period):
    # this method is native to Tradewave and does NOT return an array
    return (data[PAIR].ma(PERIOD))

def talib_sma(array, period):
    return ta.MA(array, period)

def convolve_sma(array, period):
    return np.convolve(array, np.ones((period,))/period, mode='valid')

def fftconvolve_sma(array, period):    
    return scipy.signal.fftconvolve(
        array, np.ones((period,))/period, mode='valid')    

def tick():

    close = data[PAIR].warmup_period('close')

    t1 = t.time()
    sma_api = api_sma(close, PERIOD)
    t2 = t.time()
    sma_cumsum = cumsum_sma(close, PERIOD)
    t3 = t.time()
    sma_pandas = pandas_sma(close, PERIOD)
    t4 = t.time()
    sma_talib = talib_sma(close, PERIOD)
    t5 = t.time()
    sma_convolve = convolve_sma(close, PERIOD)
    t6 = t.time()
    sma_fftconvolve = fftconvolve_sma(close, PERIOD)
    t7 = t.time()

    storage.elapsed[-1] = storage.elapsed[-1] + t2-t1
    storage.elapsed[-2] = storage.elapsed[-2] + t3-t2
    storage.elapsed[-3] = storage.elapsed[-3] + t4-t3
    storage.elapsed[-4] = storage.elapsed[-4] + t5-t4
    storage.elapsed[-5] = storage.elapsed[-5] + t6-t5    
    storage.elapsed[-6] = storage.elapsed[-6] + t7-t6        

    plot('sma_api', sma_api)  
    plot('sma_cumsum', sma_cumsum[-5])
    plot('sma_pandas', sma_pandas[-10])
    plot('sma_talib', sma_talib[-15])
    plot('sma_convolve', sma_convolve[-20])    
    plot('sma_fftconvolve', sma_fftconvolve[-25])

def stop():

    log('ticks....: %s' % info.max_ticks)

    log('api......: %.5f' % storage.elapsed[-1])
    log('cumsum...: %.5f' % storage.elapsed[-2])
    log('pandas...: %.5f' % storage.elapsed[-3])
    log('talib....: %.5f' % storage.elapsed[-4])
    log('convolve.: %.5f' % storage.elapsed[-5])    
    log('fft......: %.5f' % storage.elapsed[-6])

Ergebnisse:

[2015-01-31 23:00:00] ticks....: 744
[2015-01-31 23:00:00] api......: 0.16445
[2015-01-31 23:00:00] cumsum...: 0.03189
[2015-01-31 23:00:00] pandas...: 0.03677
[2015-01-31 23:00:00] talib....: 0.00700  # <<< Winner!
[2015-01-31 23:00:00] convolve.: 0.04871
[2015-01-31 23:00:00] fft......: 0.22306

Geben Sie hier die Bildbeschreibung ein

Präsenz
quelle
NameError: name 'info' is not defined. Ich erhalte diesen Fehler, Sir.
Md. Rezwanul Haque
1
Sieht so aus, als würden Ihre Zeitreihen nach dem Glätten verschoben. Ist dies der gewünschte Effekt?
Mrgloom
@mrgloom ja, zu Visualisierungszwecken; Andernfalls würden sie als eine Linie im Diagramm angezeigt. Md. Rezwanul Haque Sie könnten alle Verweise auf PAAR und Info entfernen; Dies waren interne Sandbox-Methoden für die inzwischen aufgelöste tradewave.net
litepresence
21

Eine sofort einsatzbereite Lösung finden Sie unter https://scipy-cookbook.readthedocs.io/items/SignalSmooth.html . Es liefert den laufenden Durchschnitt mit dem flatFenstertyp. Beachten Sie, dass dies etwas ausgefeilter ist als die einfache Do-it-yourself-Convolve-Methode, da versucht wird, die Probleme am Anfang und am Ende der Daten durch Reflektion zu behandeln (was in Ihrem Fall möglicherweise funktioniert oder nicht). ..).

Zunächst könnten Sie versuchen:

a = np.random.random(100)
plt.plot(a)
b = smooth(a, window='flat')
plt.plot(b)
Hansemann
quelle
1
Diese Methode beruht auf numpy.convolvedem Unterschied nur in der Änderung der Reihenfolge.
Alleo
10
Ich ärgere mich immer über die Signalverarbeitungsfunktion, die Ausgangssignale mit einer anderen Form als die Eingangssignale zurückgibt, wenn sowohl die Ein- als auch die Ausgänge gleich sind (z. B. beide zeitlichen Signale). Es unterbricht die Entsprechung mit der zugehörigen unabhängigen Variablen (z. B. Zeit, Häufigkeit), sodass das Zeichnen oder Vergleichen keine direkte Angelegenheit ist. Wenn Sie das Gefühl teilen, möchten Sie möglicherweise die letzten Zeilen der vorgeschlagenen Funktion als y = np ändern .convolve (w / w.sum (), s, mode = 'same'); return y [window_len-1 :-( window_len-1)]
Christian O'Reilly
@ ChristianO'Reilly, du solltest das als separate Antwort posten - genau das habe ich gesucht, da ich tatsächlich zwei andere Arrays habe, die mit den Längen der geglätteten Daten übereinstimmen müssen, um zu zeichnen usw. Ich würde gerne wissen genau wie du das gemacht hast - ist wdie Fenstergröße und sdie Daten?
Demis
@ Demis Ich bin froh, dass der Kommentar geholfen hat. Weitere Informationen zur Numpy- Faltungsfunktion finden Sie hier docs.scipy.org/doc/numpy-1.15.0/reference/generated/… Eine Faltungsfunktion ( en.wikipedia.org/wiki/Convolution ) faltet zwei Signale miteinander. In diesem Fall werden Ihre Signale mit einem normalisierten Fenster (dh einem einheitlichen Bereich) (w / w.sum ()) gefaltet.
Christian O'Reilly
18

Sie können scipy.ndimage.filters.uniform_filter1d verwenden :

import numpy as np
from scipy.ndimage.filters import uniform_filter1d
N = 1000
x = np.random.random(100000)
y = uniform_filter1d(x, size=N)

uniform_filter1d::

  • gibt die Ausgabe mit der gleichen numpy Form (dh Anzahl der Punkte)
  • ermöglicht mehrere Möglichkeiten, um den Rand zu behandeln, wo 'reflect'der Standard ist, aber in meinem Fall wollte ich lieber'nearest'

Es ist auch ziemlich schnell (fast 50-mal schneller als np.convolveund 2-5-mal schneller als der oben angegebene Cumsum-Ansatz ):

%timeit y1 = np.convolve(x, np.ones((N,))/N, mode='same')
100 loops, best of 3: 9.28 ms per loop

%timeit y2 = uniform_filter1d(x, size=N)
10000 loops, best of 3: 191 µs per loop

Hier sind 3 Funktionen, mit denen Sie Fehler / Geschwindigkeit verschiedener Implementierungen vergleichen können:

from __future__ import division
import numpy as np
import scipy.ndimage.filters as ndif
def running_mean_convolve(x, N):
    return np.convolve(x, np.ones(N) / float(N), 'valid')
def running_mean_cumsum(x, N):
    cumsum = np.cumsum(np.insert(x, 0, 0))
    return (cumsum[N:] - cumsum[:-N]) / float(N)
def running_mean_uniform_filter1d(x, N):
    return ndif.uniform_filter1d(x, N, mode='constant', origin=-(N//2))[:-(N-1)]
moi
quelle
1
Dies ist die einzige Antwort, die die Grenzprobleme zu berücksichtigen scheint (ziemlich wichtig, insbesondere beim Plotten). Danke dir!
Gabriel
1
i Profil uniform_filter1d, np.convolvemit einem Rechteck und np.cumsumanschließend np.subtract. Meine Ergebnisse: (1.) Faltung ist die langsamste. (2.) Cumsum / Subtrahieren ist ungefähr 20-30x schneller. (3.) uniform_filter1d ist ungefähr 2-3x schneller als cumsum / subtrahieren. Gewinner ist definitiv uniform_filter1d.
Trevor Boyd Smith
Die Verwendung uniform_filter1dist schneller als die cumsumLösung (um etwa 2-5x). und uniform_filter1d erhält keinen massiven Gleitkommafehler wie diecumsum Lösung.
Trevor Boyd Smith
15

Ich weiß, dass dies eine alte Frage ist, aber hier ist eine Lösung, die keine zusätzlichen Datenstrukturen oder Bibliotheken verwendet. Die Anzahl der Elemente in der Eingabeliste ist linear, und ich kann mir keinen anderen Weg vorstellen, um sie effizienter zu gestalten (wenn jemand einen besseren Weg zur Zuordnung des Ergebnisses kennt, lassen Sie es mich bitte wissen).

HINWEIS: Dies wäre viel schneller, wenn ein Numpy-Array anstelle einer Liste verwendet würde, aber ich wollte alle Abhängigkeiten beseitigen. Es wäre auch möglich, die Leistung durch Multithread-Ausführung zu verbessern

Die Funktion setzt voraus, dass die Eingabeliste eindimensional ist. Seien Sie also vorsichtig.

### Running mean/Moving average
def running_mean(l, N):
    sum = 0
    result = list( 0 for x in l)

    for i in range( 0, N ):
        sum = sum + l[i]
        result[i] = sum / (i+1)

    for i in range( N, len(l) ):
        sum = sum - l[i-N] + l[i]
        result[i] = sum / N

    return result

Beispiel

Angenommen, wir haben eine Liste, data = [ 1, 2, 3, 4, 5, 6 ]für die wir einen gleitenden Mittelwert mit einer Periode von 3 berechnen möchten, und Sie möchten auch eine Ausgabeliste, die dieselbe Größe wie die Eingabeliste hat (dies ist meistens der Fall).

Das erste Element hat den Index 0, daher sollte der rollierende Mittelwert für die Elemente Index -2, -1 und 0 berechnet werden. Offensichtlich haben wir keine Daten [-2] und Daten [-1] (es sei denn, Sie möchten spezielle verwenden Randbedingungen), daher nehmen wir an, dass diese Elemente 0 sind. Dies entspricht dem Auffüllen der Liste mit Null, außer wir füllen sie nicht auf, sondern verfolgen nur die Indizes, die aufgefüllt werden müssen (von 0 bis N-1).

Für die ersten N Elemente addieren wir also immer wieder die Elemente in einem Akkumulator.

result[0] = (0 + 0 + 1) / 3  = 0.333    ==   (sum + 1) / 3
result[1] = (0 + 1 + 2) / 3  = 1        ==   (sum + 2) / 3
result[2] = (1 + 2 + 3) / 3  = 2        ==   (sum + 3) / 3

Ab den Elementen N + 1 funktioniert eine einfache Akkumulation nicht. Wir erwarten, result[3] = (2 + 3 + 4)/3 = 3aber das ist anders als (sum + 4)/3 = 3.333.

Die Art und Weise des richtigen Wert zu berechnen ist zu subtrahieren data[0] = 1aus sum+4, so geben sum + 4 - 1 = 9.

Dies geschieht, weil derzeit sum = data[0] + data[1] + data[2], aber es gilt auch für jeden, i >= Nweil vor der Subtraktion sumist data[i-N] + ... + data[i-2] + data[i-1].

NeXuS
quelle
12

Ich bin der Meinung, dass dies durch Engpässe elegant gelöst werden kann

Siehe Basisbeispiel unten:

import numpy as np
import bottleneck as bn

a = np.random.randint(4, 1000, size=100)
mm = bn.move_mean(a, window=5, min_count=1)
  • "mm" ist das gleitende Mittel für "a".

  • "Fenster" ist die maximale Anzahl von Einträgen, die für den gleitenden Mittelwert berücksichtigt werden müssen.

  • "min_count" ist die minimale Anzahl von Einträgen, die für den gleitenden Mittelwert berücksichtigt werden müssen (z. B. für die ersten paar Elemente oder wenn das Array Nanowerte hat).

Der gute Teil ist, dass Engpass beim Umgang mit Nanowerten hilft und auch sehr effizient ist.

Anthony Anyanwu
quelle
Diese Bibliothek ist sehr schnell. Die reine gleitende Python-Durchschnittsfunktion ist langsam. Bootleneck ist eine PyData-Bibliothek, die meiner Meinung nach stabil ist und von der Python-Community kontinuierlich unterstützt werden kann. Warum also nicht?
GoingMyWay
6

Ich habe noch nicht überprüft, wie schnell dies ist, aber Sie könnten versuchen:

from collections import deque

cache = deque() # keep track of seen values
n = 10          # window size
A = xrange(100) # some dummy iterable
cum_sum = 0     # initialize cumulative sum

for t, val in enumerate(A, 1):
    cache.append(val)
    cum_sum += val
    if t < n:
        avg = cum_sum / float(t)
    else:                           # if window is saturated,
        cum_sum -= cache.popleft()  # subtract oldest value
        avg = cum_sum / float(n)
Kris
quelle
1
Das wollte ich tun. Kann jemand bitte kritisieren, warum dies ein schlechter Weg ist?
Staggart
1
Diese einfache Python-Lösung hat bei mir gut funktioniert, ohne dass Numpy erforderlich war. Am Ende rollte ich es in eine Klasse zur Wiederverwendung.
Matthew Tschiegg
6

Diese Antwort enthält Lösungen, die die Python- Standardbibliothek für drei verschiedene Szenarien verwenden.


Laufender Durchschnitt mit itertools.accumulate

Dies ist eine speichereffiziente Python 3.2+ -Lösung, die den laufenden Durchschnitt über eine iterierbare Anzahl von Werten durch Nutzung berechnet itertools.accumulate.

>>> from itertools import accumulate
>>> values = range(100)

Beachten Sie, dass valuesdies beliebig iterierbar sein kann, einschließlich Generatoren oder anderer Objekte, die im laufenden Betrieb Werte erzeugen.

Konstruieren Sie zunächst träge die kumulative Summe der Werte.

>>> cumu_sum = accumulate(value_stream)

Als nächstes enumeratedie kumulative Summe (beginnend bei 1) und konstruieren Sie einen Generator, der den Bruchteil der akkumulierten Werte und den aktuellen Aufzählungsindex liefert.

>>> rolling_avg = (accu/i for i, accu in enumerate(cumu_sum, 1))

Sie können Probleme haben, means = list(rolling_avg)wenn Sie alle Werte gleichzeitig im Speicher benötigen oder nextinkrementell aufrufen .
(Natürlich können Sie auch iterierenrolling_avg mit einer forSchleife , die nextimplizit aufgerufen wird .)

>>> next(rolling_avg) # 0/1
>>> 0.0
>>> next(rolling_avg) # (0 + 1)/2
>>> 0.5
>>> next(rolling_avg) # (0 + 1 + 2)/3
>>> 1.0

Diese Lösung kann wie folgt als Funktion geschrieben werden.

from itertools import accumulate

def rolling_avg(iterable):
    cumu_sum = accumulate(iterable)
    yield from (accu/i for i, accu in enumerate(cumu_sum, 1))
    

Eine Coroutine, an die Sie jederzeit Werte senden können

Diese Coroutine verwendet die von Ihnen gesendeten Werte und führt einen laufenden Durchschnitt der bisher angezeigten Werte.

Dies ist nützlich, wenn Sie keine iterierbaren Werte haben, sondern die zu ermittelenden Werte zu unterschiedlichen Zeiten während des gesamten Programmlebens einzeln erfassen.

def rolling_avg_coro():
    i = 0
    total = 0.0
    avg = None

    while True:
        next_value = yield avg
        i += 1
        total += next_value
        avg = total/i
        

Die Coroutine funktioniert folgendermaßen:

>>> averager = rolling_avg_coro() # instantiate coroutine
>>> next(averager) # get coroutine going (this is called priming)
>>>
>>> averager.send(5) # 5/1
>>> 5.0
>>> averager.send(3) # (5 + 3)/2
>>> 4.0
>>> print('doing something else...')
doing something else...
>>> averager.send(13) # (5 + 3 + 13)/3
>>> 7.0

Berechnung des Durchschnitts über ein Schiebefenster von Größe N

Diese Generatorfunktion nimmt eine iterierbare und eine Fenstergröße an N und liefert den Durchschnitt über die aktuellen Werte innerhalb des Fensters. Es dequewird eine Datenstruktur verwendet , die einer Liste ähnelt, jedoch für schnelle Änderungen ( pop, append) an beiden Endpunkten optimiert ist .

from collections import deque
from itertools import islice

def sliding_avg(iterable, N):        
    it = iter(iterable)
    window = deque(islice(it, N))        
    num_vals = len(window)

    if num_vals < N:
        msg = 'window size {} exceeds total number of values {}'
        raise ValueError(msg.format(N, num_vals))

    N = float(N) # force floating point division if using Python 2
    s = sum(window)
    
    while True:
        yield s/N
        try:
            nxt = next(it)
        except StopIteration:
            break
        s = s - window.popleft() + nxt
        window.append(nxt)
        

Hier ist die Funktion in Aktion:

>>> values = range(100)
>>> N = 5
>>> window_avg = sliding_avg(values, N)
>>> 
>>> next(window_avg) # (0 + 1 + 2 + 3 + 4)/5
>>> 2.0
>>> next(window_avg) # (1 + 2 + 3 + 4 + 5)/5
>>> 3.0
>>> next(window_avg) # (2 + 3 + 4 + 5 + 6)/5
>>> 4.0
timgeb
quelle
5

Ein bisschen spät zur Party, aber ich habe meine eigene kleine Funktion gemacht, die sich NICHT um die Enden oder Pads mit Nullen wickelt, die dann auch verwendet werden, um den Durchschnitt zu finden. Ein weiterer Vorteil ist, dass das Signal auch an linear beabstandeten Punkten erneut abgetastet wird. Passen Sie den Code nach Belieben an, um weitere Funktionen zu erhalten.

Die Methode ist eine einfache Matrixmultiplikation mit einem normalisierten Gaußschen Kernel.

def running_mean(y_in, x_in, N_out=101, sigma=1):
    '''
    Returns running mean as a Bell-curve weighted average at evenly spaced
    points. Does NOT wrap signal around, or pad with zeros.

    Arguments:
    y_in -- y values, the values to be smoothed and re-sampled
    x_in -- x values for array

    Keyword arguments:
    N_out -- NoOf elements in resampled array.
    sigma -- 'Width' of Bell-curve in units of param x .
    '''
    N_in = size(y_in)

    # Gaussian kernel
    x_out = np.linspace(np.min(x_in), np.max(x_in), N_out)
    x_in_mesh, x_out_mesh = np.meshgrid(x_in, x_out)
    gauss_kernel = np.exp(-np.square(x_in_mesh - x_out_mesh) / (2 * sigma**2))
    # Normalize kernel, such that the sum is one along axis 1
    normalization = np.tile(np.reshape(sum(gauss_kernel, axis=1), (N_out, 1)), (1, N_in))
    gauss_kernel_normalized = gauss_kernel / normalization
    # Perform running average as a linear operation
    y_out = gauss_kernel_normalized @ y_in

    return y_out, x_out

Eine einfache Verwendung eines sinusförmigen Signals mit zusätzlichem normalverteilten Rauschen: Geben Sie hier die Bildbeschreibung ein

Clausen
quelle
Dies funktioniert bei mir nicht (Python 3.6). 1 Stattdessen wird keine Funktion benannt sum. 2 Der Operator (keine Ahnung, was das ist) gibt einen Fehler aus. Ich kann es später untersuchen, aber mir fehlt gerade die Zeitnp.sum@
Bastian
Das @ist die Matrix Multiplikationsoperator , der Arbeitsgeräte np.matmul . Überprüfen Sie, ob Ihr y_inArray ein Numpy-Array ist. Dies könnte das Problem sein.
xyzzyqed
5

Anstelle von Numpy oder Scipy würde ich Pandas empfehlen, dies schneller zu tun:

df['data'].rolling(3).mean()

Dies nimmt den gleitenden Durchschnitt (MA) von 3 Perioden der Spalte "Daten". Sie können auch die verschobenen Versionen berechnen. Beispielsweise kann die Version, die die aktuelle Zelle ausschließt (eine zurück verschoben), einfach wie folgt berechnet werden:

df['data'].shift(periods=1).rolling(3).mean()
Gursel Karacor
quelle
Wie unterscheidet sich dies von der 2016 vorgeschlagenen Lösung ?
Herr T
2
Die 2016 vorgeschlagene Lösung verwendet, pandas.rolling_meanwährend meine verwendet pandas.DataFrame.rolling. Sie können auch die Bewegung min(), max(), sum()usw. sowie mean()mit dieser Methode einfach berechnen.
Gursel Karacor
Im ersteren müssen Sie eine andere Methode wie pandas.rolling_min, pandas.rolling_maxusw. verwenden. Sie sind ähnlich, aber unterschiedlich.
Gursel Karacor
4

In einer der obigen Antworten ist ein Kommentar von mab vergraben, der diese Methode enthält. hat was ist ein einfacher gleitender Durchschnitt:bottleneckmove_mean

import numpy as np
import bottleneck as bn

a = np.arange(10) + np.random.random(10)

mva = bn.move_mean(a, window=2, min_count=1)

min_countist ein praktischer Parameter, mit dem der gleitende Durchschnitt bis zu diesem Punkt in Ihrem Array berechnet wird. Wenn Sie nicht setzen min_count, wird es gleich sein windowund alles bis zu windowPunkten wird sein nan.

Worte dafür
quelle
3

Ein anderer Ansatz, um einen gleitenden Durchschnitt zu finden, ohne Numpy zu verwenden, Panda

import itertools
sample = [2, 6, 10, 8, 11, 10]
list(itertools.starmap(lambda a,b: b/a, 
               enumerate(itertools.accumulate(sample), 1)))

druckt [2.0, 4.0, 6.0, 6.5, 7.4, 7.833333333333333]

DmitrySemenov
quelle
itertools.accumulate existiert nicht in Python 2.7, aber in Python 3.4
Grayaii
3

Diese Frage ist jetzt noch älter als als NeXuS letzten Monat darüber schrieb, ABER ich mag, wie sein Code mit Randfällen umgeht. Da es sich jedoch um einen "einfachen gleitenden Durchschnitt" handelt, bleiben seine Ergebnisse hinter den Daten zurück, für die sie gelten. Ich dachte , dass in einer befriedigenden Weise als NumPy der Modi mit Randfällen zu tun valid, sameund fullkonnte durch Anwendung einen ähnlichen Ansatzes zu einem erreicht werden convolution()basierter Methode.

In meinem Beitrag wird ein zentraler laufender Durchschnitt verwendet, um die Ergebnisse an den Daten auszurichten. Wenn zu wenige Punkte verfügbar sind, um das Fenster in voller Größe zu verwenden, werden laufende Durchschnittswerte aus sukzessive kleineren Fenstern an den Rändern des Arrays berechnet. [Eigentlich aus immer größeren Fenstern, aber das ist ein Implementierungsdetail.]

import numpy as np

def running_mean(l, N):
    # Also works for the(strictly invalid) cases when N is even.
    if (N//2)*2 == N:
        N = N - 1
    front = np.zeros(N//2)
    back = np.zeros(N//2)

    for i in range(1, (N//2)*2, 2):
        front[i//2] = np.convolve(l[:i], np.ones((i,))/i, mode = 'valid')
    for i in range(1, (N//2)*2, 2):
        back[i//2] = np.convolve(l[-i:], np.ones((i,))/i, mode = 'valid')
    return np.concatenate([front, np.convolve(l, np.ones((N,))/N, mode = 'valid'), back[::-1]])

Es ist relativ langsam, weil es verwendet convolve()wird und wahrscheinlich von einem echten Pythonisten ziemlich aufgepeppt werden könnte, aber ich glaube, dass die Idee steht.

Marisano
quelle
3

Es gibt oben viele Antworten zur Berechnung eines laufenden Mittelwerts. Meine Antwort fügt zwei zusätzliche Funktionen hinzu:

  1. ignoriert nan Werte
  2. berechnet den Mittelwert für die N Nachbarwerte NICHT einschließlich des interessierenden Wertes selbst

Diese zweite Funktion ist besonders nützlich, um zu bestimmen, welche Werte um einen bestimmten Betrag vom allgemeinen Trend abweichen.

Ich benutze numpy.cumsum, da es die zeiteffizienteste Methode ist ( siehe Alleos Antwort oben ).

N=10 # number of points to test on each side of point of interest, best if even
padded_x = np.insert(np.insert( np.insert(x, len(x), np.empty(int(N/2))*np.nan), 0, np.empty(int(N/2))*np.nan ),0,0)
n_nan = np.cumsum(np.isnan(padded_x))
cumsum = np.nancumsum(padded_x) 
window_sum = cumsum[N+1:] - cumsum[:-(N+1)] - x # subtract value of interest from sum of all values within window
window_n_nan = n_nan[N+1:] - n_nan[:-(N+1)] - np.isnan(x)
window_n_values = (N - window_n_nan)
movavg = (window_sum) / (window_n_values)

Dieser Code funktioniert nur für Ns. Sie kann für ungerade Zahlen angepasst werden, indem Sie den np.insert von padded_x und n_nan ändern.

Beispielausgabe (roh in schwarz, movavg in blau): Rohdaten (schwarz) und gleitender Durchschnitt (blau) von 10 Punkten um jeden Wert, ohne diesen Wert.  Nanowerte werden ignoriert.

Dieser Code kann leicht angepasst werden, um alle gleitenden Durchschnittswerte zu entfernen, die aus weniger als Cutoff = 3 Nicht-Nan-Werten berechnet wurden.

window_n_values = (N - window_n_nan).astype(float) # dtype must be float to set some values to nan
cutoff = 3
window_n_values[window_n_values<cutoff] = np.nan
movavg = (window_sum) / (window_n_values)

Rohdaten (schwarz) und gleitender Durchschnitt (blau), während Fenster mit weniger als 3 Nicht-Nan-Werten ignoriert werden

gtcoder
quelle
2

Nur Python-Standardbibliothek verwenden (speichereffizient)

Geben Sie einfach eine andere Version der Verwendung nur der Standardbibliothek an deque. Es ist eine ziemliche Überraschung für mich, dass die meisten Antworten pandasoder verwenden numpy.

def moving_average(iterable, n=3):
    d = deque(maxlen=n)
    for i in iterable:
        d.append(i)
        if len(d) == n:
            yield sum(d)/n

r = moving_average([40, 30, 50, 46, 39, 44])
assert list(r) == [40.0, 42.0, 45.0, 43.0]

Eigentlich habe ich eine andere Implementierung in Python-Dokumenten gefunden

def moving_average(iterable, n=3):
    # moving_average([40, 30, 50, 46, 39, 44]) --> 40.0 42.0 45.0 43.0
    # http://en.wikipedia.org/wiki/Moving_average
    it = iter(iterable)
    d = deque(itertools.islice(it, n-1))
    d.appendleft(0)
    s = sum(d)
    for elem in it:
        s += elem - d.popleft()
        d.append(elem)
        yield s / n

Die Implementierung scheint mir jedoch etwas komplexer zu sein, als es sein sollte. Aber es muss aus einem bestimmten Grund in den Standard-Python-Dokumenten enthalten sein. Könnte jemand die Implementierung von mir und dem Standard-Dokument kommentieren?

MaThMaX
quelle
2
Ein großer Unterschied besteht darin, dass Sie die Fensterelemente bei jeder Iteration summieren und die Summe effizient aktualisieren (entfernen Sie ein Element und fügen Sie ein anderes hinzu). In Bezug auf die Komplexität führen Sie O(n*d) Berechnungen durch ( dnO(n)
dh
@Iftah, nett, danke für die Erklärung, du hast recht.
MaThMaX
2

Mit den Variablen von @ Aikude habe ich einen Einzeiler geschrieben.

import numpy as np

mylist = [1, 2, 3, 4, 5, 6, 7]
N = 3

mean = [np.mean(mylist[x:x+N]) for x in range(len(mylist)-N+1)]
print(mean)

>>> [2.0, 3.0, 4.0, 5.0, 6.0]
greentec
quelle
1

Obwohl es hier Lösungen für diese Frage gibt, werfen Sie bitte einen Blick auf meine Lösung. Es ist sehr einfach und funktioniert gut.

import numpy as np
dataset = np.asarray([1, 2, 3, 4, 5, 6, 7])
ma = list()
window = 3
for t in range(0, len(dataset)):
    if t+window <= len(dataset):
        indices = range(t, t+window)
        ma.append(np.average(np.take(dataset, indices)))
else:
    ma = np.asarray(ma)
Ayberk Yavuz
quelle
1

Nach dem Lesen der anderen Antworten glaube ich nicht, dass dies die Frage ist, aber ich bin hierher gekommen, um einen laufenden Durchschnitt einer Liste von Werten zu führen, deren Größe zugenommen hat.

Wenn Sie also eine Liste der Werte, die Sie von einem Ort (einem Standort, einem Messgerät usw.) erhalten, und den Durchschnitt der zuletzt naktualisierten Werte aufbewahren möchten, können Sie den folgenden Code verwenden, der den Aufwand für das Hinzufügen neuer Werte minimiert Elemente:

class Running_Average(object):
    def __init__(self, buffer_size=10):
        """
        Create a new Running_Average object.

        This object allows the efficient calculation of the average of the last
        `buffer_size` numbers added to it.

        Examples
        --------
        >>> a = Running_Average(2)
        >>> a.add(1)
        >>> a.get()
        1.0
        >>> a.add(1)  # there are two 1 in buffer
        >>> a.get()
        1.0
        >>> a.add(2)  # there's a 1 and a 2 in the buffer
        >>> a.get()
        1.5
        >>> a.add(2)
        >>> a.get()  # now there's only two 2 in the buffer
        2.0
        """
        self._buffer_size = int(buffer_size)  # make sure it's an int
        self.reset()

    def add(self, new):
        """
        Add a new number to the buffer, or replaces the oldest one there.
        """
        new = float(new)  # make sure it's a float
        n = len(self._buffer)
        if n < self.buffer_size:  # still have to had numbers to the buffer.
            self._buffer.append(new)
            if self._average != self._average:  # ~ if isNaN().
                self._average = new  # no previous numbers, so it's new.
            else:
                self._average *= n  # so it's only the sum of numbers.
                self._average += new  # add new number.
                self._average /= (n+1)  # divide by new number of numbers.
        else:  # buffer full, replace oldest value.
            old = self._buffer[self._index]  # the previous oldest number.
            self._buffer[self._index] = new  # replace with new one.
            self._index += 1  # update the index and make sure it's...
            self._index %= self.buffer_size  # ... smaller than buffer_size.
            self._average -= old/self.buffer_size  # remove old one...
            self._average += new/self.buffer_size  # ...and add new one...
            # ... weighted by the number of elements.

    def __call__(self):
        """
        Return the moving average value, for the lazy ones who don't want
        to write .get .
        """
        return self._average

    def get(self):
        """
        Return the moving average value.
        """
        return self()

    def reset(self):
        """
        Reset the moving average.

        If for some reason you don't want to just create a new one.
        """
        self._buffer = []  # could use np.empty(self.buffer_size)...
        self._index = 0  # and use this to keep track of how many numbers.
        self._average = float('nan')  # could use np.NaN .

    def get_buffer_size(self):
        """
        Return current buffer_size.
        """
        return self._buffer_size

    def set_buffer_size(self, buffer_size):
        """
        >>> a = Running_Average(10)
        >>> for i in range(15):
        ...     a.add(i)
        ...
        >>> a()
        9.5
        >>> a._buffer  # should not access this!!
        [10.0, 11.0, 12.0, 13.0, 14.0, 5.0, 6.0, 7.0, 8.0, 9.0]

        Decreasing buffer size:
        >>> a.buffer_size = 6
        >>> a._buffer  # should not access this!!
        [9.0, 10.0, 11.0, 12.0, 13.0, 14.0]
        >>> a.buffer_size = 2
        >>> a._buffer
        [13.0, 14.0]

        Increasing buffer size:
        >>> a.buffer_size = 5
        Warning: no older data available!
        >>> a._buffer
        [13.0, 14.0]

        Keeping buffer size:
        >>> a = Running_Average(10)
        >>> for i in range(15):
        ...     a.add(i)
        ...
        >>> a()
        9.5
        >>> a._buffer  # should not access this!!
        [10.0, 11.0, 12.0, 13.0, 14.0, 5.0, 6.0, 7.0, 8.0, 9.0]
        >>> a.buffer_size = 10  # reorders buffer!
        >>> a._buffer
        [5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0]
        """
        buffer_size = int(buffer_size)
        # order the buffer so index is zero again:
        new_buffer = self._buffer[self._index:]
        new_buffer.extend(self._buffer[:self._index])
        self._index = 0
        if self._buffer_size < buffer_size:
            print('Warning: no older data available!')  # should use Warnings!
        else:
            diff = self._buffer_size - buffer_size
            print(diff)
            new_buffer = new_buffer[diff:]
        self._buffer_size = buffer_size
        self._buffer = new_buffer

    buffer_size = property(get_buffer_size, set_buffer_size)

Und Sie können es zum Beispiel testen mit:

def graph_test(N=200):
    import matplotlib.pyplot as plt
    values = list(range(N))
    values_average_calculator = Running_Average(N/2)
    values_averages = []
    for value in values:
        values_average_calculator.add(value)
        values_averages.append(values_average_calculator())
    fig, ax = plt.subplots(1, 1)
    ax.plot(values, label='values')
    ax.plot(values_averages, label='averages')
    ax.grid()
    ax.set_xlim(0, N)
    ax.set_ylim(0, N)
    fig.show()

Welches gibt:

Werte und deren Durchschnitt als Funktion der Werte #

berna1111
quelle
1

Eine andere Lösung, die nur eine Standardbibliothek und eine Deque verwendet:

from collections import deque
import itertools

def moving_average(iterable, n=3):
    # http://en.wikipedia.org/wiki/Moving_average
    it = iter(iterable) 
    # create an iterable object from input argument
    d = deque(itertools.islice(it, n-1))  
    # create deque object by slicing iterable
    d.appendleft(0)
    s = sum(d)
    for elem in it:
        s += elem - d.popleft()
        d.append(elem)
        yield s / n

# example on how to use it
for i in  moving_average([40, 30, 50, 46, 39, 44]):
    print(i)

# 40.0
# 42.0
# 45.0
# 43.0
Vlad Bezden
quelle
1

Lassen Sie mich zu Bildungszwecken zwei weitere Numpy-Lösungen hinzufügen (die langsamer als die Cumsum-Lösung sind):

import numpy as np
from numpy.lib.stride_tricks import as_strided

def ra_strides(arr, window):
    ''' Running average using as_strided'''
    n = arr.shape[0] - window + 1
    arr_strided = as_strided(arr, shape=[n, window], strides=2*arr.strides)
    return arr_strided.mean(axis=1)

def ra_add(arr, window):
    ''' Running average using add.reduceat'''
    n = arr.shape[0] - window + 1
    indices = np.array([0, window]*n) + np.repeat(np.arange(n), 2)
    arr = np.append(arr, 0)
    return np.add.reduceat(arr, indices )[::2]/window

Verwendete Funktionen: as_strided , add.reduceat

Andreas K.
quelle
1

Alle oben genannten Lösungen sind schlecht, weil sie fehlen

  • Geschwindigkeit aufgrund einer nativen Python anstelle einer numpy vektorisierten Implementierung,
  • numerische Stabilität aufgrund schlechter Verwendung von numpy.cumsum, oder
  • Geschwindigkeit aufgrund von O(len(x) * w)Implementierungen als Windungen.

Gegeben

import numpy
m = 10000
x = numpy.random.rand(m)
w = 1000

Beachten Sie, dass x_[:w].sum()gleich ist x[:w-1].sum(). Für den ersten Durchschnitt numpy.cumsum(...)addiert x[w] / w( x_[w+1] / wsubtrahiert) und subtrahiert 0(von x_[0] / w). Das führt zux[0:w].mean()

Via cumsum, werden Sie die zweite durchschnittliche aktualisieren , indem Sie zusätzlich hinzufügen x[w+1] / wund subtrahieren x[0] / w, was zu x[1:w+1].mean().

Dies geht so lange weiter, bis x[-w:].mean()es erreicht ist.

x_ = numpy.insert(x, 0, 0)
sliding_average = x_[:w].sum() / w + numpy.cumsum(x_[w:] - x_[:-w]) / w

Diese Lösung ist vektorisiert O(m), lesbar und numerisch stabil.

Herbert
quelle
1

Wie wäre es mit einem gleitenden Durchschnittsfilter ? Es ist auch ein Einzeiler und hat den Vorteil, dass Sie den Fenstertyp leicht manipulieren können, wenn Sie etwas anderes als das Rechteck benötigen, dh. ein N-langer einfacher gleitender Durchschnitt eines Arrays a:

lfilter(np.ones(N)/N, [1], a)[N:]

Und mit dem angewendeten dreieckigen Fenster:

lfilter(np.ones(N)*scipy.signal.triang(N)/N, [1], a)[N:]

Hinweis: Normalerweise verwerfe ich die ersten N Proben als Fälschung, daher [N:]am Ende, aber es ist nicht notwendig und es handelt sich nur um eine persönliche Entscheidung.

mac13k
quelle
-7

Wenn Sie sich dafür entscheiden, Ihre eigene zu rollen, anstatt eine vorhandene Bibliothek zu verwenden, sollten Sie sich des Gleitkommafehlers bewusst sein und versuchen, dessen Auswirkungen zu minimieren:

class SumAccumulator:
    def __init__(self):
        self.values = [0]
        self.count = 0

    def add( self, val ):
        self.values.append( val )
        self.count = self.count + 1
        i = self.count
        while i & 0x01:
            i = i >> 1
            v0 = self.values.pop()
            v1 = self.values.pop()
            self.values.append( v0 + v1 )

    def get_total(self):
        return sum( reversed(self.values) )

    def get_size( self ):
        return self.count

Wenn alle Ihre Werte ungefähr die gleiche Größenordnung haben, hilft dies, die Genauigkeit zu erhalten, indem immer Werte mit ungefähr ähnlichen Größen hinzugefügt werden.

Mayur Patel
quelle
15
Dies ist eine furchtbar unklare Antwort, zumindest ein Kommentar im Code oder eine Erklärung, warum dies bei Gleitkommafehlern hilft, wäre nett.
Gabe
In meinem letzten Satz habe ich versucht anzugeben, warum es bei Gleitkommafehlern hilft. Wenn zwei Werte ungefähr die gleiche Größenordnung haben, verliert das Hinzufügen weniger an Genauigkeit als wenn Sie einer sehr kleinen Zahl eine sehr große Zahl hinzufügen. Der Code kombiniert "benachbarte" Werte auf eine Weise, dass selbst Zwischensummen immer eine angemessen nahe Größe haben sollten, um den Gleitkommafehler zu minimieren. Nichts ist narrensicher, aber diese Methode hat ein paar sehr schlecht umgesetzte Projekte in der Produktion gerettet.
Mayur Patel
1. Auf das ursprüngliche Problem angewendet, wäre dies furchtbar langsam (Berechnungsdurchschnitt), daher ist dies nur irrelevant. 2. Um unter dem Problem der Genauigkeit von 64-Bit-Zahlen zu leiden, muss man >> 2 ^ 30 von fast zusammenfassen gleiche Anzahl.
Alleo
@Alleo: Anstatt eine Addition pro Wert vorzunehmen, machen Sie zwei. Der Beweis ist der gleiche wie das Bit-Flipping-Problem. Bei dieser Antwort geht es jedoch nicht unbedingt um Leistung, sondern um Präzision. Die Speichernutzung für die Mittelung von 64-Bit-Werten würde 64 Elemente im Cache nicht überschreiten, daher ist sie auch für die Speichernutzung geeignet.
Mayur Patel
Ja, Sie haben Recht, dass dies 2x mehr Operationen als eine einfache Summe erfordert, aber das ursprüngliche Problem ist die Berechnung des laufenden Mittelwerts , nicht nur der Summe. Dies kann in O (n) erfolgen, aber Ihre Antwort erfordert O (mn), wobei m die Größe des Fensters ist.
Alleo