Oblicz średnią i odchylenie standardowe z wektora próbek w C ++ przy użyciu Boost

Odpowiedzi:

52

Korzystanie z akumulatorów jest sposobem obliczania średnich i odchyleń standardowych w trybie 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;

 

David Nehme
źródło
5
Zauważ, że tag :: variance oblicza wariancję według przybliżonego wzoru. tag :: variance (lazy) oblicza według dokładnej formuły, a konkretnie: second moment - squared meanco da niepoprawny wynik, jeśli wariancja jest bardzo mała z powodu błędów zaokrąglenia. W rzeczywistości może powodować ujemną wariancję.
panda-34,
Użyj algorytmu rekurencyjnego (online), jeśli wiesz, że będziesz mieć dużo liczb. Pozwoli to rozwiązać problemy z niedopełnieniem i przepełnieniem.
Kemin Zhou
217

Nie wiem, czy Boost ma bardziej specyficzne funkcje, ale możesz to zrobić za pomocą standardowej biblioteki.

Biorąc pod uwagę std::vector<double> v, jest to naiwny sposób:

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

Jest to podatne na przepełnienie lub niedomiar dla dużych lub małych wartości. Nieco lepszym sposobem obliczenia odchylenia standardowego jest:

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

UPDATE dla C ++ 11:

Wywołanie do std::transformmożna zapisać za pomocą funkcji lambda zamiast std::minusi std::bind2nd(obecnie przestarzałe):

std::transform(v.begin(), v.end(), diff.begin(), [mean](double x) { return x - mean; });
musiphil
źródło
1
Tak; oczywiście dolna część zależy od wartości meanobliczonej w górnej części.
musiphil
7
Pierwszy zestaw równań nie działa. Wstawiłem int 10 i 2 i otrzymałem wynik 4. Na pierwszy rzut oka wydaje mi się, że to b / c zakłada, że ​​(ab) ^ 2 = a ^ 2-b ^ 2
Charles L.
2
@CharlesL .: Powinno działać, a 4 to poprawna odpowiedź.
musiphil
3
@StudentT: Nie, ale można zastąpić (v.size() - 1)dla v.size()w ostatnim wierszu powyżej: std::sqrt(sq_sum / (v.size() - 1)). (W przypadku pierwszej metody, to jest trochę skomplikowane: std::sqrt(sq_sum / (v.size() - 1) - mean * mean * v.size() / (v.size() - 1)).
musiphil
6
Używanie std::inner_productdo sumy kwadratów jest bardzo dokładne.
Paul R
65

Jeśli wydajność jest dla Ciebie ważna, a Twój kompilator obsługuje lambdy, obliczenie wartości odchylenia standardowego może być szybsze i prostsze: W testach z VS 2012 stwierdziłem, że poniższy kod jest ponad 10 razy szybszy niż kod Boost podany w wybranej odpowiedzi ; jest również 5 razy szybsza niż bezpieczniejsza wersja odpowiedzi przy użyciu standardowych bibliotek dostarczonych przez musiphil.

Uwaga Używam przykładowego odchylenia standardowego, więc poniższy kod daje nieco inne wyniki ( Dlaczego w odchyleniach standardowych występuje minus jeden )

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));
Josh Greifer
źródło
Dzięki za podzielenie się tą odpowiedzią nawet rok później. Teraz przyszedłem kolejny rok później i zrobiłem ten ogólny zarówno dla typu wartości, jak i typu kontenera. Zobacz tutaj (uwaga: wydaje mi się, że moja pętla for oparta na zakresie jest tak szybka, jak twój kod lambda).
leemes
2
jaka jest różnica między używaniem std :: end (v) zamiast v.end ()?
spurra
3
std::end()Funkcja została dodana przez standard C ++ 11 w przypadkach, gdy nie ma nic podobnego v.end(). std::endMoże być przeładowany do mniej standardowym kontenerze - patrz en.cppreference.com/w/cpp/iterator/end
PEPR
Czy możesz wyjaśnić, dlaczego jest to szybsze?
dev_nut
4
Cóż, po pierwsze, "bezpieczna" odpowiedź (która jest podobna do mojej odpowiedzi) powoduje przejście przez tablicę 3: Raz dla sumy, raz dla średniej różnicy i raz dla podniesienia do kwadratu. W moim kodzie są tylko 2 przebiegi - to połączenie dwóch drugich w jeden. I (kiedy ostatnio patrzyłem, całkiem dawno temu!) Wywołania inner_product nie zostały zoptymalizowane. Dodatkowo „bezpieczny” kod kopiuje v do zupełnie nowej tablicy różnic, co dodaje więcej opóźnienia. Moim zdaniem mój kod też jest bardziej czytelny - i łatwo go przeportować do JavaScript i innych języków :)
Josh Greifer
5

Poprawiając odpowiedź przez musiphila , możesz napisać funkcję odchylenia standardowego bez wektora tymczasowego diff, używając tylko jednego inner_productwywołania z możliwościami lambda 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 ));
}

Podejrzewam, że wielokrotne odejmowanie jest tańsze niż zużywanie dodatkowej pamięci pośredniej i myślę, że jest bardziej czytelne, ale jeszcze nie testowałem wydajności.

kodowanie
źródło
1
Myślę, że to jest obliczanie wariancji, a nie odchylenia standardowego.
sg_man,
Odchylenie standardowe oblicza się dzieląc przez N, a nie przez N-1. Dlaczego dzielisz sumę_sq przez func.size () - 1?
pocjoc
Chyba jestem obliczania odchylenia standardowego „skorygowany” (patrz np en.wikipedia.org/wiki/... )
codeling
2

Wydaje się, że nie wspomniano o następującym eleganckim rozwiązaniu rekurencyjnym, chociaż istnieje ono od dawna. Nawiązując do sztuki programowania komputerowego Knutha,

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

następnie dla listy n>=2wartości oszacowanie odchylenia standardowego wynosi:

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

Mam nadzieję że to pomoże!

galactica
źródło
1

Moja odpowiedź jest podobna do Josha Greifera, ale uogólniona na próbkę kowariancji. Wariancja próbki to po prostu kowariancja próbki, ale z dwoma identycznymi danymi wejściowymi. Obejmuje to korelację Bessela.

    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);
    }
SmallChess
źródło
0

2x szybsze niż poprzednie wersje - głównie dlatego, że pętle transform () i inner_product () są połączone. Przepraszam za mój skrót / typedefs / makro: Flo = float. CR stała ref. VFlo - wektor. Przetestowano w 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);
}
slyy2048
źródło
Czy pętlę Cit () można zapisać jako for( float f : crVec ) { fSqSum += f * f; fSum += f; } ?
Elfen Dew
1
Tak w C ++ 11. Próba użycia makr, które uniezależniają wersję. Zaktualizowano kod. PS. Ze względu na czytelność zazwyczaj preferuję 1 akcję na LOC. Kompilator powinien zobaczyć, że są to ciągłe iteracje i dołączyć do nich, jeśli „myśli”, że jednorazowe powtórzenie jest szybsze. Robiąc to w małych, krótkich krokach (bez użycia np. Std :: inner_product ()), rodzaj stylu assemblera, wyjaśnia nowemu czytelnikowi, co to znaczy. Binarny będzie mniejszy z powodu efektu ubocznego (w niektórych przypadkach).
slyy2048
„Próba użycia makr, które sprawiają, że jest niezależna od wersji” - jednak ograniczasz się do niestandardowej konstrukcji Visual C ++ „dla każdej” ( stackoverflow.com/questions/197375/… )
kodowanie
-3

Stwórz swój własny kontener:

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();
        }
};

Ma pewne ograniczenia, ale działa pięknie, gdy wiesz, co robisz.

Sushant Kondguli
źródło
3
Odpowiadając na pytanie: ponieważ absolutnie nie ma takiej potrzeby. Utworzenie własnego kontenera nie ma absolutnie żadnych korzyści w porównaniu z napisaniem bezpłatnej funkcji.
Konrad Rudolph
1
Nie wiem nawet, od czego zacząć. Używasz listy jako podstawowej struktury danych, nawet nie buforujesz wartości, co byłby jednym z niewielu powodów, dla których przychodzi mi do głowy użycie struktury podobnej do kontenera. Zwłaszcza jeśli wartości przypadkowe są rzadkie, a średnia / odchylenie standardowe są często potrzebne.
Creat
-7

// oznacza odchylenie w C ++

/ Odchylenie, które jest różnicą między wartością obserwowaną a prawdziwą wartością wielkości będącej przedmiotem zainteresowania (np. oszacowanie może być średnią z próby) jest wartością resztkową. Pojęcia te mają zastosowanie do danych w przedziałach i poziomach współczynnika pomiaru. /

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

}

ali
źródło