Как рассчитать скользящую среднюю, не сохраняя счет и общее количество данных?
Я пытаюсь найти способ рассчитать скользящее кумулятивное среднее без сохранения количества и общих данных, полученных на данный момент.
Я придумал два алгоритма, но оба должны хранить счет:
- новое среднее = ((старый счет * старые данные) + следующие данные)/следующий счет
- новое среднее = старое среднее + (следующие данные - старое среднее)/следующее число
Проблема с этими методами состоит в том, что счет становится все больше и больше, что приводит к потере точности в результирующем среднем.
Первый метод использует старый счет и следующий счет, которые, очевидно, равны 1. Это заставило меня задуматься о том, что, возможно, есть способ удалить счет, но, к сожалению, я его еще не нашел. Это действительно дало мне немного больше, хотя, в результате второй метод, но все еще счет присутствует.
Возможно ли это, или я просто ищу невозможное?
Ответы
Ответ 1
Вы можете просто сделать:
double approxRollingAverage (double avg, double new_sample) {
avg -= avg / N;
avg += new_sample / N;
return avg;
}
Где N
- количество выборок, где вы хотите усреднить. Обратите внимание, что это приближение эквивалентно экспоненциальной скользящей средней. См. Расчет скользящего/скользящего среднего в C++.
Ответ 2
New average = old average * (n-1)/n + new value /n
Это предполагает, что счетчик изменяется только на одно значение. Если он изменяется на значения M, то:
new average = old average * (n-len(M))/n + (sum of values in M)/n).
Это математическая формула (я считаю, самый эффективный), полагаю, что вы можете сделать следующий код сами.
Ответ 3
Из блога по выполнению выборочных расчетов дисперсии, где среднее значение также рассчитывается по методу Уэлфорда:
![enter image description here]()
Жаль, что мы не можем загрузить изображения SVG.
Ответ 4
Здесь еще один ответ, предлагающий комментарий о том, что ответы Муиса, Абдуллы Аль-Агеля и Флип - математически одно и то же, за исключением того, что они написаны по-разному.
Конечно, у нас есть анализ Хосе Мануэля Рамоса, объясняющий, как ошибки округления влияют на каждую из них по-разному, но это зависит от реализации и будет меняться в зависимости от того, как каждый ответ был применен к коду.
Однако есть довольно большая разница
Это в MUIS N
, Флип k
, и Абдулла аль-Ageel n
. Абдулла Аль-Агил не совсем объясняет, каким должно быть n
, но N
и k
отличаются тем, что N
- это "количество выборок, по которым вы хотите усреднить", а k
- это число значений, отобранных. (Хотя я сомневаюсь в том, что точное количество названий N
называется точным.)
И тут мы подходим к ответу ниже. По сути, это то же самое старое экспоненциально-взвешенное скользящее среднее, что и остальные, поэтому, если вы искали альтернативу, остановитесь прямо здесь.
Экспоненциально взвешенная скользящая средняя
Первоначально:
average = 0
counter = 0
Для каждого значения:
counter += 1
average = average + (value - average) / min(counter, FACTOR)
Разница - это min(counter, FACTOR)
часть. Это то же самое, что сказать min(Flip k, Muis N)
.
FACTOR
- это константа, которая влияет на то, как быстро среднее число "догоняет" последнюю тенденцию. Чем меньше число, тем быстрее. (При 1
он больше не является средним и просто становится последним значением.)
Этот ответ требует противоречили counter
. В случае проблем min(counter, FACTOR)
можно заменить просто FACTOR
, превратив его в ответ Muis. Проблема с этим заключается в том, что на скользящее среднее влияет то, на что инициализируется average
значение. Если он был инициализирован в 0
, этот ноль может занять много времени, чтобы выйти из среднего значения.
Как это выглядит
![Exponential moving average]()
Ответ 5
Ответ Flip в вычислительном отношении более последовательный, чем Muis.
Используя формат двойного числа, вы можете увидеть проблему округления в подходе Muis:
![The Muis approach]()
Когда вы делите и вычитаете, округление появляется в предыдущем сохраненном значении, изменяя его.
Однако подход Flip сохраняет сохраненное значение и уменьшает количество делений, следовательно, уменьшая округление и минимизируя ошибку, распространяющуюся до сохраненного значения. Добавление только вызовет округления, если есть что добавить (когда N большое, добавить нечего)
![The Flip approach]()
Эти изменения замечательны, когда среднее значение для больших значений стремится к нулю.
Я показываю вам результаты, используя программу для работы с электронными таблицами:
Во-первых, полученные результаты: ![Results]()
Столбцы A и B являются значениями n и X_n соответственно.
Столбец C является подходом Flip, а столбец D - подходом Muis, результат сохраняется в среднем. Столбец E соответствует среднему значению, используемому в вычислениях.
График, показывающий среднее четных значений, следующий:
![Graph]()
Как видите, между обоими подходами есть большие различия.
Ответ 6
Пример использования javascript для сравнения:
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);
})();
Ответ 7
В Java8:
LongSummaryStatistics movingAverage = new LongSummaryStatistics();
movingAverage.accept(new data);
...
average = movingAverage.getAverage();
у вас также есть IntSummaryStatistics
, DoubleSummaryStatistics
...