Çalışan bir standart sapmayı verimli bir şekilde nasıl hesaplayabilirim?


88

Bir dizi sayı listem var, örneğin:

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

Yapmak istediğim şey, tüm dizi elemanlarında bir listenin her indeksindeki ortalama ve standart sapmayı verimli bir şekilde hesaplamaktır.

Demek istediğim, dizide dönüyorum ve değeri bir listenin belirli bir dizininde topladım. Sonunda, "ortalamalar listemdeki" her bir değeri şuna bölerim n(popülasyondan bir örneklemle değil, bir popülasyonla çalışıyorum).

Standart sapmayı yapmak için, ortalamayı şimdi hesapladığıma göre tekrar döngü yapıyorum.

Diziden iki kez geçmekten kaçınmak istiyorum, bir kez ortalama için ve sonra bir kez SD için (bir ortalamadan sonra).

Her iki değeri de hesaplamak için, diziden yalnızca bir kez geçmek için verimli bir yöntem var mı? Yorumlanmış bir dildeki (örneğin Perl veya Python) veya sözde koddaki herhangi bir kod yeterlidir.



Teşekkürler, o algoritmayı kontrol edeceğim. İhtiyacım olan şey gibi görünüyor.
Alex Reynolds

Bana doğru cevabı gösterdiğin için teşekkürler dmckee. Cevabınızı aşağıya eklemek için bir dakikanızı ayırmak isterseniz (puanları isterseniz) size "en iyi cevabı" onay işaretini vermek istiyorum.
Alex Reynolds


1
Wikipedia'da bir Python uygulaması var en.wikipedia.org/wiki/…
Hamish Grubijan

Yanıtlar:


117

Cevap, Welford'un şu alandaki "saf yöntemlerden" sonra açıkça tanımlanan algoritmasını kullanmaktır:

Diğer yanıtlarda önerilen iki geçişli veya çevrimiçi basit kareler toplamından daha sayısal olarak kararlıdır. Kararlılık , kayan nokta literatüründe " yıkıcı iptal " olarak bilinen şeye yol açtığından, birbirine yakın çok sayıda değeriniz olduğunda gerçekten önemlidir .

Varyans hesaplamasında (sapmanın karesi) örnek sayısına (N) ve N-1'e bölünmesi arasındaki farkı tazelemek isteyebilirsiniz. N-1 ile bölme, örneklemden tarafsız bir varyans tahminine yol açarken, ortalama olarak N'ye bölmek varyansı olduğundan daha düşük hesaplar (çünkü örnek ortalaması ile gerçek ortalama arasındaki varyansı hesaba katmaz).

Önceki değerlerin çevrimiçi olarak nasıl silineceği de dahil olmak üzere daha fazla ayrıntıya giren konu hakkında iki blog girişi yazdım:

Java yazılımıma da göz atabilirsiniz; javadoc, kaynak ve birim testlerinin tümü çevrimiçidir:


1
+1,
Welford'un

3
Okuyucuya stddev popülasyonu ile örnek stddev arasındaki farkı hatırlatmak için güzel cevap, +1.
Assad Ebrahim

Bunca yıldan sonra bu soruya geri döndükten sonra, harika bir cevap vermeye zaman ayırdığınız için bir teşekkür etmek istedim.
Alex Reynolds

76

Temel cevap, ilerledikçe hem x (buna 'sum_x1' diyelim) hem de x 2'nin (buna 'sum_x2' diyelim) toplamını toplamaktır. Standart sapmanın değeri şu şekildedir:

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

nerede

mean = sum_x / n

Bu, örnek standart sapmadır; bölen olarak "n - 1" yerine "n" kullanarak popülasyon standart sapmasını elde edersiniz.

Büyük örneklerle uğraşıyorsanız, iki büyük sayı arasındaki farkı almanın sayısal kararlılığı konusunda endişelenmeniz gerekebilir. Daha fazla bilgi için diğer yanıtlardaki (Wikipedia, vb.) Harici referanslara gidin.


Bu benim önereceğim şeydi. Kesinlik hatalarının sorun olmadığını varsayarak en iyi ve en hızlı yol budur.
Ray Hidayet

2
Aynı hesaplama ek yükü ile daha güvenilir bir şekilde çalıştığı için Welford'un Algoritmasını kullanmaya karar verdim.
Alex Reynolds

2
Bu, cevabın basitleştirilmiş bir versiyonudur ve girdiye bağlı olarak gerçek olmayan sonuçlar verebilir (yani, sum_x2 <sum_x1 * sum_x1 olduğunda). Geçerli bir gerçek sonuç sağlamak için, "sd = sqrt (((n * sum_x2) - (sum_x1 * sum_x1)) / (n * (n - 1)))
Dan Tao

2
@Dan geçerli bir soruna işaret ediyor - yukarıdaki formül x> 1 için parçalanıyor çünkü sonunda negatif bir sayının sqrt'sini alıyorsunuz. Knuth yaklaşımı şöyledir: sqrt ((toplam_x2 / n) - (ortalama * ortalama)) burada ortalama = (toplam_x / n).
G__

1
@UriLoya - değerleri nasıl hesapladığınız hakkında hiçbir şey söylemediniz. Ancak, intkarelerin toplamını saklamak için C'de kullanırsanız , listelediğiniz değerlerle taşma sorunlarıyla karşılaşırsınız.
Jonathan Leffler

38

Welford'un algoritma uygulamasının http://www.johndcook.com/standard_deviation.html adresinden tam anlamıyla saf bir Python çevirisi :

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

Kullanım:

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'a referansla hem doğru olan hem de algoritmayı gösteren tek cevap olduğu için bu kabul edilen cevap olmalıdır.
Johan Lundberg

26

Belki istediğin şey değil, ama ... Eğer uyuşuk bir dizi kullanırsan, işi senin için verimli bir şekilde yapacak:

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 ]

Bu arada, bu blog gönderisinde bazı ilginç tartışmalar var ve hesaplama araçları ve varyansları için tek geçişli yöntemler hakkında yorumlar var:


14

Python'un runstats Modül şey sadece bu tür içindir. PyPI'den runstats yükleyin :

pip install runstats

Runstats özetleri, tek bir veri geçişinde ortalama, varyans, standart sapma, çarpıklık ve basıklığı üretebilir. Bunu "çalışan" sürümünüzü oluşturmak için kullanabiliriz.

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

İstatistik özetleri, Bilgisayar Programlama Sanatı, Cilt 2, s. 232, 3. baskı. Bunun yararı sayısal olarak kararlı ve doğru sonuçlardır.

Feragatname: Python runstats modülünün yazarıyım.


Güzel modül. StatisticsBir .popmetodu olsaydı ilginç olurdu, böylece yuvarlanma istatistikleri de hesaplanabilirdi.
Gustavo Bezerra

@GustavoBezerra runstatsdahili bir değerler listesi tutmadığından bunun mümkün olduğundan emin değilim. Ancak çekme istekleri kabul edilir.
GrantJ

8

İstatistikler :: Tanımlayıcı , bu tür hesaplamalar için çok iyi bir Perl modülüdür:

#!/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__

Çıktı:

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

Göz at PDL ( "piddle!" Telaffuz edilir).

Bu, yüksek hassasiyetli matematik ve bilimsel hesaplama için tasarlanmış Perl Veri Dilidir.

İşte rakamlarınızı kullanan bir örnek ...

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;


Hangi üretir:

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]


Göz at PDL :: İlkel hakkında daha fazla bilgi için statsover işlevi. Bu, ADEV'in "standart sapma" olduğunu öne sürüyor gibi görünüyor.

Bununla birlikte, PRMS (Sinan'ın İstatistikleri :: Açıklayıcı örneği gösterir) veya RMS (ars'ın NumPy örneği gösterir) olabilir. Sanırım bu üçünden biri doğru olmalı ;-)

Daha fazla PDL bilgisi için bir göz atın:


1
Bu devam eden bir hesaplama değil.
Jake

3

Diziniz ne kadar büyük? Zilyonlarca öğe uzunluğunda olmadığı sürece, iki kez döngüden geçme konusunda endişelenmeyin. Kod basittir ve kolayca test edilir.

Benim tercihim , dizi dizilerinizi uyuşmuş bir 2D diziye dönüştürmek ve standart sapmayı doğrudan elde etmek için numpy array maths uzantısını kullanmak olacaktır :

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

Bu bir seçenek değilse ve saf bir Python çözümüne ihtiyacınız varsa, okumaya devam edin ...

Diziniz ise

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

O zaman standart sapma:

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

Dizinizde yalnızca bir kez döngü yapmaya karar verirseniz, devam eden toplamlar birleştirilebilir.

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

Bu, yukarıdaki liste anlama çözümü kadar zarif değildir.


Aslında zilyonlarca sayıyla uğraşmam gerekiyor, bu da verimli bir çözüme olan ihtiyacımı motive ediyor. Teşekkürler!
Alex Reynolds

bu veri kümesinin ne kadar büyük olduğu ile ilgili değil, ne kadar
Sıklıkla ilgili,


1

Bu konunun size yardımcı olacağını düşünüyorum. Standart sapma


+1 @Lasse V. Karlsen'in Wikipedia bağlantısı iyi, ancak bu benim kullandığım doğru algoritma ...
kenny

1

Fonksiyonel programlama tarzında birden çok satıra yayılmış bir "tek satırlık" burada:

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

Güncellemeyi şu şekilde ifade etmeyi seviyorum:

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)

böylece bir tek geçiş işlevi şöyle görünür:

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)

bunun, popülasyon varyansının (1 / (N-1) normalleştirme faktörü kullanan) tarafsız tahmini değil, örnek varyansını (1 / N) hesapladığına dikkat edin. Diğer cevapların aksine değişken,var yani devam eden varyansı izleyen örnek sayısı ile orantılı olarak büyümez. Her zaman bu, şimdiye kadar görülen örneklem setinin varyansıdır (varyansı elde etmede nihai "n'ye bölme" yoktur).

Bir sınıfta şöyle görünür:

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

Bu aynı zamanda ağırlıklı örnekler için de geçerlidir:

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

İşte python ile çalışan bir standart sapmayı nasıl uygulayabileceğinize dair pratik bir örnek ve numpy:

a = np.arange(1, 10)
s = 0
s2 = 0
for i in range(0, len(a)):
    s += a[i]
    s2 += a[i] ** 2 
    n = (i + 1)
    m = s / n
    std = np.sqrt((s2 / n) - (m * m))
    print(std, np.std(a[:i + 1]))

Bu, hesaplanan standart sapmayı ve numpy ile hesaplanan bir kontrol standart sapmasını yazdıracaktır:

0.0 0.0
0.5 0.5
0.8164965809277263 0.816496580927726
1.118033988749895 1.118033988749895
1.4142135623730951 1.4142135623730951
1.707825127659933 1.707825127659933
2.0 2.0
2.29128784747792 2.29128784747792
2.5819888974716116 2.581988897471611

Sadece bu başlıkta açıklanan formülü kullanıyorum:

stdev = sqrt((sum_x2 / n) - (mean * mean)) 
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.