Вычисление дисперсии с большими числами
-
19-09-2019 - |
Вопрос
На самом деле я не так часто использовал расчет дисперсии и не совсем знаю, чего ожидать.На самом деле я совсем не силен в математике.
У меня есть массив из 1000000 случайных числовых значений в диапазоне 0-10000.
Массив может вырасти еще больше, поэтому для суммы я использую 64-битное целое число.
Я пытался найти код расчета дисперсии, но не знаю, получу ли я правильный результат.
Среднее значение составляет 4692, а медиана — 4533.Я получаю отклонение 1483780.469308, используя следующий код:
// size is the element count, in this case 1000000
// value_sum is __int64
double p2 = pow( (double)(value_sum - (value_sum/size)), (double)2.0 );
double variance = sqrt( (double)(p2 / (size-1)) );
Получаю ли я разумную ценность?
Что-то не так с расчетом?
Решение
Примечание: Не похоже, что вы рассчитываете дисперсию.
Дисперсия рассчитывается путем вычитания среднего значения из каждого элемента и расчета взвешенной суммы этих различий.
Итак, что вам нужно сделать, это:
// Get mean
double mean = static_cast<double>(value_sum)/size;
// Calculate variance
double variance = 0;
for(int i = 0;i<size;++i)
{
variance += (MyArray[i]-mean)*(MyArray[i]-mean)/size;
}
// Display
cout<<variance;
Обратите внимание, что это выборочная дисперсия, которая используется, когда основное распределение неизвестно (поэтому мы предполагаем равномерное распределение).
Кроме того, после некоторого копания я обнаружил, что это не беспристрастная оценка. вольфрам Альфа есть что сказать по этому поводу, но в качестве примера, когда МАТЛАБ вычисляет дисперсию и возвращает «выборочную дисперсию с поправкой на систематическое отклонение».
Дисперсия с поправкой на смещение может быть получена путем деления каждого элемента на size-1
, или:
//Please check that size > 1
variance += (MyArray[i]-mean)*(MyArray[i]-mean)/(size-1);
Также обратите внимание, что значение mean
остается такой же.
Другие советы
Прежде всего, если вы просто хотите понять, что такое «разумная» дисперсия, имейте в виду, что дисперсия, по сути, представляет собой квадрат стандартного отклонения.Стандартное отклонение примерно измеряет типичное расстояние от точки данных до ее ожидаемого значения.
Итак, если среднее значение ваших данных равно 4692, а расчетная дисперсия равна 1483780, это означает, что ваше стандартное отклонение составляет около 1218, что предполагает, что ваши числа, как правило, находятся где-то в диапазоне 3474–5910.Так что эта дисперсия на самом деле кажется мне немного низкой, если диапазон ваших чисел составляет 0–10 000;но это, очевидно, зависит от распределения ваших данных.
Что касается самого расчета:Вы можете рассчитать дисперсию, используя текущий расчет, когда вы читаете данные в первый раз (вам не обязательно заранее знать среднее значение), используя Метод Уэлфорда:
Инициализируйте M1 = x1 и S1 = 0.
Для последующих х используйте повторение формулы
Mk = Mk-1 (xk - Mk-1)/k Sk = Sk-1 (xk - Mk-1)*(xk - Mk).
Для 2 ≤ k ≤ n - kth оценка дисперсия s2 = Sk/(k - 1).
Просто ради интереса, немного другой путь к тому же результату с использованием std::valarray вместо std::vector и (различных) алгоритмов:
template <class T>
T const variance(std::valarray<T> const &v) {
if (v.size() == 0)
return T(0.0);
T average = v.sum() / v.size();
std::valarray<T> diffs = v-average;
diffs *= diffs;
return diffs.sum()/diffs.size();
}
Как намекнул Джейкоб, на самом деле существует две возможные версии расчета дисперсии.В нынешнем виде предполагается, что ваши входные данные являются «вселенной».Если вы взяли только образец всей вселенной, в последней строке следует использовать: (diffs.size()-1)
вместо diffs.size()
.
Может быть, использовать другую формулу?
#include <functional>
#include <algorithm>
#include <iostream>
int main()
{
using namespace std;
vector<double> num( 3 );
num[ 0 ] = 4000.9, num[ 1 ] = 11111.221, num[ 2 ] = -2;
double mean = std::accumulate(num.begin(), num.end(), 0.0) / num.size();
vector<double> diff(num.size());
std::transform(num.begin(), num.end(), diff.begin(),
std::bind2nd(std::minus<double>(), mean));
double variance = std::inner_product(diff.begin(), diff.end(),
diff.begin(), 0.0) / (num.size() - 1);
cout << "mean = " << mean << endl
<< "variance = " << variance << endl;
}
Выходы:среднее значение = 5036,71 дисперсия = 3,16806e 07
Пример расчета отклонения:
#include <math.h>
#include <vector>
double Variance(std::vector<double>);
int main()
{
std::vector<double> samples;
samples.push_back(2.0);
samples.push_back(3.0);
samples.push_back(4.0);
samples.push_back(5.0);
samples.push_back(6.0);
samples.push_back(7.0);
double variance = Variance(samples);
return 0;
}
double Variance(std::vector<double> samples)
{
int size = samples.size();
double variance = 0;
double t = samples[0];
for (int i = 1; i < size; i++)
{
t += samples[i];
double diff = ((i + 1) * samples[i]) - t;
variance += (diff * diff) / ((i + 1.0) *i);
}
return variance / (size - 1);
}
Поскольку вы работаете с большими числами, а затем выполняете над ними операции с плавающей запятой, возможно, вам захочется сделать все в двойном формате;это сэкономит вам много забросов.
С использованием pow .. 2
вычисление квадрата кажется немного неудобным.Вы можете сначала вычислить свое число, а затем умножить его само на себя, чтобы получить квадрат.
Если вы выполняете деление и чувствуете необходимость притворения, примените операнды (т.е.числитель и/или знаменатель), а не результат.Вы теряете точность, если делите целые числа.
Я не уверен, верна ли ваша формула дисперсии.Вы можете посмотреть объяснение, например, в Википедии.Но я тоже не эксперт по математике, поэтому не уверен, что вы ошиблись.
Поскольку дисперсия представляет собой квадрат стандартного отклонения, ответы на СО 1174984 должно помочь.Краткий диагноз заключается в том, что вам нужно вычислить сумму квадратов значений, а также сумму значений, а вы, похоже, этого не делаете.
Поскольку у тебя есть 106 значений, а квадрат любого значения может достигать 108, вы можете получить сумму квадратов до 1014;ваши 64-битные целые числа могут хранить до 1018, поэтому вы по-прежнему можете обрабатывать в десять тысяч раз больше входных данных или значения в диапазоне до одного миллиона вместо десяти тысяч, не сталкиваясь при этом с переполнением.Поэтому нет острой необходимости переходить к чистым двойным вычислениям.