如何在不保持计数和数据总计的情况下计算移动平均线?


118

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

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

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

这些方法的问题在于计数越来越大,导致所得平均值失去精度。

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

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


1
注意:从数字上讲,存储当前总数和当前计数是最稳定的方法。否则,对于更高的计数,下一个/(下一个计数)将开始下溢。因此,如果您真的担心精度下降,请保留总计!
AlexR

Answers:


91

您可以简单地执行以下操作:

double approxRollingAverage (double avg, double new_sample) {

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

    return avg;
}

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


3
您不是必须在此行之前在N中加1吗?平均+ = new_sample / N;
达米安

20
这并不完全正确。@Muis所描述的是指数加权移动平均值,有时是适当的,但并不完全是OP所要求的。例如,请考虑当大多数点在2到4范围内但一个值超过一百万时您期望的行为。EWMA(在此处)将在相当长的一段时间内保留上百万的痕迹。如OP所示,有限卷积将在N步后立即丢失。它确实具有恒定存储的优点。
jma

9
那不是移动平均线。您所描述的是一个单极点滤波器,可对信号中的跳跃产生指数响应。移动平均创建具有长度N的线性响应
ruhig布劳纳

3
注意,这与平均值的一般定义相差很远。如果设置N = 5并输入5个5样本,则平均值将为0.67。
Dan Dascalescu '18年

2
@DanDascalescu尽管您正确地认为它实际上不是滚动平均值,但是您指定的值偏离了一个数量级。随着avg初始化0,你结束了3.36之后5 5秒,4.46之后10:cpp.sh/2ryql对于长期平均水平,这无疑是一个有用的近似值。
cincodenada

80
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).

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


什么是新值总和?这与您原始公式中的“新价值”有所不同吗?
Mikhail

在第二个示例中,@ Mikhail将m新值纳入新的平均值中。我相信sum of new value这里是指m用于计算新平均值的新值的总和。
Patrick Goley

9
对于第一个,效率稍高:new_average = (old_average * (n-1) + new_value) / n-消除了一个分隔。
Pixelstix

分别用6,0,0,9的3个元素运行平均值如何?
Roshan Mehta

1
当我执行此方程式时,值或移动平均值总是缓慢增加。它永远不会下降-只会上升。
anon58192932

30

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

在此处输入图片说明

太遗憾了,我们无法上传SVG图片。


3
这与Muis实现的方法类似,除了使用除数作为公共因素。因此只有一个师。
翻转

实际上,它更接近@ Abdullah-Al-Ageel(本质上是可交换数学),因为Muis并不能说明N的增加。复制粘贴公式参考:[n处的平均值] = [n-1处的平均值] +(x-[n-1处的平均值])/ n
drzaus

2
@Flip&drwaus:Muis和Abdullah Al-Ageel的解决方案不是完全一样吗?这是相同的计算,只是编写方式不同。对我来说,这三个答案是相同的,这个答案更直观(很遗憾,我们不能在SO上使用MathJax)。
user276648 '16

21

下面是另一个关于如何回答产品评论的MUI阿卜杜拉Ageel翻转的回答是所有数学上同样的事情,除了有不同的写法。

当然,我们有何塞·曼努埃尔·拉莫斯(JoséManuel Ramos)的分析解释了舍入误差对每个误差的影响略有不同,但这取决于实现方式,并且会根据每个答案应用于代码的方式而变化。

但是有很大的不同

这是一个在MUISN翻盖k,和阿卜杜拉Ageeln阿卜杜拉Ageel并不完全解释什么n是应该的,但Nk在不同的N是“ 要平均值的样本数量 ”,同时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,则零可能需要很长时间才能超出平均值。

最终看起来如何

指数移动平均线


3
好解释。我只是想念您图表中的纯平均值,因为那是OP所要求的。
xmedeko

也许我想念一些东西,但是你是不是偶然地故意max(counter, FACTOR)min(counter, FACTOR)总是会返回FACTOR,对吧?
WebWanderer

1
我相信的重点min(counter, FACTOR)是说明预热期。没有它,如果您的FACTOR(或N,或所需的样本数)为1000,那么您至少需要1000个样本才能获得准确的结果,因为在此之前的所有更新都将假定您有1000个样本,有20
rharter

达到该因子后停止计数可能会很好,也许那样会更快。
inf3rno

8

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

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

Muis方法

当您进行除法和减法运算时,四舍五入会出现在先前的存储值中,并对其进行更改。

但是,翻转方法保留了存储的值并减少了除法数,因此减少了舍入,并使传播到存储值的误差最小。如果要添加某些内容,则仅添加将导致舍入(当N大时,无需添加任何内容)

翻转方法

当您使用大值的平均值将其均值趋于零时,这些变化就非常明显。

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

首先,获得的结果: 结果

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

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

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

图形

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


2
并不是真正的答案,而是有用的信息。如果将第三条线添加到图形中,则将是更好的选择,以获取n个过去值的真实平均值,因此我们可以看到两种方法中哪一种最接近。
jpaugh

2
@jpaugh:B列在-1.00E + 15和1.00E + 15之间交替,因此当N为偶数时,实际均值应为0。图的标题为“偶数均值”。这意味着您要询问的第三行只是f(x)= 0。该图显示,这两种方法都会引入误差,误差会不断上升。
desowin

没错,该图准确显示了使用这两种方法进行计算所涉及的大量数字所传播的误差。
何塞·曼努埃尔·拉莫斯(JoséManuel Ramos)

图形的图例颜色错误:Muis的颜色为橙色,Flip的颜色为蓝色。
xmedeko

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;
}


1

在Java8中:

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

你也有IntSummaryStatisticsDoubleSummaryStatistics...


2
OP要求的是算法,而不是指针,该如何在Java中进行计算。
olq_plo

0

一个基于上述答案的简洁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)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.