Come calcolare la media mobile senza mantenere il conteggio e il totale dei dati?


118

Sto cercando di trovare un modo per calcolare una media cumulativa mobile senza memorizzare il conteggio e i dati totali ricevuti finora.

Ho trovato due algoritmi ma entrambi devono memorizzare il conteggio:

  • nuova media = ((vecchio conteggio * vecchi dati) + dati successivi) / conteggio successivo
  • nuova media = vecchia media + (dati successivi - vecchia media) / conteggio successivo

Il problema con questi metodi è che il conteggio diventa sempre più grande con conseguente perdita di precisione nella media risultante.

Il primo metodo utilizza il vecchio conteggio e il successivo conteggio che sono ovviamente 1 separati. Questo mi ha fatto pensare che forse c'è un modo per rimuovere il conteggio ma purtroppo non l'ho ancora trovato. Tuttavia, mi ha portato un po 'oltre, risultando nel secondo metodo ma il conteggio è ancora presente.

È possibile o sto solo cercando l'impossibile?


1
NB numericamente, memorizzare il totale e il conteggio corrente è il modo più stabile. Altrimenti, per conteggi superiori next / (next count) inizierà a underflow. Quindi, se sei davvero preoccupato di perdere la precisione, mantieni i totali!
AlexR

Risposte:


91

Puoi semplicemente fare:

double approxRollingAverage (double avg, double new_sample) {

    avg -= avg / N;
    avg += new_sample / N;

    return avg;
}

Dov'è Nil numero di campioni su cui vuoi calcolare la media. Nota che questa approssimazione è equivalente a una media mobile esponenziale. Vedi: Calcola la media mobile / mobile in C ++


3
Non devi aggiungere 1 a N in questo prima di questa riga? avg + = new_sample / N;
Damian

20
Questo non è del tutto corretto. Ciò che @Muis descrive è una media mobile ponderata esponenzialmente, che a volte è appropriata ma non è esattamente ciò che l'OP ha richiesto. Ad esempio, considera il comportamento che ti aspetti quando la maggior parte dei punti è compresa tra 2 e 4 ma un valore è superiore a un milione. Un EWMA (qui) manterrà le tracce di quel milione per un bel po 'di tempo. Una convoluzione finita, come indicato da OP, la perderebbe immediatamente dopo N passaggi. Ha il vantaggio di una conservazione costante.
jma

9
Non è una media mobile. Quello che descrivi è un filtro unipolare che crea risposte esponenziali ai salti nel segnale. Una media mobile crea una risposta lineare con lunghezza N.
ruhig brauner

3
Attenzione che questo è abbastanza lontano dalla definizione comune di media. Se si imposta N = 5 e si immettono 5 5campioni, la media sarà 0,67.
Dan Dascalescu

2
@DanDascalescu Anche se hai ragione sul fatto che non è effettivamente una media mobile, il tuo valore dichiarato è di un ordine di grandezza diverso. Con avginizializzato a 0, si finisce con 3.36dopo 5 5s e 4.46dopo 10: cpp.sh/2ryql Per medie lunghe, questa è certamente un'approssimazione utile.
cincodenada

80
New average = old average * (n-1)/n + new value /n

Ciò presuppone che il conteggio sia cambiato solo di un valore. Nel caso in cui venga modificato dai valori M, allora:

new average = old average * (n-len(M))/n + (sum of values in M)/n).

Questa è la formula matematica (credo la più efficiente), credo che tu possa fare ulteriore codice da solo


Qual è la somma del nuovo valore? è diverso in qualche modo dal "nuovo valore" nella tua formula originale?
Mikhail

@ Mikhail nel secondo esempio, ci sono mnuovi valori presi in considerazione nella nuova media. Credo che sum of new valuequi si intenda essere la somma dei mnuovi valori utilizzati per calcolare la nuova media.
Patrick Goley

9
Leggermente più efficiente per il primo: new_average = (old_average * (n-1) + new_value) / n- Rimuove una delle divisioni.
Pixelstix

Che ne dici di una media di 3 elementi con 6,0,0,9?
Roshan Mehta

1
Quando implemento questa equazione, il valore o la media corrente aumenta sempre lentamente. Non scende mai, solo su.
anon58192932

30

Da un blog sull'esecuzione di calcoli della varianza campione, in cui anche la media viene calcolata utilizzando il metodo di Welford :

inserisci qui la descrizione dell'immagine

Peccato che non possiamo caricare immagini SVG.


3
Questo è simile a quanto implementato da Muis, tranne per il fatto che il divario è utilizzato come fattore comune. Quindi solo una divisione.
Sfoglia il

In realtà è più vicino a @ Abdullah-Al-Ageel (essenzialmente matematica commutativa) in quanto Muis non tiene conto dell'incremento di N; riferimento formula copia-incolla: [Avg at n] = [Avg at n-1] + (x - [Avg at n-1]) / n
drzaus

2
@Flip & drwaus: le soluzioni di Muis e Abdullah Al-Ageel non sono esattamente le stesse? È lo stesso calcolo, solo scritto in modo diverso. Per me quelle 3 risposte sono identiche, questa è più visiva (peccato che non possiamo usare MathJax su SO).
user276648

21

Ecco un'altra risposta che offre commenti su come Muis , Abdullah Al-Ageel e la risposta di Flip siano matematicamente la stessa cosa tranne che scritte in modo diverso.

Certo, abbiamo José Manuel Ramos analisi di che spiega come gli errori di arrotondamento influiscano su ciascuno in modo leggermente diverso, ma dipende dall'implementazione e cambierebbe in base a come ogni risposta è stata applicata al codice.

C'è tuttavia una differenza piuttosto grande

È in Muis 's N, Flip 's ke Abdullah Al-Ageel 's n. Abdullah Al-Ageel non spiega esattamente cosa ndovrebbe essere, ma Nek differisce in questo Nè " il numero di campioni su cui si desidera fare la media " mentre kè il conteggio dei valori campionati. (Anche se ho dubbi sul fatto che chiamare N il numero di campioni sia accurata.)

E qui arriviamo alla risposta di seguito. È essenzialmente la stessa vecchia media mobile ponderata esponenziale degli altri, quindi se stavi cercando un'alternativa, fermati qui.

Media mobile ponderata esponenziale

inizialmente:

average = 0
counter = 0

Per ogni valore:

counter += 1
average = average + (value - average) / min(counter, FACTOR)

La differenza è il file min(counter, FACTOR) parte. Questo è come dire min(Flip's k, Muis's N).

FACTORè una costante che influisce sulla velocità con cui la media "raggiunge" l'ultima tendenza. Più piccolo è il numero, più velocemente. (A1 Non è più una media e diventa solo l'ultimo valore.)

Questa risposta richiede il contatore in esecuzione counter. Se problematico, min(counter, FACTOR)può essere sostituito con solo FACTOR, trasformandolo nella risposta di Muis . Il problema con questa operazione è che la media mobile è influenzata da qualsiasi cosaaverage venga inizializzata. Se è stato inizializzato su 0, quello zero può impiegare molto tempo per uscire dalla media.

Come va a finire

Media mobile esponenziale


3
Ben spiegato. Mi manca solo una media semplice nel tuo grafico, perché è quello che ha chiesto OP.
xmedeko

Forse mi sto perdendo qualcosa, ma tu, per caso, intendi max(counter, FACTOR). min(counter, FACTOR)restituirà sempre FACTOR, giusto?
WebWanderer

1
Credo che il punto min(counter, FACTOR)sia quello di tenere conto del periodo di riscaldamento. Senza di esso, se il tuo FATTORE (o N, o il numero di campioni desiderato) è 1000, avrai bisogno di almeno 1000 campioni prima di ottenere un risultato accurato, poiché tutti gli aggiornamenti precedenti presupporranno che tu abbia 1000 campioni, quando puoi solo hanno 20.
rharter

Sarebbe bello smettere di contare dopo aver raggiunto il fattore, probabilmente sarebbe più veloce in questo modo.
inf3rno

8

La risposta di Flip è computazionalmente più coerente di quella di Muis.

Utilizzando il formato doppio numero, potresti vedere il problema dell'arrotondamento nell'approccio Muis:

L'approccio di Muis

Quando si divide e si sottrae, viene visualizzato un arrotondamento nel valore memorizzato precedente, modificandolo.

Tuttavia, l'approccio Flip preserva il valore memorizzato e riduce il numero di divisioni, riducendo quindi l'arrotondamento e minimizzando l'errore propagato al valore memorizzato. L'aggiunta solo farà apparire gli arrotondamenti se c'è qualcosa da aggiungere (quando N è grande, non c'è niente da aggiungere)

L'approccio Flip

Questi cambiamenti sono notevoli quando si fa in modo che una media di valori grandi tenda la loro media a zero.

Ti mostro i risultati utilizzando un programma di fogli di calcolo:

In primo luogo, i risultati ottenuti: risultati

Le colonne A e B sono rispettivamente i valori ne X_n.

La colonna C è l'approccio Flip e quella D è l'approccio Muis, il risultato memorizzato nella media. La colonna E corrisponde al valore medio utilizzato nel calcolo.

Un grafico che mostra la media dei valori pari è il prossimo:

Grafico

Come puoi vedere, ci sono grandi differenze tra i due approcci.


2
Non proprio una risposta, ma informazioni utili. Sarebbe ancora meglio se aggiungessi la terza linea al grafico, per la media reale su n valori passati, in modo da poter vedere quale dei due approcci si avvicina di più.
jpaugh

2
@jpaugh: La colonna B si alterna tra -1.00E + 15 e 1.00E + 15, quindi quando N è pari, la media effettiva dovrebbe essere 0. Il titolo del grafico è "Medie parziali pari". Ciò significa che la terza riga di cui chiedi è semplicemente f (x) = 0. Il grafico mostra che entrambi gli approcci introducono errori che continuano a salire.
desowin

È corretto, il grafico mostra esattamente l'errore propagato utilizzando i grandi numeri coinvolti nei calcoli utilizzando entrambi gli approcci.
José Manuel Ramos

La legenda del tuo grafico ha colori sbagliati: quello di Muis è arancione, quello di Flip è blu.
xmedeko

6

Un esempio utilizzando javascript, per il confronto:

https://jsfiddle.net/drzaus/Lxsa4rpz/

function calcNormalAvg(list) {
    // sum(list) / len(list)
    return list.reduce(function(a, b) { return a + b; }) / list.length;
}
function calcRunningAvg(previousAverage, currentNumber, index) {
    // [ avg' * (n-1) + x ] / n
    return ( previousAverage * (index - 1) + currentNumber ) / index;
}


1

In Java8:

LongSummaryStatistics movingAverage = new LongSummaryStatistics();
movingAverage.accept(new data);
...
average = movingAverage.getAverage();

hai anche IntSummaryStatistics, DoubleSummaryStatistics...


2
OP richiede un algoritmo, non un puntatore su come calcolarlo in Java.
olq_plo

0

Una soluzione Python pulita basata sulle risposte precedenti:

class RunningAverage():
    def __init__(self):
        self.average = 0
        self.n = 0
        
    def __call__(self, new_value):
        self.n += 1
        self.average = (self.average * (self.n-1) + new_value) / self.n 
        
    def __float__(self):
        return self.average
    
    def __repr__(self):
        return "average: " + str(self.average)

utilizzo:

x = RunningAverage()
x(0)
x(2)
x(4)
print(x)
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.