Come calcolare la media mobile utilizzando NumPy?


109

Non sembra esserci alcuna funzione che calcoli semplicemente la media mobile su numpy / scipy, portando a soluzioni contorte .

La mia domanda è duplice:

  • Qual è il modo più semplice per implementare (correttamente) una media mobile con numpy?
  • Dato che questo sembra non banale e soggetto a errori, c'è un buon motivo per non avere le batterie incluse in questo caso?

19
La soluzione della convoluzione non mi sembra così contorta!
wim

4
Una media mobile non è solo un filtro passa-basso (cioè "sfocatura")? Sono abbastanza sicuro che sia esattamente il tipo di cosa per cui la convoluzione è destinata ...
user541686

@mmgp Immagino che speravo di sbagliarmi o che ci fosse una buona, ovvia ragione.
goncalopp

3
@wim Era inteso per metà come un gioco di parole. Ma il semplice fatto che la domanda esista significa che non è semplice creare una media mobile da numpy.convolute.
goncalopp

3
Possibile duplicato di Media mobile o media corrente
vivace

Risposte:


165

Se si desidera solo un semplice non ponderata media mobile, si può facilmente implementare con np.cumsum, che può essere sia metodi più veloce di FFT basati su:

EDIT Corretta un'indicizzazione errata off-by-one individuata da Bean nel codice. MODIFICARE

def moving_average(a, n=3) :
    ret = np.cumsum(a, dtype=float)
    ret[n:] = ret[n:] - ret[:-n]
    return ret[n - 1:] / n

>>> a = np.arange(20)
>>> moving_average(a)
array([  1.,   2.,   3.,   4.,   5.,   6.,   7.,   8.,   9.,  10.,  11.,
        12.,  13.,  14.,  15.,  16.,  17.,  18.])
>>> moving_average(a, n=4)
array([  1.5,   2.5,   3.5,   4.5,   5.5,   6.5,   7.5,   8.5,   9.5,
        10.5,  11.5,  12.5,  13.5,  14.5,  15.5,  16.5,  17.5])

Quindi immagino che la risposta sia: è davvero facile da implementare, e forse numpy è già un po 'gonfio di funzionalità specializzate.


10
Questo codice non è corretto. es. moving_average ([1,2,5,10], n = 2) restituisce [1., 3.5, 8.5]. Anche il caso di test del rispondente per una media mobile di valori da 0 a 19 non è corretto, sostenendo che la media di 0, 1 e 2 è 0,5. Come ha ottenuto 6 voti positivi?
JeremyKun

2
Grazie per il controllo dei bug, ora sembra funzionare correttamente. Per quanto riguarda i voti positivi, immagino che l'idea generale alla base della risposta sia stata pesata più pesantemente di un errore off-by-one nell'implementazione, ma chi lo sa.
Jaime

2
Ho trovato il problema. ret[n:] -= ret[:-n]NON è LA STESSA come ret[n:] = ret[n:] - ret[:-n]. Ho corretto il codice in questa risposta. Edit: No, qualcun altro mi ha appena battuto.
Timmmm

7
@Timmmm l'ho fatto, questo era davvero il problema. Il principio generale alla base di questa risposta è ampiamente utilizzato nell'elaborazione delle immagini (le tabelle dell'area sommata lo chiamano), quindi il problema doveva essere nell'implementazione. Un buon esempio di ottimizzazione prematura, dal momento che ricordo di aver fatto l'operazione sul posto "perché sarà più efficiente". Il lato positivo è che probabilmente ha prodotto la risposta sbagliata più velocemente ...
Jaime

43
Hmmm, sembra che questa funzione "facile da implementare" sia in realtà abbastanza facile da sbagliare e ha favorito una buona discussione sull'efficienza della memoria. Sono felice di essere gonfio se significa sapere che qualcosa è stato fatto bene.
Richard

81

La mancanza di NumPy di ​​una particolare funzione specifica del dominio è forse dovuta alla disciplina e alla fedeltà del Core Team alla direttiva primaria di NumPy: fornire un tipo di array N-dimensionale , oltre a funzioni per creare e indicizzare quegli array. Come molti obiettivi fondamentali, questo non è piccolo e NumPy lo fa brillantemente.

Lo SciPy (molto) più grande contiene una raccolta molto più ampia di librerie specifiche del dominio (chiamate sotto- pacchetti dagli sviluppatori di SciPy), ad esempio, ottimizzazione numerica ( ottimizzazione ), elaborazione del segnale ( segnale ) e calcolo integrale ( integrazione ).

La mia ipotesi è che la funzione che stai cercando sia in almeno uno dei sottopacchetti di SciPy ( forse scipy.signal ); tuttavia, cercherei prima nella raccolta degli scikit di SciPy , identificare gli scikit pertinenti e cercare la funzione di interesse lì.

Gli scikit sono pacchetti sviluppati in modo indipendente basati su NumPy / SciPy e diretti a una particolare disciplina tecnica (ad esempio, scikits-image , scikits-learn , ecc.) Molti di questi erano (in particolare, il fantastico OpenOpt per l'ottimizzazione numerica) erano molto apprezzati, progetti maturi molto prima di scegliere di risiedere sotto la relativamente nuova rubrica scikit . La homepage di Scikits ha apprezzato gli elenchi sopra di circa 30 di questi scikit , anche se almeno molti di questi non sono più in fase di sviluppo attivo.

Seguire questo consiglio ti porterebbe a scikits-timeseries ; tuttavia, quel pacchetto non è più in fase di sviluppo attivo; In effetti, Panda è diventato, per quanto ne so, il fatto NumPy -based biblioteca serie storiche.

Pandas ha diverse funzioni che possono essere utilizzate per calcolare una media mobile ; il più semplice di questi è probabilmente rolling_mean , che usi in questo modo:

>>> # the recommended syntax to import pandas
>>> import pandas as PD
>>> import numpy as NP

>>> # prepare some fake data:
>>> # the date-time indices:
>>> t = PD.date_range('1/1/2010', '12/31/2012', freq='D')

>>> # the data:
>>> x = NP.arange(0, t.shape[0])

>>> # combine the data & index into a Pandas 'Series' object
>>> D = PD.Series(x, t)

Ora, chiama semplicemente la funzione rolling_mean passando l'oggetto Series e una dimensione della finestra , che nel mio esempio sotto è di 10 giorni .

>>> d_mva = PD.rolling_mean(D, 10)

>>> # d_mva is the same size as the original Series
>>> d_mva.shape
    (1096,)

>>> # though obviously the first w values are NaN where w is the window size
>>> d_mva[:3]
    2010-01-01         NaN
    2010-01-02         NaN
    2010-01-03         NaN

verificare che abbia funzionato, ad esempio confrontando i valori 10-15 nella serie originale rispetto alla nuova serie livellata con media rotante

>>> D[10:15]
     2010-01-11    2.041076
     2010-01-12    2.041076
     2010-01-13    2.720585
     2010-01-14    2.720585
     2010-01-15    3.656987
     Freq: D

>>> d_mva[10:20]
      2010-01-11    3.131125
      2010-01-12    3.035232
      2010-01-13    2.923144
      2010-01-14    2.811055
      2010-01-15    2.785824
      Freq: D

La funzione rolling_mean, insieme a circa una dozzina di altre funzioni, sono raggruppate in modo informale nella documentazione di Pandas sotto la rubrica funzioni finestra mobile ; un secondo gruppo correlato di funzioni in Panda è indicato come funzioni ponderate esponenzialmente (ad esempio, ewma , che calcola la media ponderata mobile esponenziale). Il fatto che questo secondo gruppo non sia incluso nel primo ( funzioni finestra mobile ) è forse perché le trasformazioni ponderate esponenzialmente non si basano su una finestra di lunghezza fissa


6
I panda hanno una forte linea di funzioni per finestre mobili. Ma mi sembra un po 'troppo sovraccarico per una semplice media mobile.
Jaime

6
beh dubito che il calcolo di una media mobile sia un requisito isolato per l'OP o per chiunque altro. Se devi calcolare una media mobile, quasi certamente hai una serie temporale, il che significa che hai bisogno di una struttura dati che ti consenta di conformare un indice data-ora ai tuoi dati e questo è il "sovraccarico" a cui ti riferisci.
Doug

2
In primo luogo, grazie per aver dedicato del tempo a scrivere questa risposta estremamente istruttiva. In effetti, non riesco a vedere un utilizzo per una media mobile che non coinvolga una serie temporale. Ma ciò non significa che sia necessario conformarlo a un datetime, o anche a un particolare intervallo di campionamento (potrebbe essere sconosciuto)
goncalopp

3
Volevo solo aggiungere che la funzione di media mobile è stata estratta nella libreria Bottleneck se i panda sembrano troppo pesanti come dipendenza.
robochat

4
'rolling_mean' non fa più parte dei panda, per favore guarda la risposta usando invece 'rolling'
Vladtn

62

Un modo semplice per ottenere ciò è utilizzare np.convolve. L'idea alla base di questo è sfruttare il modo in cui viene calcolata la convoluzione discreta e utilizzarla per restituire una media mobile . Questo può essere fatto convolgendo con una sequenza np.onesdi una lunghezza uguale alla lunghezza della finestra scorrevole che vogliamo.

Per fare ciò potremmo definire la seguente funzione:

def moving_average(x, w):
    return np.convolve(x, np.ones(w), 'valid') / w

Questa funzione prenderà la convoluzione della sequenza xe una sequenza di quelle di lunghezza w. Si noti che la scelta modeè validtale che il prodotto di convoluzione viene fornito solo per i punti in cui le sequenze si sovrappongono completamente.


Qualche esempio:

x = np.array([5,3,8,10,2,1,5,1,0,2])

Per una media mobile con una finestra di lunghezza 2avremmo:

moving_average(x, 2)
# array([4. , 5.5, 9. , 6. , 1.5, 3. , 3. , 0.5, 1. ])

E per una finestra di lunghezza 4:

moving_average(x, 4)
# array([6.5 , 5.75, 5.25, 4.5 , 2.25, 1.75, 2.  ])

Come convolvefunziona?

Diamo uno sguardo più approfondito al modo in cui viene calcolata la convoluzione discreta. La seguente funzione mira a replicare il modo in cui np.convolvecalcola i valori di output:

def mov_avg(x, w):
    for m in range(len(x)-(w-1)):
        yield sum(np.ones(w) * x[m:m+w]) / w 

Che, per lo stesso esempio sopra riportato, produrrebbe anche:

list(mov_avg(x, 2))
# [4.0, 5.5, 9.0, 6.0, 1.5, 3.0, 3.0, 0.5, 1.0]

Quindi ciò che viene fatto in ogni fase è prendere il prodotto interno tra la serie di quelli e la finestra corrente . In questo caso la moltiplicazione per np.ones(w)è superflua dato che stiamo prendendo direttamente il valore sumdella sequenza.

Di seguito è riportato un esempio di come vengono calcolati i primi output in modo che sia un po 'più chiaro. Supponiamo di volere una finestra di w=4:

[1,1,1,1]
[5,3,8,10,2,1,5,1,0,2]
= (1*5 + 1*3 + 1*8 + 1*10) / w = 6.5

E il seguente output sarebbe calcolato come:

  [1,1,1,1]
[5,3,8,10,2,1,5,1,0,2]
= (1*3 + 1*8 + 1*10 + 1*2) / w = 5.75

E così via, restituendo una media mobile della sequenza una volta che tutte le sovrapposizioni sono state eseguite.


Questa è una bella idea! È più veloce della risposta di @ Jaime per la n piccola, ma diventa più lenta per la n più grande.
Felipe Gerard

Grazie @FelipeGerard! Sì, come sottolineato nei commenti, anche se questo approccio potrebbe non essere efficiente come altre soluzioni insensibili, imo è bello avere un'alternativa per i futuri visitatori data la sua semplicità e concisione
yatu

A volte è utile avere un array di output della stessa dimensione dell'input. Per questo mode='valid'può essere sostituito con 'same'. Solo in questo caso i punti limite graviteranno verso lo zero.
Ilia Barahovski

In una situazione in cui alcuni elementi della matrice "x" della funzione possono essere Nessuno o zero, come si ottengono i valori "x" corrispondenti dei valori restituiti da questa funzione? La dimensione dell'array restituito da questa funzione può essere inferiore all'array "x" fornito.
Sun Bear

15

Ecco una varietà di modi per farlo, insieme ad alcuni benchmark. I metodi migliori sono le versioni che utilizzano codice ottimizzato da altre librerie. Il bottleneck.move_meanmetodo è probabilmente il migliore in tutto. L' scipy.convolveapproccio è anche molto veloce, estensibile e sintatticamente e concettualmente semplice, ma non scala bene per valori di finestre molto grandi. Il numpy.cumsummetodo è buono se hai bisogno di un numpyapproccio puro .

Nota: alcuni di questi (ad esempio bottleneck.move_mean) non sono centrati e sposteranno i tuoi dati.

import numpy as np
import scipy as sci
import scipy.signal as sig
import pandas as pd
import bottleneck as bn
import time as time

def rollavg_direct(a,n): 
    'Direct "for" loop'
    assert n%2==1
    b = a*0.0
    for i in range(len(a)) :
        b[i]=a[max(i-n//2,0):min(i+n//2+1,len(a))].mean()
    return b

def rollavg_comprehension(a,n):
    'List comprehension'
    assert n%2==1
    r,N = int(n/2),len(a)
    return np.array([a[max(i-r,0):min(i+r+1,N)].mean() for i in range(N)]) 

def rollavg_convolve(a,n):
    'scipy.convolve'
    assert n%2==1
    return sci.convolve(a,np.ones(n,dtype='float')/n, 'same')[n//2:-n//2+1]  

def rollavg_convolve_edges(a,n):
    'scipy.convolve, edge handling'
    assert n%2==1
    return sci.convolve(a,np.ones(n,dtype='float'), 'same')/sci.convolve(np.ones(len(a)),np.ones(n), 'same')  

def rollavg_cumsum(a,n):
    'numpy.cumsum'
    assert n%2==1
    cumsum_vec = np.cumsum(np.insert(a, 0, 0)) 
    return (cumsum_vec[n:] - cumsum_vec[:-n]) / n

def rollavg_cumsum_edges(a,n):
    'numpy.cumsum, edge handling'
    assert n%2==1
    N = len(a)
    cumsum_vec = np.cumsum(np.insert(np.pad(a,(n-1,n-1),'constant'), 0, 0)) 
    d = np.hstack((np.arange(n//2+1,n),np.ones(N-n)*n,np.arange(n,n//2,-1)))  
    return (cumsum_vec[n+n//2:-n//2+1] - cumsum_vec[n//2:-n-n//2]) / d

def rollavg_roll(a,n):
    'Numpy array rolling'
    assert n%2==1
    N = len(a)
    rolling_idx = np.mod((N-1)*np.arange(n)[:,None] + np.arange(N), N)
    return a[rolling_idx].mean(axis=0)[n-1:] 

def rollavg_roll_edges(a,n):
    # see /programming/42101082/fast-numpy-roll
    'Numpy array rolling, edge handling'
    assert n%2==1
    a = np.pad(a,(0,n-1-n//2), 'constant')*np.ones(n)[:,None]
    m = a.shape[1]
    idx = np.mod((m-1)*np.arange(n)[:,None] + np.arange(m), m) # Rolling index
    out = a[np.arange(-n//2,n//2)[:,None], idx]
    d = np.hstack((np.arange(1,n),np.ones(m-2*n+1+n//2)*n,np.arange(n,n//2,-1)))
    return (out.sum(axis=0)/d)[n//2:]

def rollavg_pandas(a,n):
    'Pandas rolling average'
    return pd.DataFrame(a).rolling(n, center=True, min_periods=1).mean().to_numpy()

def rollavg_bottlneck(a,n):
    'bottleneck.move_mean'
    return bn.move_mean(a, window=n, min_count=1)

N = 10**6
a = np.random.rand(N)
functions = [rollavg_direct, rollavg_comprehension, rollavg_convolve, 
        rollavg_convolve_edges, rollavg_cumsum, rollavg_cumsum_edges, 
        rollavg_pandas, rollavg_bottlneck, rollavg_roll, rollavg_roll_edges]

print('Small window (n=3)')
%load_ext memory_profiler
for f in functions : 
    print('\n'+f.__doc__+ ' : ')
    %timeit b=f(a,3)

print('\nLarge window (n=1001)')
for f in functions[0:-2] : 
    print('\n'+f.__doc__+ ' : ')
    %timeit b=f(a,1001)

print('\nMemory\n')
print('Small window (n=3)')
N = 10**7
a = np.random.rand(N)
%load_ext memory_profiler
for f in functions[2:] : 
    print('\n'+f.__doc__+ ' : ')
    %memit b=f(a,3)

print('\nLarge window (n=1001)')
for f in functions[2:-2] : 
    print('\n'+f.__doc__+ ' : ')
    %memit b=f(a,1001)

Tempistica, finestra piccola (n = 3)

Direct "for" loop : 

4.14 s ± 23.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

List comprehension : 
3.96 s ± 27.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

scipy.convolve : 
1.07 ms ± 26.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

scipy.convolve, edge handling : 
4.68 ms ± 9.69 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

numpy.cumsum : 
5.31 ms ± 5.11 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

numpy.cumsum, edge handling : 
8.52 ms ± 11.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Pandas rolling average : 
9.85 ms ± 9.63 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

bottleneck.move_mean : 
1.3 ms ± 12.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Numpy array rolling : 
31.3 ms ± 91.9 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

Numpy array rolling, edge handling : 
61.1 ms ± 55.9 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

Tempistica, finestra grande (n = 1001)

Direct "for" loop : 
4.67 s ± 34 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

List comprehension : 
4.46 s ± 14.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

scipy.convolve : 
103 ms ± 165 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

scipy.convolve, edge handling : 
272 ms ± 1.23 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

numpy.cumsum : 
5.19 ms ± 12.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

numpy.cumsum, edge handling : 
8.7 ms ± 11.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Pandas rolling average : 
9.67 ms ± 199 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

bottleneck.move_mean : 
1.31 ms ± 15.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Memoria, finestra piccola (n = 3)

The memory_profiler extension is already loaded. To reload it, use:
  %reload_ext memory_profiler

scipy.convolve : 
peak memory: 362.66 MiB, increment: 73.61 MiB

scipy.convolve, edge handling : 
peak memory: 510.24 MiB, increment: 221.19 MiB

numpy.cumsum : 
peak memory: 441.81 MiB, increment: 152.76 MiB

numpy.cumsum, edge handling : 
peak memory: 518.14 MiB, increment: 228.84 MiB

Pandas rolling average : 
peak memory: 449.34 MiB, increment: 160.02 MiB

bottleneck.move_mean : 
peak memory: 374.17 MiB, increment: 75.54 MiB

Numpy array rolling : 
peak memory: 661.29 MiB, increment: 362.65 MiB

Numpy array rolling, edge handling : 
peak memory: 1111.25 MiB, increment: 812.61 MiB

Memoria, finestra grande (n = 1001)

scipy.convolve : 
peak memory: 370.62 MiB, increment: 71.83 MiB

scipy.convolve, edge handling : 
peak memory: 521.98 MiB, increment: 223.18 MiB

numpy.cumsum : 
peak memory: 451.32 MiB, increment: 152.52 MiB

numpy.cumsum, edge handling : 
peak memory: 527.51 MiB, increment: 228.71 MiB

Pandas rolling average : 
peak memory: 451.25 MiB, increment: 152.50 MiB

bottleneck.move_mean : 
peak memory: 374.64 MiB, increment: 75.85 MiB

11

Questa risposta usando Pandas è adattata dall'alto, poiché rolling_meannon fa più parte di Pandas

# the recommended syntax to import pandas
import pandas as pd
import numpy as np

# prepare some fake data:
# the date-time indices:
t = pd.date_range('1/1/2010', '12/31/2012', freq='D')

# the data:
x = np.arange(0, t.shape[0])

# combine the data & index into a Pandas 'Series' object
D = pd.Series(x, t)

Ora, chiama semplicemente la funzione rollingsul dataframe con una dimensione della finestra, che nel mio esempio sotto è di 10 giorni.

d_mva10 = D.rolling(10).mean()

# d_mva is the same size as the original Series
# though obviously the first w values are NaN where w is the window size
d_mva10[:11]

2010-01-01    NaN
2010-01-02    NaN
2010-01-03    NaN
2010-01-04    NaN
2010-01-05    NaN
2010-01-06    NaN
2010-01-07    NaN
2010-01-08    NaN
2010-01-09    NaN
2010-01-10    4.5
2010-01-11    5.5
Freq: D, dtype: float64

5

Sento che questo può essere facilmente risolto usando il collo di bottiglia

Vedi l'esempio di base di seguito:

import numpy as np
import bottleneck as bn

a = np.random.randint(4, 1000, size=(5, 7))
mm = bn.move_mean(a, window=2, min_count=1)

Ciò fornisce la media di spostamento lungo ciascun asse.

  • "mm" è la media 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 la media mobile (ad esempio per il primo elemento o se l'array ha valori nan).

La parte buona è che Bottleneck aiuta a gestire i valori nan ed è anche molto efficiente.


2

Nel caso in cui si desideri prestare attenzione alle condizioni dei bordi ( calcolare la media solo dagli elementi disponibili ai bordi ), la seguente funzione farà il trucco.

import numpy as np

def running_mean(x, N):
    out = np.zeros_like(x, dtype=np.float64)
    dim_len = x.shape[0]
    for i in range(dim_len):
        if N%2 == 0:
            a, b = i - (N-1)//2, i + (N-1)//2 + 2
        else:
            a, b = i - (N-1)//2, i + (N-1)//2 + 1

        #cap indices to min and max indices
        a = max(0, a)
        b = min(dim_len, b)
        out[i] = np.mean(x[a:b])
    return out

>>> running_mean(np.array([1,2,3,4]), 2)
array([1.5, 2.5, 3.5, 4. ])

>>> running_mean(np.array([1,2,3,4]), 3)
array([1.5, 2. , 3. , 3.5])

1
for i in range(len(Data)):
    Data[i, 1] = Data[i-lookback:i, 0].sum() / lookback

Prova questo pezzo di codice. Penso che sia più semplice e fa il lavoro. la ricerca è la finestra della media mobile.

In Data[i-lookback:i, 0].sum()ho messo 0per fare riferimento alla prima colonna del set di dati ma puoi inserire qualsiasi colonna che ti piace nel caso in cui tu abbia più di una colonna.


0

In realtà volevo un comportamento leggermente diverso dalla risposta accettata. Stavo costruendo un estrattore di caratteristiche di media mobile per una sklearnpipeline, quindi ho richiesto che l'output della media mobile avesse la stessa dimensione dell'input. Quello che voglio è che la media mobile assuma che la serie rimanga costante, cioè una media mobile di [1,2,3,4,5]con finestra 2 darebbe[1.5,2.5,3.5,4.5,5.0] .

Per i vettori colonna (il mio caso d'uso) otteniamo

def moving_average_col(X, n):
  z2 = np.cumsum(np.pad(X, ((n,0),(0,0)), 'constant', constant_values=0), axis=0)
  z1 = np.cumsum(np.pad(X, ((0,n),(0,0)), 'constant', constant_values=X[-1]), axis=0)
  return (z1-z2)[(n-1):-1]/n

E per gli array

def moving_average_array(X, n):
  z2 = np.cumsum(np.pad(X, (n,0), 'constant', constant_values=0))
  z1 = np.cumsum(np.pad(X, (0,n), 'constant', constant_values=X[-1]))
  return (z1-z2)[(n-1):-1]/n

Naturalmente, non è necessario assumere valori costanti per il riempimento, ma nella maggior parte dei casi dovrebbe essere adeguato.


0

talib contiene un semplice strumento di media mobile, così come altri strumenti di media simili (cioè media mobile esponenziale). Di seguito confronta il metodo con alcune delle altre soluzioni.


%timeit pd.Series(np.arange(100000)).rolling(3).mean()
2.53 ms ± 40.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit talib.SMA(real = np.arange(100000.), timeperiod = 3)
348 µs ± 3.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

%timeit moving_average(np.arange(100000))
638 µs ± 45.1 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Un avvertimento è che il reale deve avere elementi di dtype = float. Altrimenti viene generato il seguente errore

Eccezione: il reale non è doppio


0

Ecco un'implementazione rapida che utilizza numba (attenzione ai tipi). Nota che contiene nans dove spostato.

import numpy as np
import numba as nb

@nb.jit(nb.float64[:](nb.float64[:],nb.int64),
        fastmath=True,nopython=True)
def moving_average( array, window ):    
    ret = np.cumsum(array)
    ret[window:] = ret[window:] - ret[:-window]
    ma = ret[window - 1:] / window
    n = np.empty(window-1); n.fill(np.nan)
    return np.concatenate((n.ravel(), ma.ravel())) 

Questo restituisce nans all'inizio.
Adam Erickson,

0

media mobile

  • inverte la matrice in i, e prendete semplicemente la media da i a n.

  • usa la comprensione delle liste per generare mini array al volo.

x = np.random.randint(10, size=20)

def moving_average(arr, n):
    return [ (arr[:i+1][::-1][:n]).mean() for i, ele in enumerate(arr) ]
n = 5

moving_average(x, n)

0

Uso la soluzione della risposta accettata , leggermente modificata per avere la stessa lunghezza per l'output dell'input, oppure pandasla versione menzionata in un commento di un'altra risposta. Riassumo qui entrambi con un esempio riproducibile per riferimento futuro:

import numpy as np
import pandas as pd

def moving_average(a, n):
    ret = np.cumsum(a, dtype=float)
    ret[n:] = ret[n:] - ret[:-n]
    return ret / n

def moving_average_centered(a, n):
    return pd.Series(a).rolling(window=n, center=True).mean().to_numpy()

A = [0, 0, 1, 2, 4, 5, 4]
print(moving_average(A, 3))    
# [0.         0.         0.33333333 1.         2.33333333 3.66666667 4.33333333]
print(moving_average_centered(A, 3))
# [nan        0.33333333 1.         2.33333333 3.66666667 4.33333333 nan       ]

0

Confrontando la soluzione di seguito con quella che utilizza cumsum di numpy, questo impiega quasi la metà del tempo . Questo perché non è necessario passare attraverso l'intero array per eseguire il cumsum e quindi eseguire tutte le sottrazioni. Inoltre, il cumsum può essere " pericoloso " se l'array è enorme e il numero è enorme ( possibile overflow ). Certo, anche qui il pericolo esiste ma almeno si sommano solo i numeri essenziali.

def moving_average(array_numbers, n):
    if n > len(array_numbers):
      return []
    temp_sum = sum(array_numbers[:n])
    averages = [temp_sum / float(n)]
    for first_index, item in enumerate(array_numbers[n:]):
        temp_sum += item - array_numbers[first_index]
        averages.append(temp_sum / float(n))
    return averages
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.