В ответе Джейсона Р. есть недостаток, который обсуждается в книге Кнута «Искусство компьютерного программирования», том. 2. Проблема возникает, если у вас есть стандартное отклонение, которое составляет небольшую долю от среднего значения: вычисление E (x ^ 2) - (E (x) ^ 2) страдает от серьезной чувствительности к ошибкам округления с плавающей запятой.
Вы даже можете попробовать это самостоятельно в скрипте Python:
ofs = 1e9
A = [ofs+x for x in [1,-1,2,3,0,4.02,5]]
A2 = [x*x for x in A]
(sum(A2)/len(A))-(sum(A)/len(A))**2
В качестве ответа я получаю -128,0, что явно не в вычислительном отношении, поскольку математика предсказывает, что результат должен быть неотрицательным.
Кнут цитирует подход (я не помню имя изобретателя) для расчета среднего значения и стандартного отклонения, который выглядит примерно так:
initialize:
m = 0;
S = 0;
n = 0;
for each incoming sample x:
prev_mean = m;
n = n + 1;
m = m + (x-m)/n;
S = S + (x-m)*(x-prev_mean);
и затем после каждого шага значение m
является средним, и стандартное отклонение может быть рассчитано как sqrt(S/n)
или в sqrt(S/n-1)
зависимости от того, какое ваше любимое определение стандартного отклонения.
Уравнение, которое я пишу выше, немного отличается от уравнения Кнута, но оно эквивалентно в вычислительном отношении.
Когда у меня будет еще несколько минут, я напишу приведенную выше формулу в Python и покажу, что вы получите неотрицательный ответ (который, мы надеемся, близок к правильному значению).
Обновление: вот оно.
test1.py:
import math
def stats(x):
n = 0
S = 0.0
m = 0.0
for x_i in x:
n = n + 1
m_prev = m
m = m + (x_i - m) / n
S = S + (x_i - m) * (x_i - m_prev)
return {'mean': m, 'variance': S/n}
def naive_stats(x):
S1 = sum(x)
n = len(x)
S2 = sum([x_i**2 for x_i in x])
return {'mean': S1/n, 'variance': (S2/n - (S1/n)**2) }
x1 = [1,-1,2,3,0,4.02,5]
x2 = [x+1e9 for x in x1]
print "naive_stats:"
print naive_stats(x1)
print naive_stats(x2)
print "stats:"
print stats(x1)
print stats(x2)
результат:
naive_stats:
{'variance': 4.0114775510204073, 'mean': 2.0028571428571427}
{'variance': -128.0, 'mean': 1000000002.0028572}
stats:
{'variance': 4.0114775510204073, 'mean': 2.0028571428571431}
{'variance': 4.0114775868357446, 'mean': 1000000002.0028571}
Вы заметите, что все еще есть некоторая ошибка округления, но это не плохо, тогда как naive_stats
просто рвёт.
редактировать: только что заметил комментарий Велисария со ссылкой на Википедию, в которой упоминается алгоритм Кнута.