144

我正在尝试找到一种方法来计算移动累积平均值,而不存储迄今为止收到的计数和总数据。

我想出了两种算法,但都需要存储计数:

  • 新平均值 = ((旧计数 * 旧数据) + 下一个数据) / 下一个计数
  • 新平均值 = 旧平均值 +(下一个数据 - 旧平均值)​​/下一个计数

这些方法的问题是计数越来越大,导致结果平均值的精度下降。

第一种方法使用明显相差 1 的旧计数和下一个计数。这让我想到也许有一种方法可以删除计数,但不幸的是我还没有找到它。它确实让我更进一步,导致第二种方法,但仍然存在计数。

有可能吗,还是我只是在寻找不可能的事情?

4

8 回答 8

99

你可以简单地做:

double approxRollingAverage (double avg, double new_sample) {

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

    return avg;
}

N您想要平均的样本数量在哪里。请注意,此近似值等效于指数移动平均线。请参阅:在 C++ 中计算滚动/移动平均值

于 2013-05-26T08:49:41.687 回答
88
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).

这是数学公式(我相信是最有效的),相信你可以自己做进一步的代码

于 2014-05-06T11:41:16.917 回答
35

来自关于运行样本方差计算的博客,其中平均值也是使用Welford 方法计算的

在此处输入图像描述

太糟糕了,我们不能上传 SVG 图像。

于 2016-06-15T08:35:47.910 回答
31

这是另一个答案,提供了关于MuisAbdullah Al-AgeelFlip的答案在数学上都是相同的东西的评论,只是写法不同。

当然,我们有José Manuel Ramos的分析解释了舍入误差对每个错误的影响略有不同,但这取决于实现,并且会根据每个答案应用于代码的方式而改变。

不过还是有很大区别的

它在MuisNFlipkAbdullah Al-AgeelnAbdullah Al-Ageel并没有完全解释n应该是什么,但N不同kN是“你想要平均的样本数”,而k采样值的计数。(虽然我怀疑调用N 样本数量是否准确。)

在这里,我们得出下面的答案。它基本上与其他旧指数加权移动平均线相同,因此如果您正在寻找替代方案,请在此处停止。

指数加权移动平均线

最初:

average = 0
counter = 0

对于每个值:

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

区别在于min(counter, FACTOR)部分。这和说的一样min(Flip's k, Muis's N)

FACTOR是一个常数,它影响平均“赶上”最新趋势的速度。数字越小速度越快。(此时1它不再是平均值,而是成为最新值。)

这个答案需要运行计数器counter。如果有问题,min(counter, FACTOR)可以将其替换为 just FACTOR,将其变成Muis的答案。这样做的问题是移动平均线受到average初始化的任何影响。如果将其初始化为0,则该零可能需要很长时间才能超出平均值。

它最终看起来如何

指数移动平均线

于 2018-06-14T09:35:30.600 回答
12

Flip 的答案在计算上比 Muis 的答案更一致。

使用双数格式,您可以看到 Muis 方法中的舍入问题:

缪斯方法

当您进行除法和减法时,会在先前存储的值中出现舍入,从而更改它。

但是,翻转方法保留了存储值并减少了除法次数,因此减少了舍入,并最小化了传播到存储值的误差。如果有要添加的内容,则仅添加会带来舍入(当 N 很大时,无需添加任何内容)

翻转方法

当你对大值取平均值时,这些变化是显着的,它们的平均值趋于零。

我使用电子表格程序向您展示结果:

首先,得到的结果:结果

A 和 B 列分别是 n 和 X_n 值。

C 列是 Flip 方法,D 列是 Muis 方法,结果存储在均值中。E 列对应于计算中使用的中间值。

下一张显示偶数平均值的图表:

图形

如您所见,两种方法之间存在很大差异。

于 2017-10-11T07:54:59.103 回答
10

一个使用 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);
})();

于 2016-08-11T15:57:35.683 回答
5

基于上述答案的简洁 Python 解决方案:

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)

用法:

x = RunningAverage()
x(0)
x(2)
x(4)
print(x)
于 2020-07-07T05:21:25.117 回答
2

在 Java8 中:

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

你也有IntSummaryStatisticsDoubleSummaryStatistics...

于 2019-01-09T16:53:15.033 回答