Antworten:
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)
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')
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) / N
also der entsprechende Kernel (1/N, 1/N, ..., 1/N)
, und genau das erhalten wir, wenn wir es verwenden np.ones((N,))/N
.
Das mode
Argument von np.convolve
gibt an, wie mit den Kanten umgegangen werden soll. Ich habe den valid
Modus 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()
numpy.cumsum
ist jedoch komplexer.
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.
# 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)
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).numpy.convolve
O (mn); In den Dokumenten wird erwähnt, dass scipy.signal.fftconvolve
FFT verwendet wird.
running_mean([1,2,3], 2)
gibt array([1, 2])
. Ersetzen x
durch [float(value) for value in x]
macht den Trick.
x
Schwimmer enthält. Beispiel: running_mean(np.arange(int(1e7))[::-1] + 0.2, 1)[-1] - 0.2
kehrt zurück, 0.003125
während man erwartet 0.0
. Weitere Informationen: en.wikipedia.org/wiki/Loss_of_significance
Update: Das folgende Beispiel zeigt die alte pandas.rolling_mean
Funktion, 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_mean
Leistung 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.
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
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 numpy
oder hinzugefügt wird scipy.signal
.
%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 .
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 x
mit einem Vektor, der N
lang 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.
mode='valid'
bei convolve
der keine Nachbearbeitung erforderlich ist.
mode='valid'
entfernt den Übergang von beiden Enden, richtig? Wenn len(x)=10
und N=4
, für einen laufenden Mittelwert würde ich 10 Ergebnisse wollen, aber valid
7 zurückgeben.
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).
runningMean
Habe ich Nebeneffekt der Mittelung mit Nullen, wenn Sie das Array mit x[ctr:(ctr+N)]
für die rechte Seite des Arrays verlassen.
runningMeanFast
haben auch dieses Randeffektproblem.
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
NameError: name 'info' is not defined
. Ich erhalte diesen Fehler, Sir.
Eine sofort einsatzbereite Lösung finden Sie unter https://scipy-cookbook.readthedocs.io/items/SignalSmooth.html . Es liefert den laufenden Durchschnitt mit dem flat
Fenstertyp. 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)
numpy.convolve
dem Unterschied nur in der Änderung der Reihenfolge.
w
die Fenstergröße und s
die Daten?
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
::
'reflect'
der Standard ist, aber in meinem Fall wollte ich lieber'nearest'
Es ist auch ziemlich schnell (fast 50-mal schneller als np.convolve
und 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)]
uniform_filter1d
, np.convolve
mit einem Rechteck und np.cumsum
anschließ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.
uniform_filter1d
ist schneller als die cumsum
Lösung (um etwa 2-5x). und uniform_filter1d
erhält keinen massiven Gleitkommafehler wie diecumsum
Lösung.
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 = 3
aber das ist anders als (sum + 4)/3 = 3.333
.
Die Art und Weise des richtigen Wert zu berechnen ist zu subtrahieren data[0] = 1
aus 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 >= N
weil vor der Subtraktion sum
ist data[i-N] + ... + data[i-2] + data[i-1]
.
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.
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)
Diese Antwort enthält Lösungen, die die Python- Standardbibliothek für drei verschiedene Szenarien verwenden.
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 values
dies 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 enumerate
die 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 next
inkrementell aufrufen .
(Natürlich können Sie auch iterierenrolling_avg
mit einer for
Schleife , die next
implizit 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))
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
N
Diese Generatorfunktion nimmt eine iterierbare und eine Fenstergröße an N
und liefert den Durchschnitt über die aktuellen Werte innerhalb des Fensters. Es deque
wird 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
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:
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
@
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()
pandas.rolling_mean
während meine verwendet pandas.DataFrame.rolling
. Sie können auch die Bewegung min(), max(), sum()
usw. sowie mean()
mit dieser Methode einfach berechnen.
pandas.rolling_min, pandas.rolling_max
usw. verwenden. Sie sind ähnlich, aber unterschiedlich.
In einer der obigen Antworten ist ein Kommentar von mab vergraben, der diese Methode enthält. hat was ist ein einfacher gleitender Durchschnitt:bottleneck
move_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_count
ist 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 window
und alles bis zu window
Punkten wird sein nan
.
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]
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
, same
und full
konnte 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.
Es gibt oben viele Antworten zur Berechnung eines laufenden Mittelwerts. Meine Antwort fügt zwei zusätzliche Funktionen hinzu:
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):
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)
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 pandas
oder 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?
O(n*d)
Berechnungen durch ( d
n
O(n)
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)
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 n
aktualisierten 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:
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
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
Alle oben genannten Lösungen sind schlecht, weil sie fehlen
numpy.cumsum
, oderO(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] / w
subtrahiert) 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] / w
und 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.
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.
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.