実行中の標準偏差を効率的に計算する方法は?


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(母集団からのサンプルではなく、母集団で作業しています)で除算します。

標準偏差を実行するために、平均を計算したので、もう一度ループします。

配列を2回通過することは避けたいと思います。1回は平均用で、もう1回はSD用です(平均を取得した後)。

配列を1回だけ通過して、両方の値を計算するための効率的な方法はありますか?インタープリター言語(PerlやPythonなど)または擬似コードのコードであれば問題ありません。


7
異なる言語が、同じアルゴリズム:stackoverflow.com/questions/895929/...
dmckee ---元司会者の子猫

おかげで、私はそのアルゴリズムをチェックします。私が必要なもののように聞こえます。
Alex Reynolds

正しい答えを教えてくれてありがとう、dmckee。以下に回答を追加したい場合(ポイントが必要な場合)、「ベストアンサー」チェックマークを付けたいと思います。
Alex Reynolds


1
ウィキペディアには、Pythonの実装ありen.wikipedia.org/wiki/...を
ハミッシュGrubijan

回答:


116

答えは、ウェルフォードのアルゴリズムを使用することです。これは、次の「ナイーブメソッド」の後に非常に明確に定義されています。

これは、他の応答で提案されている2パスまたはオンラインの単純な二乗和コレクターよりも数値的に安定しています。安定性が本当に重要になるのは、浮動小数点の文献で「壊滅的なキャンセル」と呼ばれるものにつながるため、互いに近い値がたくさんある場合だけです。

また、分散計算(二乗偏差)でサンプル数(N)とN-1で除算することの違いをブラッシュアップすることもできます。N-1で除算すると、サンプルからの分散の不偏推定につながりますが、平均でNで除算すると、分散が過小評価されます(サンプル平均と真の平均の間の分散が考慮されていないため)。

オンラインで以前の値を削除する方法など、詳細を説明する2つのブログエントリを作成しました。

私のJava実装もご覧ください。javadoc、ソース、および単体テストはすべてオンラインです。


1
+1、ウェルフォードのアルゴリズムから値を削除することに注意してください
Svisstack 2013年

3
良い答えです。母集団の標準偏差とサンプルの標準偏差の違いを読者に思い出させるための+1です。
アサドエブラヒム2013

何年にもわたってこの質問に戻った後、私は素晴らしい答えを提供するために時間を割いてくれてありがとうという言葉を言いたかっただけです。
Alex Reynolds

76

基本的な答えは、x(「sum_x1」と呼びます)とx 2(「sum_x2」と呼びます)の両方の合計を累積することです。その場合、標準偏差の値は次のようになります。

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

どこ

mean = sum_x / n

これはサンプルの標準偏差です。除数として「n-1」の代わりに「n」を使用して母標準偏差を取得します。

大きなサンプルを扱う場合は、2つの大きな数の差を取ることの数値的安定性について心配する必要があるかもしれません。詳細については、他の回答(Wikipediaなど)の外部参照にアクセスしてください。


これが私が提案しようとしていたことです。精度エラーが問題にならないことを前提とすると、これが最善かつ最速の方法です。
Ray Hidayat

2
同じ計算オーバーヘッドでより確実に実行されるため、ウェルフォードのアルゴリズムを使用することにしました。
Alex Reynolds

2
これは回答の簡略版であり、入力によっては非現実的な結果が得られる場合があります(つまり、sum_x2 <sum_x1 * sum_x1の場合)。有効な実際の結果を保証するには、 `sd = sqrt(((n * sum_x2)-(sum_x1 * sum_x1))/(n *(n-1)))
Dan Tao

2
@Danは、有効な問題を指摘しています。負の数のsqrtを取得することになったため、上記の式はx> 1で分解されます。クヌースのアプローチは次のとおりです。sqrt((sum_x2 / n)-(mean * mean))where mean =(sum_x / n)。
g__ 2010

1
@UriLoya —値の計算方法については何も言っていません。ただし、intCで使用して二乗和を格納すると、リストした値でオーバーフローの問題が発生します。
ジョナサンレフラー

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
これは、クヌースを参照して、正しく、アルゴリズムを示している唯一の答えであるため、受け入れられた答えであるはずです。
ヨハン・ランドバーグ2016年

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の要約では、1回のデータパスで平均、分散、標準偏差、歪度、尖度を生成できます。これを使用して、「実行中」のバージョンを作成できます。

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()

統計の要約は、Art of Computer Programming、Vol 2、p。に記載されているように、1回のパスで標準偏差を計算するためのKnuth andWelford法に基づいています。232、第3版。これの利点は、数値的に安定した正確な結果です。

免責事項:私はPythonrunstatsモジュールの作成者です。


素敵なモジュール。あった場合、それは興味深いものになるだろうとStatistics持って.pop転がり統計も計算することができるようにする方法を。
グスタボベゼラ2016

@GustavoBezerrarunstatsは値の内部リストを維持していないため、それが可能かどうかはわかりません。ただし、プルリクエストは大歓迎です。
GrantJ 2016

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(「おしっこ!」と発音)を。

これは、高精度の数学と科学計算のために設計された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 ::プリミティブの詳細についてはstatsoverの機能を。これは、ADEVが「標準偏差」であることを示唆しているようです。

ただし、PRMS(SinanのStatistics ::記述例が示す)またはRMS(arsのNumPyの例が示す)の可能性があります。私はこれらの3つのうちの1つが正しいに違いないと思います;-)

PDLの詳細については、以下を参照してください。


1
これは実行中の計算ではありません。
ジェイク

3

アレイの大きさはどれくらいですか?数十億の要素の長さでない限り、2回ループする心配はありません。コードはシンプルで簡単にテストできます。

私の好みは、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) ]

配列を1回だけループすることにした場合は、実行中の合計を組み合わせることができます。

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

これは、上記のリスト内包表記ソリューションほどエレガントではありません。


私は実際には何十億もの数字を処理する必要があり、それが効率的なソリューションの必要性を動機付けています。ありがとう!
Alex Reynolds

そのない、私は毎秒ごとの計算上500個の要素を超える3500の異なる標準偏差の計算を行う必要がありますどのくらいの頻度でその程度、どのように大きなデータセットが約
PirateApp

1

標準偏差に関するウィキペディアの記事、特に高速計算方法に関するセクションを見ることができます。

Pythonを使用していることがわかった記事もあります。その中のコードは、あまり変更せずに使用できるはずです。サブリミナルメッセージ-標準偏差の実行


サブリミナルメッセージのバージョンは、数値的にあまり安定していません。
デイブ

1

この問題はあなたを助けると思います。標準偏差


+1 @Lasse V. Karlsenのウィキペディアの良いものへのリンクですが、これは私が使用した正しいアルゴリズムです...
kenny

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)正規化係数を使用)ではなく、標本分散(1 / N)を計算していることに注意してください。他の回答とは異なり、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)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.