Расчет дисперсии с большими числами - PullRequest
2 голосов
/ 12 ноября 2009

Я на самом деле не слишком много использовал расчет дисперсии, и я не знаю, чего ожидать. На самом деле я не слишком хорош в математике.

У меня есть массив из 1000000 случайных числовых значений в диапазоне 0-10000.

Массив может вырасти еще больше, поэтому я использую 64-битное int для суммы.

Я пытался найти код для вычисления дисперсии, но я не знаю, получаю ли я правильный вывод.

Среднее значение составляет 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)) );

Получаю ли я разумную стоимость?

Что-то не так с расчетом?

Ответы [ 7 ]

5 голосов
/ 12 ноября 2009

Примечание: Не похоже, что вы вычисляете дисперсию.

Дисперсия рассчитывается путем вычитания среднего значения из каждого элемента и вычисления взвешенной суммы этих разностей.

Итак, что вам нужно сделать, это:

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

Обратите внимание, что это выборочная дисперсия, и она используется, когда базовое распределение неизвестно (поэтому мы предполагаем равномерное распределение).

Кроме того, после некоторого осмотра я обнаружил, что это не объективная оценка. Wolfram Alpha есть что сказать по этому поводу, но в качестве примера, когда MATLAB вычисляет дисперсию, он возвращает "выборочную дисперсию с поправкой на смещение".

Дисперсию с поправкой на смещение можно получить путем деления каждого элемента на size-1 или:

//Please check that size > 1
variance += (MyArray[i]-mean)*(MyArray[i]-mean)/(size-1); 

Также обратите внимание, что значение mean остается неизменным.

3 голосов
/ 12 ноября 2009

Ради интереса, немного другой путь к тому же результату, используя 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().

3 голосов
/ 12 ноября 2009

Прежде всего, если вы просто хотите разобраться в том, что такое «разумная» разница, имейте в виду, что дисперсия в основном является квадратом стандартного отклонения. Стандартное отклонение приблизительно измеряет типичное расстояние от точки данных до ее ожидаемого значения.

Так что, если ваши данные имеют среднее значение 4692, а ваша расчетная дисперсия выходит на 1483780, это означает, что ваше стандартное отклонение составляет около 1218, что предполагает, что ваши числа, как правило, находятся где-то в диапазоне от 3474 до 5910. эта разница на самом деле кажется мне немного низкой, если диапазон ваших чисел составляет 0 - 10000; но это, очевидно, зависит от распределения ваших данных.

Что касается самого расчета: Вы можете рассчитать дисперсию, используя текущий расчет, когда вы читаете свои данные в первый раз (вам не нужно заранее знать среднее значение), используя Метод Уэлфорда :

Инициализировать M1 = x1 и S1 = 0.

Для последующих х используйте повторение формулы

Mk = Mk-1 + (xk - Mk-1) / k Sk = Sk-1 + (xk - Mk-1) * (xk - Mk).

Для 2 ≤ k ≤ n k-я оценка дисперсия s2 = Sk / (k - 1).

2 голосов
/ 12 ноября 2009

Может быть, вы используете другую формулу?

#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

1 голос
/ 14 марта 2017

Пример расчета дисперсии:

#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);
}
0 голосов
/ 12 ноября 2009

Поскольку дисперсия является квадратом стандартного отклонения, ответы на SO 1174984 должны помочь. Краткий диагноз заключается в том, что вам нужно вычислить сумму квадратов значений, а также сумму значений, и вы, похоже, этого не делаете.

Поскольку у вас есть значения 10 6 , а квадрат любого значения может быть до 10 8 , вы можете получить сумму квадратов до 10 14 ; ваши 64-разрядные целые числа могут хранить до 10 18 , поэтому вы все равно можете обрабатывать в десять тысяч раз больше входных данных или значений в диапазоне до одного миллиона вместо всего лишь десяти тысяч, не сталкиваясь с переполнениями. Поэтому нет необходимости переходить к чисто двойным вычислениям.

0 голосов
/ 12 ноября 2009

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

Использование pow .. 2 для вычисления квадрата кажется немного неловким. Вы можете сначала рассчитать свое число, а затем умножить его на единицу, чтобы получить квадрат.

Если вы выполняете деление и чувствуете необходимость приведения, приведите операнды (то есть числитель и / или знаменатель), чтобы удвоить, а не результат. Вы теряете точность, если делите целые числа.

Я не уверен, что ваша формула дисперсии верна. Вы можете посмотреть объяснение в Википедии, например. Но я не специалист по математике, поэтому я не уверен, что вы ошиблись.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...