J'essaie de trouver un moyen de calculer une moyenne cumulative mobile sans stocker le nombre et le total des données reçues jusqu'à présent.
Je suis venu avec deux algorithmes mais les deux ont besoin de stocker le compte:
Le problème avec ces méthodes est que le nombre devient de plus en plus grand, ce qui entraîne une perte de précision dans la moyenne résultante.
La première méthode utilise l’ancien compte et le compte suivant, qui sont évidemment égaux à 1. Cela m'a fait penser qu'il est peut-être possible de supprimer le compte, mais malheureusement je ne l'ai pas encore trouvé. Cela m'a cependant amené un peu plus loin, ce qui a abouti à la deuxième méthode, mais le compte est toujours présent.
Est-ce possible ou suis-je en train de chercher l'impossible?
Vous pouvez simplement faire:
double approxRollingAverage (double avg, double new_sample) {
avg -= avg / N;
avg += new_sample / N;
return avg;
}
Où N
est le nombre d'échantillons pour lesquels vous souhaitez établir une moyenne. Notez que cette approximation est équivalente à une moyenne mobile exponentielle. Voir: Calculer la moyenne roulante/mobile en C++
New average = old average * (n-1)/n + new value /n
Cela suppose que le nombre ne soit modifié que par une valeur. Dans le cas où il est modifié par M valeurs, alors:
new average = old average * (n-len(M))/n + (sum of values in M)/n).
C’est la formule mathématique (je crois la plus efficace), croyez que vous pouvez faire plus de code par vous-même
À partir de n blog sur les calculs de variance d'échantillon en cours, où la moyenne est également calculée à l'aide de méthode de Welford :
Dommage que nous ne puissions pas télécharger des images SVG.
Voici encore une autre réponse offrant des commentaires sur la façon dont Muis, = Abdullah Al-Ageel et Flip La réponse est mathématiquement la même chose sauf écrit différemment.
Certes, nous avons l’analyse de José Manuel Ramos expliquant comment les erreurs d’arrondi affectent chaque différend légèrement, mais cela dépend de l’implémentation et peut changer en fonction de la manière dont chaque réponse est appliquée au code.
C'est dans Muis's N
, Flip's k
, et Abdullah Al-Ageel 's n
. Abdullah Al-Ageel n'explique pas vraiment ce que devrait être n
, mais N
et k
diffèrent par le fait que N
est " le nombre d'échantillons sur lequel vous voulez faire la moyenne" tandis que k
est le nombre de valeurs échantillonnées. (Bien que je doute de savoir si l'appel de N
le nombre d'échantillons est exact.)
Et nous arrivons à la réponse ci-dessous. C'est essentiellement le même vieux moyenne mobile pondérée exponentielle que les autres, donc si vous cherchez une alternative, arrêtez ici.
Initialement:
average = 0
counter = 0
Pour chaque valeur:
counter += 1
average = average + (value - average) / min(counter, FACTOR)
La différence est la partie min(counter, FACTOR)
. Cela revient à dire min(Flip's k, Muis's N)
.
FACTOR
est une constante qui affecte la rapidité avec laquelle la moyenne "rattrape" la dernière tendance. Plus le nombre est petit, plus vite. (À 1
, Ce n'est plus une moyenne et devient simplement la dernière valeur.)
Cette réponse nécessite le compteur en cours d'exécution counter
. Si problématique, la min(counter, FACTOR)
peut être remplacée par la simple FACTOR
, en la transformant en réponse Muis. Le problème avec ceci est que la moyenne mobile est affectée par tout ce à quoi average
est initialisé. S'il a été initialisé à 0
, Ce zéro peut prendre beaucoup de temps pour sortir de la moyenne.
La réponse de Flip est numériquement plus cohérente que celle de Muis.
En utilisant le format à double numéro, vous pouvez voir le problème d'arrondi dans l'approche Muis:
Lorsque vous divisez et soustrayez, un arrondi apparaît dans la valeur stockée précédente, en le modifiant.
Cependant, l'approche Flip préserve la valeur stockée et réduit le nombre de divisions, réduisant ainsi l'arrondi et minimisant l'erreur propagée à la valeur stockée. Ajouter seulement fera apparaître des arrondis s'il y a quelque chose à ajouter (quand N est grand, il n'y a rien à ajouter)
Ces changements sont remarquables lorsque vous faites une moyenne de grandes valeurs tendent leur moyenne à zéro.
Je vous montre les résultats à l'aide d'un tableur:
Premièrement, les résultats obtenus:
Les colonnes A et B sont les valeurs n et X_n, respectivement.
La colonne C correspond à l’approche Flip et la première à l’approche Muis, le résultat stocké dans la moyenne. La colonne E correspond à la valeur moyenne utilisée dans le calcul.
Le graphique suivant montre la moyenne des valeurs paires:
Comme vous pouvez le constater, il existe de grandes différences entre les deux approches.
Un exemple utilisant javascript, à titre de comparaison:
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;
}
(function(){
// populate base list
var list = [];
function getSeedNumber() { return Math.random()*100; }
for(var i = 0; i < 50; i++) list.Push( getSeedNumber() );
// our calculation functions, for comparison
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;
}
function calcMovingAvg(accumulator, new_value, alpha) {
return (alpha * new_value) + (1.0 - alpha) * accumulator;
}
// start our baseline
var baseAvg = calcNormalAvg(list);
var runningAvg = baseAvg, movingAvg = baseAvg;
console.log('base avg: %d', baseAvg);
var okay = true;
// table of output, cleaner console view
var results = [];
// add 10 more numbers to the list and compare calculations
for(var n = list.length, i = 0; i < 10; i++, n++) {
var newNumber = getSeedNumber();
runningAvg = calcRunningAvg(runningAvg, newNumber, n+1);
movingAvg = calcMovingAvg(movingAvg, newNumber, 1/(n+1));
list.Push(newNumber);
baseAvg = calcNormalAvg(list);
// assert and inspect
console.log('added [%d] to list at pos %d, running avg = %d vs. regular avg = %d (%s), vs. moving avg = %d (%s)'
, newNumber, list.length, runningAvg, baseAvg, runningAvg == baseAvg, movingAvg, movingAvg == baseAvg
)
results.Push( {x: newNumber, n:list.length, regular: baseAvg, running: runningAvg, moving: movingAvg, eqRun: baseAvg == runningAvg, eqMov: baseAvg == movingAvg } );
if(runningAvg != baseAvg) console.warn('Fail!');
okay = okay && (runningAvg == baseAvg);
}
console.log('Everything matched for running avg? %s', okay);
if(console.table) console.table(results);
})();
En Java8:
LongSummaryStatistics movingAverage = new LongSummaryStatistics();
movingAverage.accept(new data);
...
average = movingAverage.getAverage();
vous avez aussi IntSummaryStatistics
, DoubleSummaryStatistics
...