Как эффективно рассчитать текущее стандартное отклонение?

64

У меня есть список списков чисел, например:

[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/…
  • 0
    Спасибо, я проверю этот алгоритм. Похоже, что мне нужно.
Показать ещё 3 комментария
Теги:
statistics

13 ответов

85
Лучший ответ

Ответ заключается в использовании алгоритма Welford, который очень четко определен после "наивных методов" в:

Он более численно устойчив, чем двухпроходная или онлайн простая сумма квадратов коллекционеров, предложенная в других ответах. Устойчивость действительно имеет значение, когда у вас много значений, которые близки друг к другу, поскольку они приводят к так называемому катастрофическому отмене в литература с плавающей точкой.

Вы также можете освежить разницу между делением на количество выборок (N) и N-1 в вычислении дисперсии (отклонение в квадрате). Разделение на N-1 приводит к несмещенной оценке дисперсии по выборке, тогда как деление на N в среднем недооценивает дисперсию (поскольку оно не учитывает дисперсию между средним значением выборки и истинным средним значением).

Я написал две записи в блоге по этой теме, которые приводятся в более подробной информации, в том числе о том, как удалить предыдущие значения в Интернете:

Вы также можете взглянуть на мой Java-инструмент; тесты javadoc, source и unit все онлайн:

  • 1
    +1, за заботу об удалении значений из алгоритма Уэлфорда
  • 1
    Хороший ответ, +1 за напоминание читателю о разнице между популяцией stddev и образцом stddev.
Показать ещё 1 комментарий
64

Основным ответом является накопление суммы как x (назовем его sum_x1), так и x 2 (назовите его 'sum_x2') по ходу. Значение стандартного отклонения:

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

где

mean = sum_x / n

Это стандартное отклонение выборки; вы получаете стандартное отклонение населения, используя "n" вместо "n - 1" в качестве делителя.

Возможно, вам придется беспокоиться о численной стабильности при разнице между двумя большими числами, если вы имеете дело с большими образцами. Перейдите к внешним ссылкам в других ответах (Wikipedia и т.д.) Для получения дополнительной информации.

  • 0
    Это то, что я собирался предложить. Это лучший и самый быстрый способ, при условии, что ошибки точности не являются проблемой.
  • 2
    Я решил пойти с алгоритмом Уэлфорда, так как он работает более надежно с теми же вычислительными затратами.
Показать ещё 6 комментариев
25

Возможно, не то, что вы просили, но... Если вы используете массив 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 ]

Кстати, в этом блоге есть интересная дискуссия и комментарии к однопроходным методам вычисления средств и отклонений:

15

Вот буквальный чистый перевод Python реализации алгоритма Welford из http://www.johndcook.com/standard_deviation.html:

https://github.com/liyanage/python-modules/blob/master/running_stats.py

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();
  • 3
    Это должен быть принятый ответ, так как он единственный, который является правильным и показывает алгоритм со ссылкой на Кнута.
8

Модуль Python runstats Module предназначен именно для этого. Установить 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()

Статистические сводки основаны на методе Кнута и Велфорда для вычисления стандартного отклонения за один проход, как описано в Art of Computer Programming, Vol. 2, p. 232, 3-е издание. Преимущество этого - это численно стабильные и точные результаты.

Отказ от ответственности: Я являюсь автором модуля runstats Python.

  • 0
    Хороший модуль. Было бы интересно, если бы у Statistics была метод .pop чтобы можно было также рассчитать скользящую статистику.
  • 0
    @GustavoBezerra runstats не поддерживает внутренний список значений, поэтому я не уверен, что это возможно. Но запросы на тягу приветствуются.
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 Statistics:: Descriptive example) или RMS (что показывает пример NumPy). Я думаю, что один из этих трех должен быть прав: -)

Для получения дополнительной информации о PDL посмотрите:

  • 0
    Это не текущий расчет.
7

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

Это не так элегантно, как решение для понимания списка выше.

  • 0
    На самом деле мне приходится иметь дело с миллионами чисел, что мотивирует мою потребность в эффективном решении. Спасибо!
2

Вы можете посмотреть статью Википедии на Стандартное отклонение, в частности раздел о методах расчета Rapid.

Также найдена статья, в которой используется Python, вы можете использовать код в ней без особых изменений: Subliminal Messages - Выполнение стандартных отклонений.

  • 0
    Версия Subliminal Messages не очень стабильна численно.
2

Я думаю, эта проблема вам поможет. Стандартное отклонение

  • 0
    +1 @Lasse V. Карлсен ссылается на Википедию хорошо, но это правильный алгоритм, который я использовал ...
1

Как говорится в следующем ответе: Предоставляет ли pandas/scipy/numpy функцию кумулятивного стандартного отклонения? Модуль Python Pandas содержит метод вычисления текущего или совокупного стандартного отклонения. Для этого вам нужно будет преобразовать ваши данные в фреймворк Pandas (или серию, если это 1D), но для этого есть функции.

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
0

Здесь "однострочный", распространяемый по нескольким строкам, в стиле функционального программирования:

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню