Обчисліть середнє та стандартне відхилення від вектора зразків у C ++ за допомогою Boost


91

Чи є спосіб обчислити середнє та стандартне відхилення для вектора, що містить зразки, за допомогою Boost ?

Або мені потрібно створити акумулятор і подати в нього вектор?


Однопрохідне рішення див. Na stackoverflow.com/questions/7616511/…
Gulzar,

Відповіді:


52

Використання акумуляторів - це спосіб обчислення середніх значень та стандартних відхилень у Boost .

accumulator_set<double, stats<tag::variance> > acc;
for_each(a_vec.begin(), a_vec.end(), bind<void>(ref(acc), _1));

cout << mean(acc) << endl;
cout << sqrt(variance(acc)) << endl;

 


5
Зверніть увагу, що tag :: variance обчислює дисперсію за приблизною формулою. tag :: variance (ледачий) обчислює за точною формулою, зокрема: second moment - squared meanце дасть неправильний результат, якщо дисперсія дуже мала через помилки округлення. Це насправді може спричинити негативну дисперсію.
panda-34

Використовуйте рекурсивний (онлайн) алгоритм, якщо знаєте, що у вас буде багато чисел. Це допоможе вирішити як проблеми, так і проблеми з переповненням.
Кемін Чжоу,

217

Не знаю, чи Boost має більш конкретні функції, але це можна зробити за допомогою стандартної бібліотеки.

Враховуючи std::vector<double> v, це наївний спосіб:

#include <numeric>

double sum = std::accumulate(v.begin(), v.end(), 0.0);
double mean = sum / v.size();

double sq_sum = std::inner_product(v.begin(), v.end(), v.begin(), 0.0);
double stdev = std::sqrt(sq_sum / v.size() - mean * mean);

Це сприйнятливо до переповнення або затоплення для величезних або крихітних значень. Трохи кращим способом обчислення стандартного відхилення є:

double sum = std::accumulate(v.begin(), v.end(), 0.0);
double mean = sum / v.size();

std::vector<double> diff(v.size());
std::transform(v.begin(), v.end(), diff.begin(),
               std::bind2nd(std::minus<double>(), mean));
double sq_sum = std::inner_product(diff.begin(), diff.end(), diff.begin(), 0.0);
double stdev = std::sqrt(sq_sum / v.size());

ОНОВЛЕННЯ для C ++ 11:

Виклик до std::transformможна записати, використовуючи лямбда-функцію замість std::minusта std::bind2nd(на даний момент застарілим):

std::transform(v.begin(), v.end(), diff.begin(), [mean](double x) { return x - mean; });

1
Так; очевидно, що нижня частина залежить від значення, meanобчисленого у верхній частині.
musiphil

7
Перший набір рівнянь не працює. Я поставив int 10 & 2 і отримав результат 4. На перший погляд, я думаю, що це b / c, він припускає, що (ab) ^ 2 = a ^ 2-b ^ 2
Charles L.

2
@CharlesL .: Це має спрацювати, і 4 - це правильна відповідь.
musiphil

3
@StudentT: Ні, але ви можете замінити (v.size() - 1)на v.size()в останньому рядку вище: std::sqrt(sq_sum / (v.size() - 1)). (Для першого методу це трохи складно: std::sqrt(sq_sum / (v.size() - 1) - mean * mean * v.size() / (v.size() - 1))...
musiphil

6
Використання std::inner_productдля суми квадратів дуже акуратне.
Paul R

65

Якщо для вас важлива продуктивність, і ваш компілятор підтримує лямбди, обчислення stdev можна зробити швидше і простіше: У тестах з VS 2012 я виявив, що наступний код на 10 разів швидший, ніж код Boost, вказаний у вибраній відповіді ; це також в 5 разів швидше, ніж безпечніша версія відповіді, використовуючи стандартні бібліотеки, надані musiphil.

Примітка. Я використовую зразок стандартного відхилення, тому наведений нижче код дає дещо інші результати ( Чому в стандартних відхиленнях є мінус )

double sum = std::accumulate(std::begin(v), std::end(v), 0.0);
double m =  sum / v.size();

double accum = 0.0;
std::for_each (std::begin(v), std::end(v), [&](const double d) {
    accum += (d - m) * (d - m);
});

double stdev = sqrt(accum / (v.size()-1));

Дякуємо, що поділилися цією відповіддю навіть через рік. Тепер я прийшов ще через рік і зробив цей загальний як для типу значення, так і для типу контейнера. Дивіться тут (Примітка: Я думаю, що мій цикл for, заснований на діапазоні, є таким же швидким, як і ваш лямбда-код.)
leemes

2
у чому різниця між використанням std :: end (v) замість v.end ()?
spurra

3
std::end()Функція була додана в відповідності зі стандартом C ++ 11 для випадків , коли немає нічого подібного v.end(). std::endМоже бути перевантажений для менш стандартного контейнера - см en.cppreference.com/w/cpp/iterator/end
pepr

Чи можете ви пояснити, чому це швидше?
dev_nut

4
Ну, з одного боку, "безпечна" відповідь (яка подібна до моєї відповіді) робить 3 проходження через масив: один раз для суми, один раз для різниці середнього значення і один раз для квадратури. У моєму коді лише 2 проходи - це поєднання двох других проходів в один. І (коли я востаннє дивився, вже досить давно!), Дзвінки inner_product не були оптимізовані. Крім того, "безпечний" код копіює v у абсолютно новий масив відмінностей, що додає більше затримки. На мій погляд, мій код теж є більш читабельним - і його легко перенести на JavaScript та інші мови :)
Джош Грейфер

5

Поліпшення на відповідь по musiphil , ви можете написати стандартну функцію відхилення без тимчасового вектора diff, використовуючи тільки один inner_productвиклик з C ++ 11 можливостей лямбда:

double stddev(std::vector<double> const & func)
{
    double mean = std::accumulate(func.begin(), func.end(), 0.0) / func.size();
    double sq_sum = std::inner_product(func.begin(), func.end(), func.begin(), 0.0,
        [](double const & x, double const & y) { return x + y; },
        [mean](double const & x, double const & y) { return (x - mean)*(y - mean); });
    return std::sqrt(sq_sum / ( func.size() - 1 ));
}

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


1
Я думаю, що це обчислення дисперсії, а не стандартне відхилення.
sg_man

STD відхилення обчислюється діленням на N, а не на N-1. Чому ви ділите sq_sum на func.size () - 1?
pocjoc

Я думаю, я обчислюю "виправлене стандартне відхилення" (див., Наприклад, en.wikipedia.org/wiki/… )
кодування

2

Здається, наступне елегантне рекурсивне рішення не згадувалося, хоча воно існує довгий час. Посилаючись на мистецтво комп’ютерного програмування Кнута,

mean_1 = x_1, variance_1 = 0;            //initial conditions; edge case;

//for k >= 2, 
mean_k     = mean_k-1 + (x_k - mean_k-1) / k;
variance_k = variance_k-1 + (x_k - mean_k-1) * (x_k - mean_k);

тоді для списку n>=2значень оцінка стандартного відхилення становить:

stddev = std::sqrt(variance_n / (n-1)). 

Сподіваюся, це допомагає!


1

Моя відповідь подібна до Джоша Грейфера, але узагальнена для вибіркової коваріації. Дисперсія вибірки - це просто коваріація вибірки, але з однаковими двома входами. Сюди входить кореляція Бесселя.

    template <class Iter> typename Iter::value_type cov(const Iter &x, const Iter &y)
    {
        double sum_x = std::accumulate(std::begin(x), std::end(x), 0.0);
        double sum_y = std::accumulate(std::begin(y), std::end(y), 0.0);

        double mx =  sum_x / x.size();
        double my =  sum_y / y.size();

        double accum = 0.0;

        for (auto i = 0; i < x.size(); i++)
        {
            accum += (x.at(i) - mx) * (y.at(i) - my);
        }

        return accum / (x.size() - 1);
    }

0

Удвічі швидше, ніж у згаданих версіях - здебільшого тому, що цикли transform () та inner_product () об’єднані. Вибачте про мій ярлик / typedefs / macro: Flo = float. CR const ref. VFlo - вектор. Перевірено у VS2010

#define fe(EL, CONTAINER)   for each (auto EL in CONTAINER)  //VS2010
Flo stdDev(VFlo CR crVec) {
    SZ  n = crVec.size();               if (n < 2) return 0.0f;
    Flo fSqSum = 0.0f, fSum = 0.0f;
    fe(f, crVec) fSqSum += f * f;       // EDIT: was Cit(VFlo, crVec) {
    fe(f, crVec) fSum   += f;
    Flo fSumSq      = fSum * fSum;
    Flo fSumSqDivN  = fSumSq / n;
    Flo fSubSqSum   = fSqSum - fSumSqDivN;
    Flo fPreSqrt    = fSubSqSum / (n - 1);
    return sqrt(fPreSqrt);
}

Чи можна цикл Cit () записати як for( float f : crVec ) { fSqSum += f * f; fSum += f; } ?
Elfen Dew

1
Так, в C ++ 11. Намагання використовувати макроси, які роблять його незалежним від версії. Оновлено код. PS. Для читабельності я зазвичай віддаю перевагу 1 дії на LOC. Компілятор повинен побачити, що це постійні ітерації, і приєднатись до них, якщо він "вважає", що швидше повторити один раз. Виконання цього невеликими короткими кроками (без використання std :: inner_product (), наприклад), типу стилю збірки, пояснює новому читачеві, що це означає. Бінарний файл буде меншим побічним ефектом (у деяких випадках).
slyy2048

«Спроба використання макросів , які роблять його версію залежать» - поки ви обмежуєте себе в нестандартному Visual C ++ «для кожного» конструкту ( stackoverflow.com/questions/197375 / ... )
codeling

-3

Створіть власний контейнер:

template <class T>
class statList : public std::list<T>
{
    public:
        statList() : std::list<T>::list() {}
        ~statList() {}
        T mean() {
           return accumulate(begin(),end(),0.0)/size();
        }
        T stddev() {
           T diff_sum = 0;
           T m = mean();
           for(iterator it= begin(); it != end(); ++it)
               diff_sum += ((*it - m)*(*it -m));
           return diff_sum/size();
        }
};

Він має певні обмеження, але прекрасно працює, коли ти знаєш, що робиш.


3
Відповісти на питання: адже в цьому абсолютно немає потреби. Створення власного контейнера абсолютно не має переваг порівняно з написанням безкоштовної функції.
Конрад Рудольф

1
Навіть не знаю, з чого з цього почати. Ви використовуєте список як базову структуру даних, ви навіть не кешуєте значення, що було б однією з небагатьох причин, за якими я можу придумати використовувати структуру, подібну до контейнера. Особливо, якщо значення шансів рідко, а середнє значення / stddev потрібні часто.
Створити

-7

// означає відхилення в c ++

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

#include <iostream>
#include <conio.h>
using namespace std;

/* run this program using the console pauser or add your own getch,     system("pause") or input loop */

int main(int argc, char** argv)
{
int i,cnt;
cout<<"please inter count:\t";
cin>>cnt;
float *num=new float [cnt];
float   *s=new float [cnt];
float sum=0,ave,M,M_D;

for(i=0;i<cnt;i++)
{
    cin>>num[i];
    sum+=num[i];    
}
ave=sum/cnt;
for(i=0;i<cnt;i++)
{
s[i]=ave-num[i];    
if(s[i]<0)
{
s[i]=s[i]*(-1); 
}
cout<<"\n|ave - number| = "<<s[i];  
M+=s[i];    
}
M_D=M/cnt;
cout<<"\n\n Average:             "<<ave;
cout<<"\n M.D(Mean Deviation): "<<M_D;
getch();
return 0;

}

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