Risposte:
Per una soluzione breve e veloce che fa tutto in un ciclo, senza dipendenze, il codice qui sotto funziona alla grande.
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: le soluzioni più efficienti sono state proposte da Alleo e jasaarim .
Puoi usare np.convolve
per quello:
np.convolve(x, np.ones((N,))/N, mode='valid')
La media corrente è un caso dell'operazione matematica di convoluzione . Per la media corrente, si fa scorrere una finestra lungo l'input e si calcola la media del contenuto della finestra. Per segnali 1D discreti, la convoluzione è la stessa cosa, tranne per il fatto che si calcola una combinazione lineare arbitraria, cioè moltiplica ogni elemento per un coefficiente corrispondente e somma i risultati. Quei coefficienti, uno per ogni posizione nella finestra, sono talvolta chiamati kernel di convoluzione . Ora, la media aritmetica dei valori N è (x_1 + x_2 + ... + x_N) / N
, quindi lo è il kernel corrispondente (1/N, 1/N, ..., 1/N)
, ed è esattamente quello che otteniamo usando np.ones((N,))/N
.
L' mode
argomento di np.convolve
specifica come gestire i bordi. Ho scelto la valid
modalità qui perché penso che sia così che la maggior parte delle persone si aspetta che la corsa funzioni, ma potresti avere altre priorità. Ecco una trama che illustra la differenza tra le modalità:
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
ha una maggiore complessità.
La convoluzione è molto meglio dell'approccio diretto, ma (immagino) utilizza FFT e quindi piuttosto lenta. Tuttavia, specialmente per il calcolo della corsa, il seguente approccio funziona bene
def running_mean(x, N):
cumsum = numpy.cumsum(numpy.insert(x, 0, 0))
return (cumsum[N:] - cumsum[:-N]) / float(N)
Il codice da verificare
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
Si noti che numpy.allclose(result1, result2)
èTrue
, due metodi sono equivalenti. Maggiore è la N, maggiore è la differenza nel tempo.
# 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
ma l'errore in virgola mobile continuerà a essere significativo per un numero relativamente elevato di punti (circa> 1e5 ma dipende dai tuoi dati)numpy.convolve
O (mn); la sua documentazione menziona che scipy.signal.fftconvolve
utilizza FFT.
running_mean([1,2,3], 2)
dà array([1, 2])
. Sostituire x
con [float(value) for value in x]
fa il trucco.
x
contiene galleggianti. Esempio: running_mean(np.arange(int(1e7))[::-1] + 0.2, 1)[-1] - 0.2
ritorna 0.003125
mentre ci si aspetta 0.0
. Ulteriori informazioni: en.wikipedia.org/wiki/Loss_of_significance
Aggiornamento: L'esempio seguente mostra la vecchia pandas.rolling_mean
funzione che è stata rimossa nelle ultime versioni di Panda. Un equivalente moderno della chiamata di funzione qui sotto sarebbe
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])
Panda è più adatto a questo di NumPy o SciPy. La sua funzione rolling_mean fa comodamente il lavoro. Restituisce anche un array NumPy quando l'input è un array.
È difficile battere le rolling_mean
prestazioni con qualsiasi implementazione Python pura personalizzata. Ecco un esempio di prestazione rispetto a due delle soluzioni proposte:
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
Ci sono anche buone opzioni su come gestire i valori dei bordi.
df.rolling(windowsize).mean()
ora funziona invece (molto rapidamente potrei aggiungere). per 6.000 serie di righe ha %timeit test1.rolling(20).mean()
restituito 1000 loop, meglio di 3: 1,16 ms per loop
df.rolling()
funziona abbastanza bene, il problema è che anche questo modulo non supporterà ndarrays in futuro. Per usarlo dovremo prima caricare i nostri dati in un Dataframe Pandas. Mi piacerebbe vedere questa funzione aggiunta a numpy
o scipy.signal
.
%timeit bottleneck.move_mean(x, N)
è da 3 a 15 volte più veloce dei metodi cumsum e panda sul mio pc. Dai un'occhiata al loro benchmark nel README del repository .
Puoi calcolare una media corrente con:
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
Ma è lento.
Fortunatamente, NumPy include una convolve funzione che possiamo utilizzare per accelerare le cose. La media corrente equivale a contorcersi x
con un vettore N
lungo, con tutti i membri uguali 1/N
. L'implementazione numpy di convolve include il transitorio iniziale, quindi è necessario rimuovere i primi punti N-1:
def runningMeanFast(x, N):
return np.convolve(x, np.ones((N,))/N)[(N-1):]
Sulla mia macchina, la versione veloce è 20-30 volte più veloce, a seconda della lunghezza del vettore di input e delle dimensioni della finestra della media.
Si noti che convolve include una 'same'
modalità che sembra che dovrebbe affrontare il problema del transitorio iniziale, ma lo divide tra l'inizio e la fine.
mode='valid'
in convolve
cui non è necessaria alcuna post-elaborazione.
mode='valid'
rimuove il transitorio da entrambe le estremità, giusto? Se len(x)=10
e N=4
, per una media di corsa, vorrei 10 risultati ma valid
restituisce 7.
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')
(con pyplot e numpy importati).
runningMean
Ho un effetto collaterale della media con zeri, quando esci dall'array con x[ctr:(ctr+N)]
per il lato destro dell'array.
runningMeanFast
anche questo problema con l'effetto bordo.
o modulo per Python che calcola
nei miei test su Tradewave.net TA-lib vince sempre:
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])
i risultati:
[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
. Ricevo questo errore, signore.
Per una soluzione pronta per l'uso, consultare https://scipy-cookbook.readthedocs.io/items/SignalSmooth.html . Fornisce media corrente con ilflat
tipo di finestra. Si noti che questo è un po 'più sofisticato del semplice metodo convoluto fai-da-te, poiché cerca di gestire i problemi all'inizio e alla fine dei dati riflettendoli (che potrebbero o meno funzionare nel tuo caso. ..).
Per cominciare, puoi provare:
a = np.random.random(100)
plt.plot(a)
b = smooth(a, window='flat')
plt.plot(b)
numpy.convolve
, la differenza solo nel modificare la sequenza.
w
la dimensione della finestra e s
i dati?
Puoi usare scipy.ndimage.filters.uniform_filter1d :
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'
è l'impostazione predefinita, ma nel mio caso, ho preferito'nearest'
È anche piuttosto veloce (quasi 50 volte più veloce di np.convolve
e 2-5 volte più veloce dell'approccio cumsum sopra indicato ):
%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
ecco 3 funzioni che ti consentono di confrontare errori / velocità di diverse implementazioni:
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
con un rettangolo, e np.cumsum
seguito da np.subtract
. i miei risultati: (1.) contorto è il più lento. (2.) Il cumulo / sottrazione è circa 20-30 volte più veloce. (3.) uniform_filter1d è circa 2-3 volte più veloce di cumsum / sottrarre. il vincitore è sicuramente uniform_filter1d.
uniform_filter1d
è più veloce della cumsum
soluzione (di circa 2-5x). e uniform_filter1d
non si verifica un grosso errore in virgola mobile come lacumsum
soluzione.
So che questa è una vecchia domanda, ma ecco una soluzione che non utilizza alcuna struttura di dati o librerie aggiuntive. È lineare nel numero di elementi dell'elenco di input e non riesco a pensare a nessun altro modo per renderlo più efficiente (in realtà se qualcuno conosce un modo migliore di allocare il risultato, per favore fatemi sapere).
NOTA: questo sarebbe molto più veloce utilizzando un array intorpidito anziché un elenco, ma volevo eliminare tutte le dipendenze. Sarebbe anche possibile migliorare le prestazioni mediante l'esecuzione multi-thread
La funzione presuppone che l'elenco di input sia monodimensionale, quindi fai attenzione.
### 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
Esempio
Supponiamo che abbiamo un elenco data = [ 1, 2, 3, 4, 5, 6 ]
su cui vogliamo calcolare una media mobile con periodo di 3 e che si desidera anche un elenco di output che abbia le stesse dimensioni di quello di input (il più delle volte è il caso).
Il primo elemento ha indice 0, quindi la media mobile dovrebbe essere calcolata su elementi di indice -2, -1 e 0. Ovviamente non abbiamo dati [-2] e dati [-1] (a meno che tu non voglia usare speciali condizioni al contorno), quindi assumiamo che quegli elementi siano 0. Ciò equivale a zero-padding dell'elenco, tranne per il fatto che non lo pading in realtà, basta tenere traccia degli indici che richiedono il padding (da 0 a N-1).
Quindi, per i primi N elementi continuiamo a sommare gli elementi in un accumulatore.
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
Dagli elementi N + 1 in avanti l'accumulo semplice non funziona. ci aspettiamo result[3] = (2 + 3 + 4)/3 = 3
ma questo è diverso da (sum + 4)/3 = 3.333
.
Il modo per calcolare il valore corretto è quello di sottrarre data[0] = 1
da sum+4
, dando così sum + 4 - 1 = 9
.
Questo accade perché attualmente sum = data[0] + data[1] + data[2]
, ma è anche vero per ogni i >= N
perché, prima della sottrazione, lo sum
è data[i-N] + ... + data[i-2] + data[i-1]
.
Sento che questo può essere elegantemente risolto usando il collo di bottiglia
Vedi esempio di base di seguito:
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" è il mezzo mobile per "a".
"finestra" è il numero massimo di voci da considerare per la media mobile.
"min_count" è il numero minimo di voci da considerare per lo spostamento della media (ad es. per i primi elementi o se l'array ha valori nan).
La parte buona è che il collo di bottiglia aiuta a gestire i valori nanometrici ed è anche molto efficiente.
Non ho ancora verificato quanto sia veloce, ma potresti provare:
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)
Questa risposta contiene soluzioni che utilizzano la libreria standard Python per tre diversi scenari.
itertools.accumulate
Questa è una soluzione Python 3.2+ efficiente in termini di memoria che calcola la media corrente su un iterabile di valori sfruttando la leva itertools.accumulate
.
>>> from itertools import accumulate
>>> values = range(100)
Nota che values
può essere qualsiasi iterabile, inclusi generatori o qualsiasi altro oggetto che produce valori al volo.
Innanzitutto, costruisci pigramente la somma cumulativa dei valori.
>>> cumu_sum = accumulate(value_stream)
Successivamente, enumerate
la somma cumulativa (a partire da 1) e costruisci un generatore che produce la frazione dei valori accumulati e l'indice di enumerazione corrente.
>>> rolling_avg = (accu/i for i, accu in enumerate(cumu_sum, 1))
È possibile emettere means = list(rolling_avg)
se sono necessari tutti i valori in memoria contemporaneamente o chiamare in modo next
incrementale.
(Naturalmente, puoi anche iterare rolling_avg
con un for
ciclo, che chiamerà next
implicitamente.)
>>> next(rolling_avg) # 0/1
>>> 0.0
>>> next(rolling_avg) # (0 + 1)/2
>>> 0.5
>>> next(rolling_avg) # (0 + 1 + 2)/3
>>> 1.0
Questa soluzione può essere scritta come una funzione come segue.
from itertools import accumulate
def rolling_avg(iterable):
cumu_sum = accumulate(iterable)
yield from (accu/i for i, accu in enumerate(cumu_sum, 1))
Questo coroutine consuma i valori inviati e mantiene una media corrente dei valori visti finora.
È utile quando non si dispone di un iterabile di valori ma si acquisiscono i valori da calcolare uno a uno in momenti diversi durante la vita del programma.
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
La coroutine funziona così:
>>> 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
Questa funzione generatore prende una dimensione iterabile e una finestra N
e produce la media rispetto ai valori correnti all'interno della finestra. Utilizza a deque
, che è una struttura di dati simile a un elenco, ma ottimizzata per modifiche rapide ( pop
, append
) in entrambi gli endpoint .
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)
Ecco la funzione in azione:
>>> 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
Un po 'in ritardo per la festa, ma ho realizzato la mia piccola funzione che NON avvolge le estremità o i pad con zero che vengono quindi utilizzati per trovare anche la media. Come ulteriore vantaggio è che ricampiona anche il segnale in punti spaziati linearmente. Personalizza il codice a piacimento per ottenere altre funzionalità.
Il metodo è una semplice moltiplicazione matriciale con un kernel gaussiano normalizzato.
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
Un semplice utilizzo su un segnale sinusoidale con aggiunta di rumore distribuito normale:
sum
, usando np.sum
invece 2 L' @
operatore (non ho idea di cosa sia) genera un errore. Potrei esaminarlo più tardi, ma in questo momento mi manca il tempo
Invece di intorpidire o scipy, consiglierei ai panda di farlo più rapidamente:
df['data'].rolling(3).mean()
Questo richiede la media mobile (MA) di 3 periodi della colonna "dati". Puoi anche calcolare le versioni spostate, ad esempio quella che esclude la cella corrente (spostata indietro) può essere calcolata facilmente come:
df['data'].shift(periods=1).rolling(3).mean()
pandas.rolling_mean
mentre la mia usa pandas.DataFrame.rolling
. Puoi anche calcolare lo spostamento min(), max(), sum()
ecc., Nonché mean()
con questo metodo facilmente.
pandas.rolling_min, pandas.rolling_max
ecc. Sono simili ma diversi.
C'è un commento di Mab sepolto in una delle risposte sopra che ha questo metodo. bottleneck
ha move_mean
quale è una media mobile semplice:
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
è un parametro utile che in pratica porterà la media mobile fino a quel punto dell'array. Se non si imposta min_count
, sarà uguale window
e tutto sarà fino ai window
punti nan
.
Un altro approccio per trovare la media mobile senza usare intorpidimento, panda
import itertools
sample = [2, 6, 10, 8, 11, 10]
list(itertools.starmap(lambda a,b: b/a,
enumerate(itertools.accumulate(sample), 1)))
stamperà [2.0, 4.0, 6.0, 6.5, 7.4, 7.833333333333333]
Questa domanda è ora ancora più antica di quando NeXuS ne ha scritto il mese scorso, MA mi piace come il suo codice gestisce i casi limite. Tuttavia, poiché si tratta di una "media mobile semplice", i suoi risultati sono in ritardo rispetto ai dati a cui si applicano. Ho pensato che si tratta di casi limite in modo più soddisfacente rispetto alle modalità di NumPy valid
, same
e full
potrebbe essere raggiunto mediante l'applicazione di un approccio simile a un convolution()
metodo basato.
Il mio contributo utilizza una media corrente centrale per allineare i risultati con i loro dati. Quando ci sono troppi punti disponibili per l'uso della finestra a dimensione intera, le medie correnti vengono calcolate da finestre successivamente più piccole ai bordi dell'array. [In realtà, da finestre successivamente più grandi, ma questo è un dettaglio di implementazione.]
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]])
È relativamente lento perché lo utilizza convolve()
, e probabilmente potrebbe essere abbastanza elaborato da un vero Pythonista, tuttavia, credo che l'idea sia valida.
Ci sono molte risposte sopra sul calcolo di una media corrente. La mia risposta aggiunge due funzionalità extra:
Questa seconda funzione è particolarmente utile per determinare quali valori differiscono dall'andamento generale di un certo importo.
Uso numpy.cumsum poiché è il metodo più efficiente in termini di tempo ( vedere la risposta di Alleo sopra ).
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)
Questo codice funziona solo per Ns. Può essere regolato per i numeri dispari modificando np.insert di padded_x e n_nan.
Esempio di output (raw in nero, movavg in blu):
Questo codice può essere facilmente adattato per rimuovere tutti i valori della media mobile calcolati da meno di cutoff = 3 valori non-nan.
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)
Usa solo libreria standard Python (efficiente in termini di memoria)
Basta dare un'altra versione dell'uso della deque
sola libreria standard . È una sorpresa per me che la maggior parte delle risposte stia utilizzando pandas
o 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]
In realtà ho trovato un'altra implementazione nei documenti di Python
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
Tuttavia l'implementazione mi sembra un po 'più complessa di quanto dovrebbe essere. Ma deve essere nei documenti standard di Python per una ragione, qualcuno potrebbe commentare l'implementazione del mio e del documento standard?
O(n*d)
calcoli ( d
essendo la dimensione della finestra, n
dimensione dell'iterabile) e loro stanno facendoO(n)
Sebbene ci siano soluzioni per questa domanda qui, dai un'occhiata alla mia soluzione. È molto semplice e funziona bene.
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)
Leggendo le altre risposte, non penso che questo sia ciò che la domanda ha posto, ma sono arrivato qui con la necessità di mantenere una media corrente di un elenco di valori che stava crescendo di dimensioni.
Quindi, se vuoi mantenere un elenco di valori che stai acquisendo da qualche parte (un sito, un dispositivo di misurazione, ecc.) E la media degli ultimi n
valori aggiornati, puoi usare il codice qui sotto, che minimizza lo sforzo di aggiungere nuovi elementi:
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)
E puoi provarlo con, ad esempio:
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()
Che dà:
Un'altra soluzione che utilizza solo una libreria standard e un deque:
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
A scopo educativo, vorrei aggiungere altre due soluzioni Numpy (che sono più lente della soluzione cumsum):
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
Funzioni utilizzate: as_strided , add.reduceat
Tutte le soluzioni di cui sopra sono scarse perché mancano
numpy.cumsum
, oO(len(x) * w)
implementazioni come convoluzioni.Dato
import numpy
m = 10000
x = numpy.random.rand(m)
w = 1000
Si noti che è x_[:w].sum()
uguale x[:w-1].sum()
. Quindi, per la prima media, numpy.cumsum(...)
aggiunge x[w] / w
(via x_[w+1] / w
) e sottrae 0
(da x_[0] / w
). Questo risulta inx[0:w].mean()
Tramite cumsum, aggiornerai la seconda media aggiungendo x[w+1] / w
e sottraendo ulteriormente x[0] / w
, risultando x[1:w+1].mean()
.
Questo continua fino a quando non x[-w:].mean()
viene raggiunto.
x_ = numpy.insert(x, 0, 0)
sliding_average = x_[:w].sum() / w + numpy.cumsum(x_[w:] - x_[:-w]) / w
Questa soluzione è vettoriale O(m)
, leggibile e numericamente stabile.
Che ne dici di un filtro a media mobile ? È anche un liner e ha il vantaggio di poter facilmente manipolare il tipo di finestra se hai bisogno di qualcosa di diverso dal rettangolo, cioè. una media mobile semplice lunga N di un array a:
lfilter(np.ones(N)/N, [1], a)[N:]
E con la finestra triangolare applicata:
lfilter(np.ones(N)*scipy.signal.triang(N)/N, [1], a)[N:]
Nota: di solito scarto i primi N campioni come fasulli quindi [N:]
alla fine, ma non è necessario e si tratta solo di una scelta personale.
Se scegli di creare il tuo, anziché utilizzare una libreria esistente, sii consapevole dell'errore in virgola mobile e prova a minimizzare i suoi effetti:
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
Se tutti i tuoi valori sono all'incirca allo stesso ordine di grandezza, ciò contribuirà a preservare la precisione aggiungendo sempre valori di grandezza approssimativamente simili.