Як ефективно розрахувати стандартне відхилення, що працює?


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 --- Екс-модератор кошеня

Дякую, я перевірю цей алгоритм. Звучить як те, що мені потрібно.
Алекс Рейнольдс

Дякую, що вказали мені на правильну відповідь, dmckee. Я хотів би поставити вам галочку "найкраща відповідь", якщо ви хочете трохи виділити свою відповідь нижче (якщо вам потрібні бали).
Алекс Рейнольдс,

1
Крім того, є кілька прикладів на rosettacode.org/wiki/Standard_Deviation
glenn jackman

1
У Вікіпедії є реалізація Python en.wikipedia.org/wiki/…
Гаміш Грубіян

Відповіді:


116

Відповідь полягає у використанні алгоритму Велфорда, який дуже чітко визначений після "наївних методів" у:

Він чисельніше стабільніший, ніж двоходовий або простий в Інтернеті простий збірник квадратів, запропонований в інших відповідях. Стійкість насправді має значення лише тоді, коли у вас багато значень, близьких одне до одного, оскільки вони ведуть до того, що в літературі з плаваючою комою називається " катастрофічним скасуванням ".

Можливо, ви також захочете уточнити різницю між діленням на кількість зразків (N) та N-1 у розрахунку дисперсії (квадратичне відхилення). Поділ на N-1 призводить до неупередженої оцінки дисперсії від вибірки, тоді як ділення на N в середньому занижує дисперсію (оскільки вона не враховує дисперсію між середнім значенням вибірки та дійсним середнім значенням).

Я написав два записи в блозі на цю тему, які детальніше, зокрема про те, як видалити попередні значення в мережі:

Ви також можете поглянути на мою реалізацію Java; javadoc, джерело та модульні тести - всі онлайн:


1
+1, за
подбання

3
Приємна відповідь, +1 за нагадування читачеві про різницю між популяцією stddev та зразком stddev.
Асад Ебрагім

Повернувшись до цього питання через усі ці роки, я просто хотів сказати подяку за те, що знайшов час, щоб надати чудову відповідь.
Алекс Рейнольдс,

76

Основна відповідь полягає в тому, щоб накопичувати суму як x (назвіть це "sum_x1"), так і x 2 (називайте це "sum_x2") під час руху. Тоді значення стандартного відхилення:

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

де

mean = sum_x / n

Це стандартне відхилення вибірки; ви отримуєте стандартне відхилення сукупності, використовуючи 'n' замість 'n - 1' як дільник.

Можливо, вам доведеться турбуватися про чисельну стабільність прийому різниці між двома великими числами, якщо ви маєте справу з великими вибірками. Для отримання додаткової інформації перейдіть до зовнішніх посилань в інших відповідях (Вікіпедія тощо).


Це те, що я збирався запропонувати. Це найкращий і найшвидший спосіб, якщо припустити, що помилки точності не є проблемою.
Ray Hidayat

2
Я вирішив піти на алгоритм Велфорда, оскільки він працює надійніше з тими самими обчислювальними витратами.
Алекс Рейнольдс,

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 від'ємного числа. Підхід Кнута: sqrt ((sum_x2 / n) - (середнє * середнє)) де середнє = (sum_x / n).
G__

1
@UriLoya - ти нічого не говорив про те, як ти обчислюєш значення. Однак, якщо ви використовуєте intв C для зберігання суми квадратів, у вас виникають проблеми із переповненням значень, які ви перелічуєте.
Джонатан Леффлер,

38

Ось буквальний чистий переклад Python реалізації алгоритму Велфорда з 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
Це має бути прийнятою відповіддю, оскільки вона є єдиною правильною та відображає алгоритм із посиланням на Кнута.
Йоган Лундберг,

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

У модуль Python Runstats для тільки такого роду речі. Встановіть runtats з PyPI:

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

Резюме статистики базується на методі Кнута та Уелфорда для обчислення середньоквадратичного відхилення за один прохід, як описано в Мистецтві комп'ютерного програмування, том 2, с. 232, 3-е видання. Перевагою цього є чисельно стабільні та точні результати.

Застереження: я є автором модуля запуску Python.


Приємний модуль. Було б цікаво , якби там було Statisticsє .popметод так прокатна статистика також може бути обчислена.
Густаво Безерра

@GustavoBezerra runstatsне підтримує внутрішній список значень, тому я не впевнений, що це можливо. Але запрошення на витягування вітаються.
GrantJ

8

Статистика :: Опис - це дуже пристойний модуль 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 (яку показує приклад "Статистика Синана :: Описовий приклад") або RMS (яку показує приклад NumPy ars). Думаю, один із цих трьох повинен бути правий ;-)

Щоб отримати додаткову інформацію про PDL, перегляньте:


1
Це не поточний розрахунок.
Джейк

3

Наскільки великий ваш масив? Якщо це не довгі мільйони елементів, не турбуйтеся про те, щоб прокрутити їх двічі. Код простий і легко перевіряється.

Моїм уподобанням було б використовувати математичне розширення масиву numpy, щоб перетворити ваш масив масивів у двовимірний масив numpy і отримати стандартне відхилення безпосередньо:

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

Це не настільки елегантно, як рішення для розуміння списку вище.


Насправді мені доводиться мати справу з мільйонами чисел, що мотивує мою потребу в ефективному рішенні. Дякую!
Алекс Рейнольдс,

справа не в тому, наскільки великий набір даних, а в тому, ЧАСТО, мені доводиться робити 3500 різних розрахунків стандартного відхилення понад 500 елементів на кожному розрахунку в секунду
PirateApp

1

Ви можете переглянути статтю Вікіпедії про стандартне відхилення , зокрема розділ про швидкі методи обчислення.

Також я знайшов статтю, яка використовує Python, і ви повинні мати можливість використовувати в ньому код без особливих змін: Підсвідомі повідомлення - Запуск стандартних відхилень .


Версія Subliminal Messages чисельно не дуже стабільна.
Дейв


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

Як описується наступна відповідь: Чи забезпечують pandas / scipy / numpy сукупну функцію стандартного відхилення? Модуль Python Pandas містить метод для обчислення поточного або кумулятивного стандартного відхилення . Для цього вам доведеться перетворити ваші дані у фрейм даних pandas (або в серію, якщо вона 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)

так що однопрохідна функція буде виглядати так:

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 / (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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.