Làm thế nào để tính toán một cách hiệu quả độ lệch chuẩn đang chạy?


87

Tôi có một mảng danh sách các số, ví dụ:

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

Những gì tôi muốn làm là tính toán hiệu quả giá trị trung bình và độ lệch chuẩn tại mỗi chỉ mục của danh sách, trên tất cả các phần tử mảng.

Để làm được điều đó, tôi đã lặp qua mảng và tính tổng giá trị tại một chỉ mục nhất định của danh sách. Cuối cùng, tôi chia từng giá trị trong "danh sách trung bình" của mình cho n(Tôi đang làm việc với một tập hợp, không phải một mẫu từ tổng thể).

Để thực hiện độ lệch chuẩn, tôi lặp lại một lần nữa, bây giờ tôi đã tính được giá trị trung bình.

Tôi muốn tránh đi qua mảng hai lần, một lần cho giá trị trung bình và sau đó một lần cho SD (sau khi tôi có giá trị trung bình).

Có phương pháp nào hiệu quả để tính cả hai giá trị mà chỉ duyệt qua mảng một lần không? Bất kỳ mã nào bằng ngôn ngữ thông dịch (ví dụ: Perl hoặc Python) hoặc mã giả đều tốt.


7
Ngôn ngữ khác nhau, nhưng cùng một thuật toán: stackoverflow.com/questions/895929/...
dmckee --- cựu điều hành kitten

Cảm ơn, tôi sẽ kiểm tra thuật toán đó. Nghe giống như những gì tôi cần.
Alex Reynolds

Cảm ơn vì đã chỉ cho tôi câu trả lời đúng, dmckee. Tôi muốn cung cấp cho bạn dấu kiểm "câu trả lời hay nhất", nếu bạn muốn dành một chút thời gian để thêm câu trả lời của mình bên dưới (nếu bạn muốn có điểm).
Alex Reynolds,

1
Ngoài ra, có rất nhiều ví dụ ở rosettacode.org/wiki/Standard_Deviation
glenn jackman

1
Wikipedia có một thực hiện Python en.wikipedia.org/wiki/...
Hamish Grubijan

Câu trả lời:


116

Câu trả lời là sử dụng thuật toán của Welford, được xác định rất rõ ràng sau "phương pháp ngây thơ" trong:

Nó ổn định hơn về mặt số học so với hai lần vượt qua hoặc tổng bình phương đơn giản trực tuyến được đề xuất trong các câu trả lời khác. Sự ổn định chỉ thực sự quan trọng khi bạn có nhiều giá trị gần nhau vì chúng dẫn đến cái được gọi là " sự hủy bỏ thảm khốc " trong văn học dấu phẩy động.

Bạn cũng có thể muốn tìm hiểu sự khác biệt giữa phép chia cho số lượng mẫu (N) và N-1 trong phép tính phương sai (độ lệch bình phương). Chia cho N-1 dẫn đến ước tính phương sai không chệch khỏi mẫu, trong khi chia cho N trung bình sẽ đánh giá thấp phương sai (vì nó không tính đến phương sai giữa giá trị trung bình mẫu và giá trị trung bình thực).

Tôi đã viết hai mục blog về chủ đề này đi vào chi tiết hơn, bao gồm cách xóa các giá trị trước đó trực tuyến:

Bạn cũng có thể xem triển khai Java của tôi; javadoc, source và unit test đều trực tuyến:


1
+1, cho chăm sóc về việc xóa các giá trị từ thuật toán Welford của
Svisstack

3
Câu trả lời hay, +1 để nhắc nhở người đọc về sự khác biệt giữa stddev dân số và stddev mẫu.
Assad Ebrahim

Sau nhiều năm quay lại câu hỏi này, tôi chỉ muốn nói một lời cảm ơn vì đã dành thời gian để cung cấp một câu trả lời tuyệt vời.
Alex Reynolds

76

Câu trả lời cơ bản là tích tổng của cả x (gọi là 'sum_x1') và x 2 (gọi là 'sum_x2') khi bạn thực hiện. Giá trị của độ lệch chuẩn khi đó là:

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

Ở đâu

mean = sum_x / n

Đây là độ lệch chuẩn mẫu; bạn nhận được độ lệch chuẩn tổng thể bằng cách sử dụng 'n' thay vì 'n - 1' làm số chia.

Bạn có thể cần phải lo lắng về tính ổn định số của việc lấy chênh lệch giữa hai số lớn nếu bạn đang xử lý các mẫu lớn. Đi tới phần tham khảo bên ngoài trong các câu trả lời khác (Wikipedia, v.v.) để biết thêm thông tin.


Đây là những gì tôi sẽ đề xuất. Đó là cách tốt nhất và nhanh nhất, giả sử lỗi chính xác không phải là vấn đề.
Ray Hidayat,

2
Tôi quyết định sử dụng Thuật toán của Welford vì nó hoạt động đáng tin cậy hơn với cùng chi phí tính toán.
Alex Reynolds,

2
Đây là phiên bản đơn giản hóa của câu trả lời và có thể cho kết quả không thực tùy thuộc vào đầu vào (tức là khi sum_x2 <sum_x1 * sum_x1). Để đảm bảo kết quả thực hợp lệ, đi với `sd = sqrt (((n * sum_x2) - (sum_x1 * sum_x1)) / (n * (n - 1)))
Dan Tao

2
@Dan chỉ ra một vấn đề hợp lệ - công thức ở trên được chia nhỏ cho x> 1 vì cuối cùng bạn lấy sqrt của một số âm. Cách tiếp cận Knuth là: sqrt ((sum_x2 / n) - (mean * mean)) trong đó mean = (sum_x / n).
G__

1
@UriLoya - bạn chưa nói gì về cách bạn tính toán các giá trị. Tuy nhiên, nếu bạn sử dụng inttrong C để lưu trữ tổng các ô vuông, bạn sẽ gặp phải vấn đề tràn với các giá trị mà bạn liệt kê.
Jonathan Leffler

38

Đây là bản dịch Python thuần túy theo nghĩa đen về việc triển khai thuật toán Welford từ http://www.johndcook.com/standard_defining.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())

Sử dụng:

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
Đây phải là câu trả lời được chấp nhận vì nó là câu duy nhất vừa đúng vừa hiển thị thuật toán, có tham chiếu đến Knuth.
Johan Lundberg,

26

Có lẽ không phải những gì bạn đang hỏi, nhưng ... Nếu bạn sử dụng một mảng numpy, nó sẽ thực hiện công việc cho bạn một cách hiệu quả:

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 ]

Nhân tiện, có một số cuộc thảo luận thú vị trong bài đăng blog này và nhận xét về các phương pháp một lần cho các phương tiện và phương sai tính toán:


14

Các Python runstats Mô-đun là chỉ các loại điều này. Cài đặt runstats từ PyPI:

pip install runstats

Tóm tắt Runstats có thể tạo ra giá trị trung bình, phương sai, độ lệch chuẩn, độ lệch và độ lệch trong một lần truyền dữ liệu. Chúng tôi có thể sử dụng điều này để tạo phiên bản "đang chạy" của bạn.

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

Các tóm tắt thống kê dựa trên phương pháp Knuth và Welford để tính toán độ lệch chuẩn trong một lần chuyển như được mô tả trong Nghệ thuật Lập trình Máy tính, Tập 2, tr. 232, tái bản lần thứ 3. Lợi ích của việc này là kết quả ổn định và chính xác về mặt số học.

Tuyên bố từ chối trách nhiệm: Tôi là tác giả của mô-đun runstats Python.


Mô-đun đẹp. Sẽ rất thú vị nếu có Statisticsmột .popphương pháp để có thể tính toán thống kê luân phiên.
Gustavo Bezerra

@GustavoBezerra runstatskhông duy trì danh sách giá trị nội bộ nên tôi không chắc điều đó có thể xảy ra. Nhưng yêu cầu kéo được hoan nghênh.
GrantJ

8

Thống kê :: Mô tả là một mô-đun Perl rất phù hợp cho các loại tính toán này:

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

Đầu ra:

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

Hãy xem PDL (phát âm là "piddle!").

Đây là Ngôn ngữ Dữ liệu Perl được thiết kế cho toán học và máy tính khoa học có độ chính xác cao.

Đây là một ví dụ sử dụng số liệu của bạn ...

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;


Sản xuất:

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]


Hãy xem PDL :: Primitive để biết thêm thông tin về chức năng statover . Điều này dường như cho thấy rằng ADEV là "độ lệch chuẩn".

Tuy nhiên nó có thể là PRMS (mà Sinan's Statistics :: Descriptive ví dụ hiển thị) hoặc RMS (mà ví dụ NumPy của ars cho thấy). Tôi đoán một trong ba điều này phải đúng ;-)

Để biết thêm thông tin PDL, hãy xem tại:


1
Đây không phải là một phép tính đang chạy.
Jake

3

Mảng của bạn lớn như thế nào? Trừ khi nó dài các phần tử, đừng lo lắng về việc lặp lại nó hai lần. Mã này đơn giản và dễ dàng kiểm tra.

Sở thích của tôi sẽ được sử dụng NumPy mở rộng toán mảng để chuyển đổi mảng lại mảng thành một NumPy 2D mảng và có được độ lệch chuẩn trực tiếp:

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

Nếu đó không phải là một tùy chọn và bạn cần một giải pháp Python thuần túy, hãy tiếp tục đọc ...

Nếu mảng của bạn là

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

Khi đó độ lệch chuẩn là:

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

Nếu bạn quyết tâm lặp lại mảng của mình chỉ một lần, các tổng đang chạy có thể được kết hợp.

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

Điều này gần như không thanh lịch như giải pháp hiểu danh sách ở trên.


Tôi thực sự phải đối mặt với hàng loạt con số, đó là động lực thúc đẩy tôi cần một giải pháp hiệu quả. Cảm ơn!
Alex Reynolds,

Nó không phải về mức độ lớn của tập dữ liệu, mà là về cách OFTEN, tôi phải thực hiện 3500 phép tính độ lệch chuẩn khác nhau trên 500 phần tử trên mỗi phép tính mỗi giây
PirateApp


1

Tôi nghĩ vấn đề này sẽ giúp ích cho bạn. Độ lệch chuẩn


Liên kết 1 @Lasse V. của Karlsen để tốt của Wikipedia, nhưng đây là thuật toán đúng Tôi đã sử dụng ...
kenny

1

Đây là "một lớp lót", trải dài trên nhiều dòng, theo phong cách lập trình chức năng:

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

Tôi muốn thể hiện cập nhật theo cách này:

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)

để hàm một lần sẽ trông như thế này:

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)

lưu ý rằng đây là tính toán phương sai mẫu (1 / N), không phải ước tính không thiên vị của phương sai tổng thể (sử dụng hệ số chuẩn hóa 1 / (N-1)). Không giống như các câu trả lời khác, biến, vartheo dõi phương sai đang chạy không phát triển tương ứng với số lượng mẫu. Tại mọi thời điểm, nó chỉ là phương sai của tập hợp các mẫu được nhìn thấy cho đến nay (không có "chia cho n" cuối cùng để nhận được phương sai).

Trong một lớp, nó sẽ như thế này:

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

Điều này cũng hoạt động đối với các mẫu có trọng số:

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)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.