web-dev-qa-db-ja.com

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

数字のリストの配列があります、例えば:

[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のためです(平均を取得した後)。

両方の値を計算するための効率的な方法はありますか?インタプリタ言語(PerlやPythonなど)または擬似コードのコードは問題ありません。

78
Alex Reynolds

答えは、ウェルフォードのアルゴリズムを使用することです。これは、以下の「単純な方法」の後に非常に明確に定義されています。

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

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

このトピックに関して、以前の値をオンラインで削除する方法など、詳細に進む2つのブログエントリを書きました。

My Java implement; javadoc、ソース、およびユニットテストはすべてオンラインです:

105
Bob Carpenter

基本的な答えは、x(「sum_x1」と呼ぶ)とxの両方の合計を累積することです2 (「sum_x2」と呼んでください)標準偏差の値は次のとおりです。

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

どこ

mean = sum_x / n

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

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

70

おそらくあなたが求めていたものではありませんが、... 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 ]

ところで、このブログ投稿には興味深い議論があり、平均と分散を計算するためのワンパス法に関するコメントがあります。

26
ars

リテラルの純粋なPython http://www.johndcook.com/standard_deviation.html からのウェルフォードのアルゴリズム実装の翻訳です。

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

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();
25
Marc Liyanage

Python runstats Module は、まさにこの種のものです。 runstatsのインストール PyPIから:

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

統計の要約は、Art of Computer Programming、Vol 2、p。 232、第3版。この利点は、数値的に安定した正確な結果です。

免責事項:私は著者Python runstatsモジュールです。

11
GrantJ

[〜#〜] 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 をご覧ください スタッツオーバー 関数。これは、ADEVが「標準偏差」であることを示唆しているようです。

しかし、おそらくPRMS(SinanのStatistics :: Descriptionの例が示す)またはRMS(arsのNumPyの例が示す)。

PDLの詳細については、以下をご覧ください。

8
draegtun

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
Sinan Ünür

アレイの大きさは?長さが数十億の要素でない限り、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

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

3
Stephen Simmons

この問題はあなたの役に立つと思います。 標準偏差

2
peterdemin
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
Anuraag

標準偏差 に関するウィキペディアの記事、特に高速計算方法に関するセクションをご覧ください。

Pythonを使用する記事もありますが、Pythonでコードをほとんど変更せずに使用できるはずです: Subliminal Messages-Running Standard Deviations

次の答えが示すように: pandas/scipy/numpyは累積標準偏差関数を提供しますか? Python Pandasモジュールにはランニングまたは 累積標準偏差 を計算する方法。そのためには、データをpandasデータフレーム(または1Dの場合はシリーズ)に変換する必要があります。しかし、そのための機能があります。

1
Ramon Crehuet

関数型プログラミングスタイルで、複数行にまたがる「ワンライナー」を次に示します。

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)))
0
Mehrdad

更新を次のように表現したい:

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)
0
Dave