如何计算移动平均线而不保留计数和数据总数?

use*_*674 105 moving-average

我试图找到一种计算移动累积平均值的方法,而不存储到目前为止收到的计数和总数据.

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

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

这些方法的问题在于计数越来越大,导致平均值的精度下降.

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

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

Mui*_*uis 86

你可以简单地做:

double approxRollingAverage (double avg, double new_sample) {

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

    return avg;
}
Run Code Online (Sandbox Code Playgroud)

哪里N是要平均值的样本数量.请注意,此近似值等于指数移动平均值.请参阅:使用C++计算滚动/移动平均值

  • 这不完全正确.@Muis描述的是一个指数加权的移动平均,有时候是合适的,但并不是OP所要求的.例如,考虑大多数点在2到4范围内时所期望的行为,但是一个值超过一百万.EWMA(这里)将在相当长的一段时间内保持这一百万的痕迹.如OP所示,有限卷积将在N步之后立即丢失.它确实具有恒定存储的优势. (14认同)
  • 那不是移动平均线.你所描述的是一个单极点滤波器,它可以对信号中的跳跃产生指数响应.移动平均线创建长度为N的线性响应. (8认同)
  • @DanDascalescu 这是假设 `avg` 被初始化为 0。如果你将它初始化为第一个元素,它的行为会好得多。在您的示例中,它将一直为 5。 (4认同)
  • 你不必在此行前加1到N吗?avg + = new_sample/N; (3认同)
  • 注意,这与平均值的一般定义相差很远。如果您将N = 5并输入5个“ 5”个样本,则平均值为0.67。 (2认同)
  • @DanDascalescu尽管您正确地认为它实际上不是滚动平均值,但是您指定的值偏离了一个数量级。在将“ avg”初始化为“ 0”的情况下,您在5个“ 5”之后最终得到“ 3.36”,而在10个之后则最终获得“ 4.46”:http://cpp.sh/2ryql对于长平均值,这当然是一个有用的近似值。 (2认同)

小智 72

New average = old average * (n-1)/n + new value /n
Run Code Online (Sandbox Code Playgroud)

这假设计数仅改变一个值.如果它被M值改变,那么:

new average = old average * (n-len(M))/n + (sum of values in M)/n).
Run Code Online (Sandbox Code Playgroud)

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

  • 第一个效率略高:`new_average =(old_average*(n-1)+ new_value)/ n` - 删除其中一个除法. (5认同)
  • 当我实现这个方程时,值或运行平均值总是缓慢增加。它永远不会下降——只会上升。 (2认同)

Fli*_*lip 26

博客上运行样本方差计算,其中均值也使用Welford的方法计算:

在此输入图像描述

太糟糕了,我们无法上传SVG图像.

  • 这与Muis实施的类似,不同之处在于鸿沟是一个共同因素.因此只有一个师. (3认同)
  • 它实际上更接近@Abdullah-Al-Ageel(本质上是交换数学),因为 Muis 不考虑递增 N;复制粘贴公式参考:[Avg at n] = [Avg at n-1] + (x - [Avg at n-1]) / n (2认同)
  • @Flip&drwaus:Muis和Abdullah Al-Ageel的解决方案不是完全一样吗?这是相同的计算,只是编写方式不同。对我而言,这三个答案是相同的,这个答案更直观(很遗憾,我们不能在SO上使用MathJax)。 (2认同)

ant*_*tak 13

这里有一个关于如何另一个答案提供评论的MUI,阿卜杜拉Ageel翻转的回答是所有数学上同样的事情,除了有不同的写法.

当然,我们有JoséManuelRamos的分析,解释了舍入错误对每个错误的影响有多么不同,但这依赖于实现,并且会根据每个答案如何应用于代码而改变.

然而,有一个相当大的差异

这是在MuisN,FlipkAbdullah Al-Ageeln. 阿卜杜拉Ageel并不完全解释什么n是应该的,但Nk在不同的N是" 在要平均值的样本数 ",而k采样值的计数.(虽然我怀疑是否准确调用N 样本数量.)

在这里,我们来到下面的答案.它基本上与其他指数加权移动平均值相同,因此如果您正在寻找替代方案,请立即停在此处.

指数加权移动平均线

原来:

average = 0
counter = 0
Run Code Online (Sandbox Code Playgroud)

对于每个值:

counter += 1
average = average + (value - average) / min(counter, FACTOR)
Run Code Online (Sandbox Code Playgroud)

不同之处在于min(counter, FACTOR)部分.这跟说的一样min(Flip's k, Muis's N).

FACTOR是一个常数,它影响平均"赶上"到最新趋势的速度.数字越小越快.(1它不再是平均值,只是变成最新值.)

这个答案需要运行计数器counter.如果有问题,min(counter, FACTOR)可以用Just替换FACTOR,把它变成Muis的答案.这样做的问题是移动平均线受average初始化的影响.如果它被初始化为0,那么零可能需要很长时间才能超出平均值.

它最终看起来如何

指数移动平均线

  • 好解释。我只是想念您图表中的纯平均值,因为OP要求了这一点。 (3认同)

Jos*_*mos 7

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

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

Muis方法

除法和减法时,前一个存储值中会出现一个舍入值,并对其进行更改.

但是,Flip方法会保留存储的值并减少分割数量,从而减少舍入,并最大限度地减少传播到存储值的误差.如果有要添加的东西,只添加将导致舍入(当N很大时,没有什么可添加的)

翻转方法

当你将大值的意思倾向于它们的意思为零时,这些变化是显着的.

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

首先,得到的结果: 结果

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

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

显示偶数值均值的图表是下一个:

图形

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

  • 不是一个真正的答案,但有用的信息.如果您在图表中添加第3行,对于*n*过去值的真实平均值,会更好,因此我们可以看到两种方法中哪一种最接近. (2认同)
  • @jpaugh:B列在-1.00E + 15和1.00E + 15之间交替,因此,当N为偶数时,实际均值应为0。图的标题为“偶数均值”。这意味着您要询问的第三行只是f(x)= 0。该图显示,这两种方法都会引入误差,误差会不断上升。 (2认同)
  • 您图表的图例颜色错误:Muis 的为橙色,Flip 的为蓝色。 (2认同)

drz*_*aus 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;
}
Run Code Online (Sandbox Code Playgroud)

(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);
})();
Run Code Online (Sandbox Code Playgroud)


Dim*_*iev 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)
Run Code Online (Sandbox Code Playgroud)

用法:

x = RunningAverage()
x(0)
x(2)
x(4)
print(x)
Run Code Online (Sandbox Code Playgroud)