97 votes

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

Y a-t-il un moyen de calculer la moyenne et l'écart-type pour un vecteur contenant des échantillons en utilisant Boost ?

Ou dois-je créer un accumulateur et y faire passer le vecteur ?

0 votes

Pour une solution en une seule passe, consultez stackoverflow.com/questions/7616511/…

234voto

musiphil Points 916

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

Étant donné std::vector v, voici la manière naïve de le faire :

#include 

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 provoquer un dépassement ou un sous-dépassement pour des valeurs énormes ou très petites. Une manière légèrement meilleure de calculer l'écart type est la suivante :

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

std::vector diff(v.size());
std::transform(v.begin(), v.end(), diff.begin(),
               std::bind2nd(std::minus(), 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::transform peut être écrit en utilisant une fonction lambda au lieu de std::minus et std::bind2nd (désormais obsolètes) :

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

0 votes

Le bas de la section de code dépend-il du haut? C'est-à-dire, est-ce que le "mean" dans la deuxième section est le même que le "mean" défini dans la première section?

1 votes

Oui ; évidemment, la partie basse dépend de la valeur de mean calculée dans la partie haute.

8 votes

Le premier ensemble d'équations ne fonctionne pas. J'ai mis int 10 & 2, et j'ai obtenu une sortie de 4. À première vue, je pense que c'est parce qu'il suppose que (a-b)^2 = a^2-b^2

71voto

Josh Greifer Points 1465

Si la performance est importante pour vous et que votre compilateur prend en charge les lambdas, le calcul de l'écart type peut être plus rapide et plus simple: dans des 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 ; il est aussi 5 fois plus rapide que la version plus sûre de la réponse utilisant les bibliothèques standard donnée par musiphil.

Note : j'utilise l'écart type d'échantillon, 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));

1 votes

Merci d'avoir partagé cette réponse même un an plus tard. Maintenant, je reviens un an plus tard et j'ai rendu celle-ci générique pour les types de valeurs et de conteneurs. Voir ici (Remarque : je suppose que ma boucle basée sur une plage est aussi rapide que votre code lambda.)

2 votes

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

3 votes

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

54voto

David Nehme Points 11564

Utiliser des accumulateurs est la façon de calculer les moyennes et les écarts-types dans Boost.

accumulator_set > acc;
for_each(a_vec.begin(), a_vec.end(), bind(ref(acc), _1));

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

7 votes

Notez que la balise variance calcule la variance selon une formule approximative. La balise variance (paresseuse) calcule selon une formule exacte, spécifiquement : deuxième moment - carré de la moyenne, ce qui peut produire un résultat incorrect si la variance est très faible en raison d'erreurs d'arrondi. Il peut en fait produire une variance négative.

0 votes

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

6voto

codeling Points 6542

Amélioration de la réponse de musiphil, vous pouvez écrire une fonction d'écart type sans le vecteur temporaire diff, en utilisant simplement un seul appel inner_product avec les capacités de lambda de C++11 :

double stddev(std::vector 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 soustraire plusieurs fois est moins cher que d'utiliser un stockage intermédiaire supplémentaire, et je pense que c'est plus lisible, mais je n'ai pas encore testé les performances.

Quant à l'explication de pourquoi utiliser N-1 (comme dans func.size() - 1), voir ces questions - notez comment la question stipule que nous avons un "vecteur contenant des échantillons".

1 votes

Je pense que cela calcule la variance, pas l'écart type.

0 votes

La déviation standard est calculée en divisant par N et non par N-1. Pourquoi divisez-vous le sq_sum par func.size()-1?

4voto

galactica Points 184

Il semble que la solution récursive élégante suivante n'a pas été mentionnée, bien qu'elle existe depuis longtemps. En référence à l'Art of Computer Programming de Knuth,

mean_1 = x_1, variance_1 = 0;            //conditions initiales; cas limite;

//pour 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>=2 valeurs, l'estimation de l'écart-type est:

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

J'espère que cela aidera!

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X