실행 표준 편차를 효율적으로 계산하는 방법은 무엇입니까?


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 --- ex-moderator kitten

감사합니다. 알고리즘을 확인해 보겠습니다. 내가 필요한 것 같은데.
Alex Reynolds

정답을 알려 주셔서 감사합니다, dmckee. 잠시 시간을내어 아래에 답변을 추가 하시려면 "우수 답변"체크 표시를 드리고자합니다 (포인트를 원하시면).
Alex Reynolds

1

1
위키 백과는 파이썬 구현이 en.wikipedia.org/wiki/...을
해미 Grubijan에게

답변:


116

대답은 Welford의 알고리즘을 사용하는 것입니다. 이는 다음에서 "순진한 방법"뒤에 매우 명확하게 정의되어 있습니다.

다른 응답에서 제안 된 2- 패스 또는 온라인 단순 제곱합 수집기보다 수치 적으로 더 안정적입니다. 안정성 은 부동 소수점 문헌에서 " 치명적 취소 " 로 알려진 결과로 이어지기 때문에 서로 가까운 값이 많은 경우에만 실제로 중요합니다 .

분산 계산 (제곱 편차)에서 샘플 수 (N)로 나누는 것과 N-1 사이의 차이를 살펴볼 수도 있습니다. N-1로 나누면 표본에서 편향되지 않은 분산 추정값이 생성되는 반면, 평균 N으로 나누면 분산이 과소 평가됩니다 (표본 평균과 실제 평균 사이의 분산을 고려하지 않기 때문).

온라인에서 이전 값을 삭제하는 방법을 포함하여 더 자세한 내용을 다루는 주제에 대한 두 개의 블로그 항목을 작성했습니다.

내 Java 구현을 살펴볼 수도 있습니다. javadoc, 소스 및 단위 테스트는 모두 온라인입니다.


1
+1, Welford의 알고리즘에서 값을 삭제하는 데주의를 기울
였습니다

3
독자에게 모집단 stddev와 샘플 stddev의 차이점을 상기시켜주는 +1입니다.
Assad Ebrahim

시간을내어 훌륭한 답변을 해주신 것에 대해 감사의 말을 전하고 싶었습니다.
Alex Reynolds

76

기본적인 대답은 x ( 'sum_x1'이라고 부름)와 x 2 ( 'sum_x2'라고 부름)의 합계를 모으는 것입니다. 표준 편차의 값은 다음과 같습니다.

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

어디

mean = sum_x / n

이것은 표본 표준 편차입니다. 제수로 'n-1'대신 'n'을 사용하여 모집단 표준 편차를 얻습니다.

큰 표본을 다루는 경우 두 큰 숫자 간의 차이를 가져 오는 수치 안정성에 대해 걱정해야 할 수도 있습니다. 자세한 내용은 다른 답변 (Wikipedia 등)의 외부 참조로 이동하십시오.


이것이 제가 제안하려고했던 것입니다. 정밀도 오류가 문제가 아니라고 가정 할 때 가장 빠르고 가장 빠른 방법입니다.
Ray Hidayat

2
동일한 계산 오버 헤드로 더 안정적으로 수행되는 Welford의 알고리즘을 사용하기로 결정했습니다.
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에 대해 분석됩니다. Knuth 접근 방식은 다음과 같습니다. sqrt ((sum_x2 / n)-(평균 * 평균)) 여기서 mean = (sum_x / n).
G__ 2010-07-27

1
@UriLoya — 값을 계산하는 방법에 대해 아무 말도하지 않았습니다. 그러나 intC에서 제곱합을 저장하는 경우 나열한 값에 오버플로 문제가 발생합니다.
Jonathan Leffler

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와 관련하여 정확하고 알고리즘을 보여주는 유일한 답변이므로 허용되는 답변이어야합니다.
Johan Lundberg

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

파이썬 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()

통계 요약은 Art of Computer Programming, Vol 2, p에 설명 된대로 한 번의 패스로 표준 편차를 계산하기위한 Knuth 및 Welford 방법을 기반으로합니다. 232, 3 판. 이것의 이점은 수치 적으로 안정적이고 정확한 결과입니다.

면책 조항 : 저는 Python runstats 모듈의 작성자입니다.


멋진 모듈입니다. 롤링 통계도 계산할 수 있도록 Statisticshas .pop메소드 가 있다면 흥미로울 것 입니다.
Gustavo Bezerra

@GustavoBezerra runstats는 내부 값 목록을 유지하지 않으므로 그것이 가능한지 확신 하지 못합니다. 그러나 풀 리퀘스트는 환영합니다.
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 를 참조하십시오. 함수 . 이것은 ADEV가 "표준 편차"임을 시사하는 것 같습니다.

그러나 PRMS (Sinan의 Statistics :: Descriptive 예제가 표시됨) 또는 RMS (ars의 NumPy 예제가 표시됨) 일 수 있습니다. 이 세 가지 중 하나가 맞을 것 같아요 ;-)

자세한 PDL 정보는 다음을 참조하십시오.


1
이것은 실행중인 계산이 아닙니다.
Jake

3

어레이가 얼마나 큽니까? 수백만 개의 요소가 긴 경우가 아니라면 두 번 반복하는 것에 대해 걱정하지 마십시오. 코드는 간단하고 쉽게 테스트됩니다.

내 선호는 numpy 배열 수학 확장을 사용하여 배열 배열을 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

이것은 위의 목록 이해 솔루션만큼 우아하지는 않습니다.


저는 실제로 엄청난 수의 숫자를 처리해야하는데, 이것이 효율적인 솔루션에 대한 제 필요를 자극하는 이유입니다. 감사!
Alex Reynolds

데이터 세트의 크기가 아니라 얼마나 자주 발생하는지에 대한 것이
아닙니다.


1

이 문제가 도움이 될 것 같습니다. 표준 편차


+1 @Lasse V. Karlsen의 Wikipedia 링크는 좋지만 이것이 제가 사용한 올바른 알고리즘입니다 ...
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)

0

다음은 python과 함께 실행 표준 편차를 구현하는 방법에 대한 실용적인 예입니다 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]))

이것은 계산 된 표준 편차와 numpy로 계산 된 표준 편차를 출력합니다.

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

이 스레드에 설명 된 공식을 사용하고 있습니다.

stdev = sqrt((sum_x2 / n) - (mean * mean)) 
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.