Calculer la moyenne et l'écart type d'un vecteur d'échantillons en C ++ à l'aide de Boost


Réponses:


52

L'utilisation d'accumulateurs est le moyen de calculer les moyennes et les écarts types dans 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
Notez que tag :: variance calcule la variance par une formule approximative. tag :: variance (paresseux) calcule par une formule exacte, spécifiquement: second moment - squared meanqui produira un résultat incorrect si la variance est très petite en raison d'erreurs d'arrondi. Cela peut en fait produire une variance négative.
panda-34

Utilisez l'algorithme récursif (en ligne) si vous savez que vous allez avoir beaucoup de nombres. Cela prendra en charge les problèmes de sous-et de débordement.
Kemin Zhou

216

Je ne sais pas si Boost a des fonctions plus spécifiques, mais vous pouvez le faire avec la bibliothèque standard.

Étant donné std::vector<double> v, c'est la manière naïve:

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

Ceci est susceptible de débordement ou de sous-dépassement pour des valeurs énormes ou minuscules. Une meilleure façon de calculer l'écart type est:

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

MISE À JOUR pour C ++ 11:

L'appel à std::transformpeut être écrit à l'aide d'une fonction lambda au lieu de std::minusand std::bind2nd(désormais obsolète):

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

1
Oui; évidemment, la partie inférieure dépend de la valeur de meancalculée dans la partie supérieure.
musiphil

7
Le premier ensemble d'équations ne fonctionne pas. J'ai mis int 10 & 2, et j'ai obtenu une sortie de 4. En un coup d'œil, je pense que c'est b / c, cela suppose que (ab) ^ 2 = a ^ 2-b ^ 2
Charles L.

2
@CharlesL .: Cela devrait fonctionner, et 4 est la bonne réponse.
musiphil

3
@StudentT: Non, mais vous pouvez substituer (v.size() - 1)à v.size()la dernière ligne ci - dessus: std::sqrt(sq_sum / (v.size() - 1)). (Pour la première méthode, il est un peu compliqué: std::sqrt(sq_sum / (v.size() - 1) - mean * mean * v.size() / (v.size() - 1)).
musiphil

5
L'utilisation std::inner_productde la somme des carrés est très soignée.
Paul R

65

Si les performances sont importantes pour vous et que votre compilateur prend en charge les lambdas, le calcul stdev peut être plus rapide et plus simple: dans les tests avec VS 2012, j'ai trouvé que le code suivant est plus de 10 fois plus rapide que le code Boost donné dans la réponse choisie ; c'est aussi 5 fois plus rapide que la version la plus sûre de la réponse utilisant les bibliothèques standard fournies par musiphil.

Remarque J'utilise un exemple d'écart type, donc le code ci-dessous donne des résultats légèrement différents ( Pourquoi il y a un moins un dans les écarts types )

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

Merci d'avoir partagé cette réponse même un an plus tard. Maintenant, je viens une autre année plus tard et j'ai rendu celui-ci générique à la fois pour le type de valeur et le type de conteneur. Voir ici (Remarque: je suppose que ma boucle for basée sur la plage est aussi rapide que votre code lambda.)
leemes

2
Quelle est la différence entre l'utilisation de std :: end (v) au lieu de v.end ()?
spurra

3
La std::end()fonction a été ajoutée par la norme C ++ 11 pour les cas où il n'y a rien de tel v.end(). Le std::endpeut être surchargé pour le conteneur moins standard - voir en.cppreference.com/w/cpp/iterator/end
pepr

Pouvez-vous expliquer pourquoi est-ce plus rapide?
dev_nut

4
Eh bien pour une chose, la réponse "sûre" (qui est comme ma réponse) fait 3 passages à travers le tableau: une fois pour la somme, une fois pour la différence moyenne et une fois pour la mise au carré. Dans mon code, il n'y a que 2 passes - il confond les deux secondes en une seule. Et (la dernière fois que j'ai regardé, il y a un bon moment maintenant!) Les appels inner_product n'étaient pas optimisés. De plus, le code "sécurisé" copie v dans un tout nouveau tableau de différences, ce qui ajoute plus de délai. À mon avis, mon code est également plus lisible - et est facilement porté en JavaScript et dans d'autres langages :)
Josh Greifer

5

En améliorant la réponse par musiphil , vous pouvez écrire une fonction d'écart type sans le vecteur temporaire diff, en utilisant simplement un seul inner_productappel avec les capacités 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 ));
}

Je soupçonne que faire la soustraction plusieurs fois est moins cher que d'utiliser du stockage intermédiaire supplémentaire, et je pense que c'est plus lisible, mais je n'ai pas encore testé les performances.


1
Je pense qu'il s'agit de calculer la variance, pas l'écart type.
sg_man

L'écart-type est calculé en divisant par N et non par N-1. Pourquoi divisez-vous le sq_sum par func.size () - 1?
pocjoc

Je suppose que je
calcule

2

Il semble que l'élégante solution récursive suivante n'ait pas été mentionnée, bien qu'elle existe depuis longtemps. Se référant à l'art de Knuth de la programmation informatique,

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

alors pour une liste de n>=2valeurs, l'estimation de l'écart type est:

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

J'espère que cela t'aides!


1

Ma réponse est similaire à celle de Josh Greifer mais généralisée à la covariance de l'échantillon. La variance de l'échantillon n'est que la covariance de l'échantillon, mais avec les deux entrées identiques. Cela inclut la corrélation de Bessel.

    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

2x plus rapide que les versions précédentes - principalement parce que les boucles transform () et inner_product () sont jointes. Désolé pour mon raccourci / typedefs / macro: Flo = float. CR const réf. VFlo - vecteur. Testé dans 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);
}

La boucle Cit () peut-elle être écrite comme for( float f : crVec ) { fSqSum += f * f; fSum += f; } ?
Elfen Dew

1
Oui en C ++ 11. Essayer d'utiliser des macros qui le rendent indépendant de la version. Mise à jour du code. PS. Pour la lisibilité, je préfère généralement 1 action par LOC. Le compilateur doit voir que ce sont des itérations constantes et les joindre s'il "pense" qu'il est plus rapide d'itérer une fois. Le faire par petites étapes courtes (sans utiliser std :: inner_product () par exemple), sorte de style d'assemblage, explique au nouveau lecteur ce que cela signifie. Le binaire sera plus petit par effet secondaire (dans certains cas).
slyy2048

"Essayer d'utiliser des macros qui le rendent indépendant de la version" - mais vous vous limitez au Visual C ++ non standard "pour chaque" construction ( stackoverflow.com/questions/197375/… )
codage

-3

Créez votre propre conteneur:

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

Il a certaines limites, mais cela fonctionne à merveille lorsque vous savez ce que vous faites.


3
Pour répondre à la question: parce qu'il n'y en a absolument pas besoin. Créer votre propre conteneur n'a absolument aucun avantage par rapport à l'écriture d'une fonction gratuite.
Konrad Rudolph

1
Je ne sais même pas par où commencer. Vous utilisez une liste comme structure de données sous-jacente, vous ne mettez même pas en cache les valeurs, ce qui serait l'une des rares raisons auxquelles je peux penser pour utiliser une structure de type conteneur. Surtout si les valeurs changent rarement et que la moyenne / stddev est souvent nécessaire.
Cré

-7

// signifie une déviation en c ++

/ Un écart qui est une différence entre une valeur observée et la valeur réelle d'une quantité d'intérêt (telle qu'une moyenne de population) est une erreur et un écart qui est la différence entre la valeur observée et une estimation de la valeur réelle (telle une estimation peut être une moyenne d'échantillon) est un résidu. Ces concepts s'appliquent aux données aux niveaux d'intervalle et de rapport de mesure. /

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

}

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.