วิธีคำนวณค่าเบี่ยงเบนมาตรฐานที่กำลังทำงานอยู่อย่างมีประสิทธิภาพ?


88

ฉันมีรายการตัวเลขมากมายเช่น:

[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 --- อดีตผู้ดูแลลูกแมว

ขอบคุณฉันจะตรวจสอบอัลกอริทึมนั้น ดูเหมือนว่าฉันต้องการอะไร
Alex Reynolds

ขอบคุณที่ชี้ให้ฉันเห็นคำตอบที่ถูกต้อง dmckee ฉันต้องการให้เครื่องหมายถูก "คำตอบที่ดีที่สุด" หากคุณต้องการใช้เวลาสักครู่เพื่อเพิ่มคำตอบของคุณด้านล่าง (หากคุณต้องการคะแนน)
Alex Reynolds

1
นอกจากนี้ยังมีตัวอย่างอีกมากมายที่rosettacode.org/wiki/Standard_Deviation
glenn jackman

1
Wikipedia มีการใช้งาน Python en.wikipedia.org/wiki/…
Hamish Grubijan

คำตอบ:


118

คำตอบคือการใช้อัลกอริทึมของ Welford ซึ่งกำหนดไว้ชัดเจนมากหลังจาก "วิธีการไร้เดียงสา" ใน:

มีความเสถียรทางตัวเลขมากกว่าการสะสมกำลังสองแบบสองทางหรือแบบออนไลน์ที่แนะนำในคำตอบอื่น ๆ ความมั่นคงจะมีความสำคัญมากก็ต่อเมื่อคุณมีค่ามากมายที่อยู่ใกล้กันเนื่องจากนำไปสู่สิ่งที่เรียกว่า " การยกเลิกหายนะ " ในวรรณกรรมประเด็นลอยตัว

คุณอาจต้องการเปรียบเทียบความแตกต่างระหว่างการหารด้วยจำนวนตัวอย่าง (N) และ N-1 ในการคำนวณความแปรปรวน (ส่วนเบี่ยงเบนกำลังสอง) การหารด้วย N-1 จะนำไปสู่การประมาณค่าความแปรปรวนที่เป็นกลางจากตัวอย่างในขณะที่การหารด้วย N โดยเฉลี่ยจะประเมินความแปรปรวนต่ำกว่า (เนื่องจากไม่ได้คำนึงถึงความแปรปรวนระหว่างค่าเฉลี่ยตัวอย่างกับค่าเฉลี่ยที่แท้จริง)

ฉันเขียนบล็อกสองรายการในหัวข้อซึ่งมีรายละเอียดเพิ่มเติมรวมถึงวิธีการลบค่าก่อนหน้าทางออนไลน์:

คุณยังสามารถดูการใช้งาน Java ของฉัน การทดสอบ javadoc แหล่งที่มาและหน่วยออนไลน์ทั้งหมด:


1
+1 สำหรับการดูแลเกี่ยวกับการลบค่าจากอัลกอริทึมของ Welford
Svisstack

4
คำตอบที่ดี +1 สำหรับเตือนผู้อ่านถึงความแตกต่างระหว่าง stddev ของประชากรกับ stddev ตัวอย่าง
Assad Ebrahim

หลังจากกลับมาที่คำถามนี้ตลอดหลายปีที่ผ่านมาฉันแค่อยากจะกล่าวขอบคุณที่สละเวลาให้คำตอบที่ดี
Alex Reynolds

77

คำตอบพื้นฐานคือการสะสมผลรวมของทั้งx (เรียกว่า 'sum_x1') และx 2 (เรียกว่า 'sum_x2') ในขณะที่คุณไป ค่าของค่าเบี่ยงเบนมาตรฐานคือ:

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

ที่ไหน

mean = sum_x / n

นี่คือค่าเบี่ยงเบนมาตรฐานตัวอย่าง คุณจะได้ค่าเบี่ยงเบนมาตรฐานของประชากรโดยใช้ 'n' แทน 'n - 1' เป็นตัวหาร

คุณอาจต้องกังวลเกี่ยวกับความเสถียรของตัวเลขในการรับความแตกต่างระหว่างตัวเลขขนาดใหญ่สองตัวหากคุณกำลังจัดการกับตัวอย่างขนาดใหญ่ ไปที่การอ้างอิงภายนอกในคำตอบอื่น ๆ (Wikipedia ฯลฯ ) สำหรับข้อมูลเพิ่มเติม


นี่คือสิ่งที่ฉันจะแนะนำ เป็นวิธีที่ดีที่สุดและเร็วที่สุดโดยถือว่าข้อผิดพลาดเกี่ยวกับความแม่นยำไม่ใช่ปัญหา
Ray Hidayat

2
ฉันตัดสินใจใช้ Algorithm ของ 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 ชี้ให้เห็นปัญหาที่ถูกต้อง - สูตรด้านบนแบ่งเป็น x> 1 เพราะคุณได้ sqrt ของจำนวนลบ วิธี Knuth คือ sqrt ((sum_x2 / n) - (mean * mean)) โดยที่ mean = (sum_x / n)
G__

1
@UriLoya - คุณไม่ได้พูดอะไรเกี่ยวกับการคำนวณค่าต่างๆ อย่างไรก็ตามหากคุณใช้intใน C เพื่อเก็บผลรวมของกำลังสองคุณจะพบปัญหาล้นกับค่าที่คุณระบุ
Jonathan Leffler

38

นี่คือการแปล Python อย่างแท้จริงของการใช้อัลกอริทึมของ Welford จากhttp://www.johndcook.com/standard_deviation.html :

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

บางทีอาจจะไม่ใช่สิ่งที่คุณถาม แต่ ... หากคุณใช้อาร์เรย์จำนวนนับมันจะทำงานให้คุณได้อย่างมีประสิทธิภาพ:

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 โมดูลสำหรับเพียงการเรียงลำดับของสิ่งนี้ ติดตั้ง 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()

สรุปสถิติเป็นไปตามวิธี Knuth และ Welford สำหรับการคำนวณค่าเบี่ยงเบนมาตรฐานในรอบเดียวตามที่อธิบายไว้ใน Art of Computer Programming, Vol 2, p 232, พิมพ์ครั้งที่ 3. ประโยชน์ของสิ่งนี้คือผลลัพธ์ที่มีความเสถียรและแม่นยำในเชิงตัวเลข

ข้อจำกัดความรับผิดชอบ:ฉันเป็นผู้เขียนโมดูล Python runstats


โมดูลที่ดี มันจะเป็นที่น่าสนใจถ้ามีStatisticsมี.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สำหรับข้อมูลเพิ่มเติมเกี่ยวกับฟังก์ชันstatsover ดูเหมือนว่า ADEV จะเป็น "ค่าเบี่ยงเบนมาตรฐาน"

อย่างไรก็ตามอาจเป็น PRMS (ซึ่ง Sinan's Statistics :: Descriptive ตัวอย่างแสดง) หรือ RMS (ซึ่งตัวอย่าง NumPy ของ ars แสดงให้เห็น) ฉันเดาว่าหนึ่งในสามคนนี้ต้องถูก ;-)

สำหรับข้อมูล PDL เพิ่มเติมโปรดดูที่:


1
นี่ไม่ใช่การคำนวณที่กำลังทำงานอยู่
Jake

3

อาร์เรย์ของคุณใหญ่แค่ไหน? เว้นแต่จะมีความยาว zillions ไม่ต้องกังวลว่าจะวนซ้ำสองครั้ง รหัสนี้ง่ายและทดสอบได้ง่าย

ความชอบของฉันคือใช้ส่วนขยายคณิตศาสตร์อาร์เรย์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

นี่ไม่ได้เกือบจะสวยหรูเท่าวิธีแก้ปัญหาความเข้าใจรายการด้านบน


จริงๆแล้วฉันต้องจัดการกับจำนวน zillions ซึ่งเป็นสิ่งที่กระตุ้นให้ฉันต้องการโซลูชันที่มีประสิทธิภาพ ขอบคุณ!
Alex Reynolds

มันไม่ได้เกี่ยวกับว่าชุดข้อมูลนั้นใหญ่แค่ไหนมันเกี่ยวกับวิธีการบ่อยฉันต้องทำการคำนวณค่าเบี่ยงเบนมาตรฐานที่แตกต่างกัน 3500 รายการมากกว่า 500 องค์ประกอบในการคำนวณแต่ละครั้งต่อวินาที
PirateApp

1

คุณสามารถดูบทความ Wikipedia เกี่ยวกับStandard Deviationโดยเฉพาะในส่วนเกี่ยวกับวิธีการคำนวณอย่างรวดเร็ว

นอกจากนี้ยังมีบทความที่ฉันพบว่าใช้ Python, คุณควรจะสามารถในการใช้รหัสในนั้นโดยไม่มีการเปลี่ยนแปลงมาก: ข้อความอ่อน - วิ่งเบี่ยงเบนมาตรฐาน


เวอร์ชัน Subliminal Messages ไม่เสถียรในเชิงตัวเลข
Dave


1

นี่คือ "one-liner" ซึ่งกระจายไปหลายบรรทัดในรูปแบบการเขียนโปรแกรมเชิงฟังก์ชัน:

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

ดังที่คำตอบต่อไปนี้อธิบายไว้: pandas / scipy / numpy มีฟังก์ชันค่าเบี่ยงเบนมาตรฐานสะสมหรือไม่ โมดูลหลามนุ่นมีวิธีการคำนวณการทำงานหรือเบี่ยงเบนมาตรฐานสะสม เพื่อที่คุณจะต้องแปลงข้อมูลของคุณเป็นดาต้าเฟรมของแพนด้า (หรือชุดข้อมูลหากเป็น 1D) แต่มีฟังก์ชันสำหรับสิ่งนั้น


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)

เพื่อให้ฟังก์ชัน one-pass มีลักษณะดังนี้:

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) ไม่ใช่ค่าประมาณที่เป็นกลางของความแปรปรวนของประชากร (ซึ่งใช้ตัวประกอบ normalzation 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)

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

สิ่งนี้จะพิมพ์ค่าเบี่ยงเบนมาตรฐานที่คำนวณได้และตรวจสอบค่าเบี่ยงเบนมาตรฐานที่คำนวณด้วยตัวเลข:

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.