如何有效计算运行标准偏差?


87

我有一组数字列表,例如:

[0] (0.01, 0.01, 0.02, 0.04, 0.03)
[1] (0.00, 0.02, 0.02, 0.03, 0.02)
[2] (0.01, 0.02, 0.02, 0.03, 0.02)
     ...
[n] (0.01, 0.00, 0.01, 0.05, 0.03)

我想做的是有效地计算所有数组元素在列表的每个索引处的均值和标准差。

为了表示平均值,我一直在遍历数组并求和给定列表索引处的值。最后,我将“平均值列表”中的每个值除以n(我正在处理总体,而不是总体中的样本)。

要进行标准偏差,现在我已经计算出平均值,因此我再次遍历。

我想避免两次遍历数组,一次是平均值,然后一次是SD(在得到平均值之后)。

是否有一种有效的方法来计算两个值,而只需要遍历数组一次?任何使用解释语言(例如Perl或Python)或伪代码的代码都可以。


7
不同的语言,但相同的算法:stackoverflow.com/questions/895929/...
dmckee ---前主持人小猫

谢谢,我将检查该算法。听起来像我需要的。
亚历克斯·雷诺兹

感谢您为我提供正确的答案,dmckee。如果您想花一点时间在下方添加您的答案(如果要点),我想给您“最佳答案”打勾。
亚历克斯·雷诺兹


1
维基百科有一个Python实现en.wikipedia.org/wiki/...
麦高Grubijan

Answers:


116

答案是使用韦尔福德算法,该算法在以下“天真方法”之后非常明确地定义:

它比其他响应中建议的两次通过或在线简单平方和收集器在数值上更稳定。只有当您拥有许多彼此接近的值时,稳定性才真正重要,因为它们会导致浮点文学中的“灾难性抵消”。

您可能还想梳理方差计算(平方差)中除以样本数(N)和N-1之间的差异。除以N-1会导致对样本方差的无偏估计,而平均除以N会低估方差(因为它没有考虑样本均值和真实均值之间的方差)。

我写了两个关于该主题的博客条目,其中包含更多详细信息,包括如何在线删除以前的值:

您也可以看一下我的Java工具。javadoc,源代码和单元测试都在线:


1
+1,注意删除Welford算法的值
Svisstack

3
不错的答案,+ 1是为了提醒读者注意总体stddev与样本stddev之间的差异。
阿萨德·易卜拉欣

这些年来,我一直在回头问这个问题,我想说声谢谢,感谢您抽出宝贵的时间来提供一个很好的答案。
亚历克斯·雷诺兹

76

基本的答案是随行累加x(称为“ sum_x1”)和x 2(称为“ sum_x2”)之和。那么标准差的值是:

stdev = sqrt((sum_x2 / n) - (mean * mean)) 

哪里

mean = sum_x / n

这是样本标准偏差;您将使用“ n”而不是“ n-1”作为除数来获得总体标准差。

如果要处理大样本,可能需要担心取两个大数之差的数值稳定性。有关其他信息,请转到其他答案(维基百科等)中的外部参考。


这就是我要建议的。假设精度误差不是问题,这是最好,最快的方法。
Ray Hidayat

2
我决定采用韦尔福德算法,因为它在相同的计算开销下性能更加可靠。
亚历克斯·雷诺兹

2
这是答案的简化版本,根据输入(例如,sum_x2 <sum_x1 * sum_x1),可能会给出非真实的结果。为了确保获得有效的真实结果,请使用`sd = sqrt((((n * sum_x2)-(sum_x1 * sum_x1))/(n *(n-1)))
丹涛

2
@Dan指出了一个有效的问题-上面的公式对于x> 1无效,因为您最终使用的是负数的sqrt。Knuth方法是:sqrt((sum_x2 / n)-(mean *平均值))其中mean =(sum_x / n)。
G__

1
@UriLoya —关于如何计算值,您什么也没说。但是,如果int在C中使用存储平方和,则列出的值会遇到溢出问题。
乔纳森·莱夫勒

38

这是来自http://www.johndcook.com/standard_deviation.html的Welford算法实现的字面纯Python转换

https://github.com/liyanage/python-modules/blob/master/running_stats.py

import math

class RunningStats:

    def __init__(self):
        self.n = 0
        self.old_m = 0
        self.new_m = 0
        self.old_s = 0
        self.new_s = 0

    def clear(self):
        self.n = 0

    def push(self, x):
        self.n += 1

        if self.n == 1:
            self.old_m = self.new_m = x
            self.old_s = 0
        else:
            self.new_m = self.old_m + (x - self.old_m) / self.n
            self.new_s = self.old_s + (x - self.old_m) * (x - self.new_m)

            self.old_m = self.new_m
            self.old_s = self.new_s

    def mean(self):
        return self.new_m if self.n else 0.0

    def variance(self):
        return self.new_s / (self.n - 1) if self.n > 1 else 0.0

    def standard_deviation(self):
        return math.sqrt(self.variance())

用法:

rs = RunningStats()
rs.push(17.0)
rs.push(19.0)
rs.push(24.0)

mean = rs.mean()
variance = rs.variance()
stdev = rs.standard_deviation()

print(f'Mean: {mean}, Variance: {variance}, Std. Dev.: {stdev}')

9
参考Knuth,这应该是公认的答案,因为它是唯一既正确又显示算法的答案。
约翰·伦德伯格

26

也许不是您要问的,但是...如果您使用numpy数组,它将为您高效地完成工作:

from numpy import array

nums = array(((0.01, 0.01, 0.02, 0.04, 0.03),
              (0.00, 0.02, 0.02, 0.03, 0.02),
              (0.01, 0.02, 0.02, 0.03, 0.02),
              (0.01, 0.00, 0.01, 0.05, 0.03)))

print nums.std(axis=1)
# [ 0.0116619   0.00979796  0.00632456  0.01788854]

print nums.mean(axis=1)
# [ 0.022  0.018  0.02   0.02 ]

顺便说一句,在此博客文章中进行了一些有趣的讨论,并评论了用于计算均值和方差的单次通过方法:


14

Python的RUNSTATS模块是只是这样的事情。从PyPI安装runstats

pip install runstats

Runstats摘要可以在单次数据传递中产生均值,方差,标准差,偏度和峰度。我们可以使用它来创建您的“运行”版本。

from runstats import Statistics

stats = [Statistics() for num in range(len(data[0]))]

for row in data:

    for index, val in enumerate(row):
        stats[index].push(val)

    for index, stat in enumerate(stats):
        print 'Index', index, 'mean:', stat.mean()
        print 'Index', index, 'standard deviation:', stat.stddev()

统计摘要基于Knuth和Welford方法,一次计算标准偏差,如《计算机编程艺术》第二卷,第1页中所述。232,第3版。这样做的好处是数值稳定且结果准确。

免责声明:我是Python runstats模块的作者。


尼斯模块。如果有Statistics一个.pop方法很有趣,那么滚动统计也可以计算出来。
古斯塔沃·贝塞拉

@GustavoBezerrarunstats不会维护值的内部列表,因此我不确定是否可能。但是欢迎提出请求。
GrantJ

8

Statistics :: Descriptive是用于这些类型的计算的非常不错的Perl模块:

#!/usr/bin/perl

use strict; use warnings;

use Statistics::Descriptive qw( :all );

my $data = [
    [ 0.01, 0.01, 0.02, 0.04, 0.03 ],
    [ 0.00, 0.02, 0.02, 0.03, 0.02 ],
    [ 0.01, 0.02, 0.02, 0.03, 0.02 ],
    [ 0.01, 0.00, 0.01, 0.05, 0.03 ],
];

my $stat = Statistics::Descriptive::Full->new;
# You also have the option of using sparse data structures

for my $ref ( @$data ) {
    $stat->add_data( @$ref );
    printf "Running mean: %f\n", $stat->mean;
    printf "Running stdev: %f\n", $stat->standard_deviation;
}
__END__

输出:

C:\Temp> g
Running mean: 0.022000
Running stdev: 0.013038
Running mean: 0.020000
Running stdev: 0.011547
Running mean: 0.020000
Running stdev: 0.010000
Running mean: 0.020000
Running stdev: 0.012566

8

看看PDL(发音为“ piddle!”)。

这是Perl数据语言,专为高精度数学和科学计算而设计。

这是一个使用你的数字的例子。

use strict;
use warnings;
use PDL;

my $figs = pdl [
    [0.01, 0.01, 0.02, 0.04, 0.03],
    [0.00, 0.02, 0.02, 0.03, 0.02],
    [0.01, 0.02, 0.02, 0.03, 0.02],
    [0.01, 0.00, 0.01, 0.05, 0.03],
];

my ( $mean, $prms, $median, $min, $max, $adev, $rms ) = statsover( $figs );

say "Mean scores:     ", $mean;
say "Std dev? (adev): ", $adev;
say "Std dev? (prms): ", $prms;
say "Std dev? (rms):  ", $rms;


产生:

Mean scores:     [0.022 0.018 0.02 0.02]
Std dev? (adev): [0.0104 0.0072 0.004 0.016]
Std dev? (prms): [0.013038405 0.010954451 0.0070710678 0.02]
Std dev? (rms):  [0.011661904 0.009797959 0.0063245553 0.017888544]


看看PDL :: Primitive了解有关statsover的更多信息函数的。这似乎表明ADEV是“标准偏差”。

但是它可能是PRMS(Sinan的Statistics :: Descriptive示例显示)或RMS(ars的NumPy示例显示)。我猜这三者之一一定是正确的;-)

有关更多PDL信息,请查看:


1
这不是运行中的计算。
杰克

3

您的阵列有多大?除非它有成千上万个元素,否则不必担心会循环两次。该代码简单且易于测试。

我的偏好是使用numpy array maths扩展将数组数组转换为numpy 2D数组并直接获取标准差:

>>> x = [ [ 1, 2, 4, 3, 4, 5 ], [ 3, 4, 5, 6, 7, 8 ] ] * 10
>>> import numpy
>>> a = numpy.array(x)
>>> a.std(axis=0) 
array([ 1. ,  1. ,  0.5,  1.5,  1.5,  1.5])
>>> a.mean(axis=0)
array([ 2. ,  3. ,  4.5,  4.5,  5.5,  6.5])

如果这不是一个选择,而您需要一个纯Python解决方案,请继续阅读...

如果你的数组是

x = [ 
      [ 1, 2, 4, 3, 4, 5 ],
      [ 3, 4, 5, 6, 7, 8 ],
      ....
]

那么标准偏差为:

d = len(x[0])
n = len(x)
sum_x = [ sum(v[i] for v in x) for i in range(d) ]
sum_x2 = [ sum(v[i]**2 for v in x) for i in range(d) ]
std_dev = [ sqrt((sx2 - sx**2)/N)  for sx, sx2 in zip(sum_x, sum_x2) ]

如果确定只循环遍历一次数组,则可以对运行总和进行合并。

sum_x  = [ 0 ] * d
sum_x2 = [ 0 ] * d
for v in x:
   for i, t in enumerate(v):
   sum_x[i] += t
   sum_x2[i] += t**2

这几乎没有上面的列表理解解决方案那么优雅。


实际上,我确实需要处理成千上万的数字,这正是促使我寻求有效解决方案的原因。谢谢!
亚历克斯·雷诺兹

它不是关于数据集的大小,而是关于频率的大小,我必须每秒对500个元素进行3500次不同的标准差计算,每秒计算一次
PirateApp



1

这是功能编程风格的“单线”,分布在多行中:

def variance(data, opt=0):
    return (lambda (m2, i, _): m2 / (opt + i - 1))(
        reduce(
            lambda (m2, i, avg), x:
            (
                m2 + (x - avg) ** 2 * i / (i + 1),
                i + 1,
                avg + (x - avg) / (i + 1)
            ),
            data,
            (0, 0, 0)))

1
n=int(raw_input("Enter no. of terms:"))

L=[]

for i in range (1,n+1):

    x=float(raw_input("Enter term:"))

    L.append(x)

sum=0

for i in range(n):

    sum=sum+L[i]

avg=sum/n

sumdev=0

for j in range(n):

    sumdev=sumdev+(L[j]-avg)**2

dev=(sumdev/n)**0.5

print "Standard deviation is", dev


1

我喜欢这样表达更新:

def running_update(x, N, mu, var):
    '''
        @arg x: the current data sample
        @arg N : the number of previous samples
        @arg mu: the mean of the previous samples
        @arg var : the variance over the previous samples
        @retval (N+1, mu', var') -- updated mean, variance and count
    '''
    N = N + 1
    rho = 1.0/N
    d = x - mu
    mu += rho*d
    var += rho*((1-rho)*d**2 - var)
    return (N, mu, var)

这样一遍函数将如下所示:

def one_pass(data):
    N = 0
    mu = 0.0
    var = 0.0
    for x in data:
        N = N + 1
        rho = 1.0/N
        d = x - mu
        mu += rho*d
        var += rho*((1-rho)*d**2 - var)
        # could yield here if you want partial results
   return (N, mu, var)

请注意,这是在计算样本方差(1 / N),而不是总体方差的无偏估计(它使用1 /(N-1)归一化因子)。与其他答案不同,var跟踪运行方差的变量不会与样本数量成正比。在任何时候,它只是到目前为止所看到的样本集的方差(在获得方差时没有最终的“除以n”)。

在一个类中,它看起来像这样:

class RunningMeanVar(object):
    def __init__(self):
        self.N = 0
        self.mu = 0.0
        self.var = 0.0
    def push(self, x):
        self.N = self.N + 1
        rho = 1.0/N
        d = x-self.mu
        self.mu += rho*d
        self.var += + rho*((1-rho)*d**2-self.var)
    # reset, accessors etc. can be setup as you see fit

这也适用于加权样本:

def running_update(w, x, N, mu, var):
    '''
        @arg w: the weight of the current sample
        @arg x: the current data sample
        @arg mu: the mean of the previous N sample
        @arg var : the variance over the previous N samples
        @arg N : the number of previous samples
        @retval (N+w, mu', var') -- updated mean, variance and count
    '''
    N = N + w
    rho = w/N
    d = x - mu
    mu += rho*d
    var += rho*((1-rho)*d**2 - var)
    return (N, mu, var)
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.