23

我想知道是否有一种算法可以计算未绑定数据集的平均值和标准差。

例如,我正在监控一个测量值,比如电流。我想获得所有历史数据的平均值。每当有新值出现时,更新均值和标准差?因为数据太大而无法存储,我希望它可以在不存储数据的情况下即时更新均值和标准差。

即使存储数据,标准方式(d1 + ... + dn)/n也不起作用,总和会破坏数据表示。

我通过大约 sum(d1/n + d2/n + ... d3/n),如果 n 是休,则误差太大并累积。此外,在这种情况下,n 是未绑定的。

数据的数量肯定是无限制的,无论什么时候来,都需要更新值。

有人知道是否有算法吗?

4

5 回答 5

19

【问题变了吗?也许我只看了开头。我已更新/编辑以提供更好的答复:]

我知道没有完美的解决方案(在不断的记忆中),但我可以提供各种方法。

首先,对于基本计算,您只需要所有值sum_x的总和 ( )、它们的平方和 ( sum_x2) 和总计数 ( n)。然后:

mean = sum_x / n
stdev = sqrt( sum_x2/n - mean^2 )

并且所有这些值 ( sum_x, sum_x2, n) 都可以从流中更新。

问题(如您所说)是处理溢出和/或有限的精度。如果您考虑浮点何时sum_x2如此之大以至于内部表示不包括单个平方值的大小值,您可以看到这一点。

避免该问题的一种简单方法是使用精确算术,但这会越来越慢(并且还使用 O(log(n)) 内存)。

另一种可以提供帮助的方法是规范化值 - 如果您知道值通常是X那么您可以进行计算x - X,使总和更小(显然您然后添加X到平均值!)。这有助于推迟精度损失点(并且可以/应该与此处的任何其他方法结合使用 - 例如,在分箱时,您可以使用前一个箱的平均值)。有关如何逐步执行此操作,请参阅此算法(knuth 的方法) 。

如果您不介意(小常数因子)O(n)内存成本,您可以重新启动每个N值(例如百万 - 更聪明的方法仍然是通过检测精度何时过低来适应此值),存储先前的平均值和标准差和然后结合最终结果(因此您的平均值是最近运行总数和旧分箱值的适当加权值)。

分箱方法可能可以推广到多个级别(您开始分箱)并且会减少到 O(log(n)) 内存使用,但我还没有制定出细节。

最后,一个更实际的解决方案可能是对 1000 个值执行初始方法,然后并行开始新的求和。您可以显示两者的加权平均值,然后在另外 1000 个值之后,删除旧的总和(在逐渐减少它们的权重之后)并开始一个新的集合。所以你总是有两组总和,并显示它们之间的加权平均值,这给出了反映最后 1000 个值的连续数据(仅)。在某些情况下,这已经足够好了,我想(这不是一个精确的值,因为它仅适用于“最近的”数据,但它是平滑且具有代表性的,并且使用了固定数量的内存)。

ps,后来我发生了一些事情-实际上,无论如何“永远”这样做并没有多大意义,因为您将到达一个值绝对由旧数据主导的地步。最好使用“运行均值”来减轻旧值的权重。参见例如https://en.wikipedia.org/wiki/Moving_average - 但是,我不知道标准开发的常见等价物。

于 2012-04-28T15:56:31.837 回答
5

有趣的问题。

让我们先讨论平均值,如果只是因为它更简单一点。

您对运行总计的舍入误差是正确的。对于足够大的数据集,它会降低您的准确性。您想数据进行预排序,先汇总小数据;但在你的情况下,这当然是不可能的。但是,您可以通过保持多个运行总计来获得排序数据的大部分好处。

C 或 C++ 风格的概念示例:

const double max_small  =    0.001;
const double max_medium = 1000.0;

double total_small;
double total_medium;
double total_large;

while(true) {
    const double datum = get_datum(); // (Use here whatever function you use to get a datum.)
    if (!is_datum_valid()) break;
    if (abs(datum) <= max_small) total_small += datum;
    else if (abs(datum) <= max_medium) total_medium += datum;
    else total_large += datum;
}

double total = 0.0;
total += total_small;
total += total_medium;
total += total_large;

在实际代码中,您可能会保留三个以上的运行总计——当然,您还将保持数据平方的运行总计——但上面的示例传达了这个想法。您可以填写详细信息。

此外,采用@andrewcooke 的想法,您可以通过以下方式扩展循环:

while(true) {
    const double datum = get_datum();
    if (!is_datum_valid()) break;
    if (abs(datum) <= max_small) {
        total_small += datum;
        if (abs(total_small) > max_medium) {
            total_large += total_small;
            total_small = 0.0;
        }
    }
    else if (abs(datum) <= max_medium) total_medium += datum;
    else total_large += datum;
}

同样,您可以填写详细信息。祝你好运。

附录:标准差的实际计算

此处的各种评论线程中提出了一个很好的问题,即如何在不事先了解平均值的情况下计算标准偏差。幸运的是,已知有一个技巧可以计算标准差。我准备了两页的笔记来解释这里的技巧(PDF)。

无论如何,在运行统计数据中包含标准差所需要做的就是不仅对数据求和,还对数据的平方求和;当然,正方形可以按照与数据本身类似的方式求和,遵循与上面代码中相同的模式。

于 2012-04-28T16:07:24.487 回答
4

不。

(我想:不,但我被证明是错误的)。

你可以携带总和和计数,这样

sum(i)=500, count(i)=50, => avg:=10
next value = 20
sum=520, count=51 => avg:= 10.19

但是 stddev 不能那样构建。您必须为每个值生成新平均值的增量,并将它们平方,然后除以 N。 但是:问题是,这些值是什么类型的值(从数学的角度来看 - 远离物理学! :) )。在正常情况下,我不希望值在 2000 个元素之后发生变化。否则,首先构建 mean 和 stddev 可能会有问题。

而对于 2000 个元素,应该可以快速计算该值。

也许您可以使用缓冲区,并始终为每 2000 个值计算最后 2000 个值的 avg 和 stddev。这是否是有意义的数据是你必须决定的。


我们不能在聊天中继续我们的讨论,...

因为它没有详细说明降价。所以我用我的帖子来澄清我的立场,主要是用 thb 发表评论,但安德鲁似乎也相信 stddev 的滑动计算。

这是一个宽表,可以使计算变得明显且易于理解。这些列是:

  • i:运行指数。我们首先计算值 1-3,然后计算值 1-5。
  • x(i) 是我任意选择的数据。3,4,5 和 4,6
  • sum 就是他们总结的结果。有趣的是组的最后一个:12 和 22。注意:我们不取 3 个值和 2 个值的总和,而是取前 3 个值和前 5 个值的总和。
  • 平均值仅为 12/3 或 22/5。如果您知道 i 和 sum,则可以滑动计算 avg。sum(i+1) = (sum (i)+x(i))/i+1目前没有争议。
  • 为了计算标准差,我们必须将每个值的差值与平均值相乘,并将其平方(从而失去符号,否则会使差值无效 - 它始终为 0)。第二个影响是,与许多小距离相比,很少的大距离会导致更大的 stddev。距离(1,1,-1,-1)=> 4*1² = 4.对比:(2,-2)=> 2² + -2² = 4+4 = 8。第一列是 3 个值,第二列是 5 个值(按照计算)。
  • 下一列(最后一个)² 进行平方。
  • 总结一下
  • 除以 n-1
  • 取平方根

带计算的电子表格(oocalc 屏幕截图)

也许我们可以同意,这是计算标准差的有效方法。现在的问题是,如果您知道完整的第 3 行(x(3)=5 除外)如何计算它,现在您得到两个单独的值 (4, 6),如工作表所示,但不知道 (x( i) 对于 i = 1、2、3。

我的主张失败了:你可以。

好的 - 尝试使用您的公式。

ð² = 1/(N-1) (总和 (x i ²) - 1/N (总和 (x i ))²)

所以对于我得到的 4 个值

  • N=5
  • 总和(x i ) = 22
  • 总和(x i ²) = 102

插入您的公式:

ð² = 1/(N-1) (Sum (x<sub>i</sub>²) - 1/N (Sum (x<sub>i</sub>))²)
ð² = 1/4 (102 - 1/5 (22²))
ð² = 1/4 (102 - 1/5 (484))
ð² = 1/4 (102 - 96.8)
ð² = 1/4 (5.2)
ð² = 1.3
ð  = 1.140

我的结果是 1.14,你的结果是 1.14所以有一个捷径。非常有趣 - 我仍然感到惊讶。

于 2012-04-28T16:14:35.330 回答
2

事实上,即使在计算标准差时较小的数据集上,也不应该计算平方和。这个问题被称为灾难性取消(链接是维基百科)。

维基百科也有两篇文章可以帮助你摆脱这个问题:

  • Kahan 求和算法,在对大量非常小的值求和时(例如,在对所有 x/n 值求和时)避免系统误差
  • 计算方差的算法,特别是“在线”版本应该适用于大型数据集。它将逐步更新每个观察值的平均值,因此值保持在数据的范围内!您可能需要对方差使用高阶版本,因为第一个在线算法仍然计算平方和偏差之和,因此对于较大的 n,这可能会破坏您的值范围。高阶版本中的 M2 应该包含平均平方偏差,它在您的输出范围内。

这可能是简单统计计算最常见的问题之一。

请注意,当均值保持在 0 左右时,问题不会发生,远小于方差。

于 2013-05-01T11:59:33.710 回答
0

所以,我不确定这是否是以前使用过的算法,但无论如何我都会提供它。我的想法是用错误的平均值计算标准偏差,然后根据实际平均值进行校正。这是我写的关于它的图片

于 2019-10-29T20:21:57.257 回答