Calcule la media y la desviación estándar de un vector de muestras en C ++ usando Boost


Respuestas:


52

El uso de acumuladores es la forma de calcular las medias y las desviaciones estándar en 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
Tenga en cuenta que tag :: varianza calcula la varianza mediante una fórmula aproximada. tag :: varianza (lazy) calcula mediante una fórmula exacta, específicamente: second moment - squared meanque producirá un resultado incorrecto si la varianza es muy pequeña debido a errores de redondeo. De hecho, puede producir una variación negativa.
panda-34

Utilice el algoritmo recursivo (en línea) si sabe que va a tener muchos números. Esto se encargará de los problemas de desbordamiento y de desbordamiento.
Kemin Zhou

218

No sé si Boost tiene funciones más específicas, pero puedes hacerlo con la biblioteca estándar.

Dado std::vector<double> v, esta es la forma ingenua:

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

Esto es susceptible de desbordamiento o subdesbordamiento para valores grandes o pequeños. Una forma ligeramente mejor de calcular la desviación estándar es:

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

ACTUALIZACIÓN para C ++ 11:

La llamada a std::transformse puede escribir usando una función lambda en lugar de std::minusy std::bind2nd(ahora en desuso):

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

1
Si; obviamente, la parte inferior depende del valor de meancalculado en la parte superior.
musiphil

7
El primer conjunto de ecuaciones no funciona. Puse int 10 & 2, y obtuve una salida de 4. De un vistazo, creo que es b / c asume que (ab) ^ 2 = a ^ 2-b ^ 2
Charles L.

2
@CharlesL .: Debería funcionar, y 4 es la respuesta correcta.
musiphil

3
@StudentT: No, pero se puede sustituir (v.size() - 1)por v.size()la última línea anterior: std::sqrt(sq_sum / (v.size() - 1)). (Para el primer método, que es un poco complicado: std::sqrt(sq_sum / (v.size() - 1) - mean * mean * v.size() / (v.size() - 1)).
musiphil

6
Usar std::inner_productpara suma de cuadrados es muy bueno.
Paul R

65

Si el rendimiento es importante para usted y su compilador admite lambdas, el cálculo de stdev se puede hacer más rápido y más simple: en las pruebas con VS 2012, descubrí que el siguiente código es más de 10 veces más rápido que el código Boost dado en la respuesta elegida ; también es 5 veces más rápido que la versión más segura de la respuesta usando bibliotecas estándar proporcionadas por musiphil.

Tenga en cuenta que estoy usando una desviación estándar de muestra, por lo que el siguiente código da resultados ligeramente diferentes (por qué hay un menos uno en las desviaciones estándar )

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

Gracias por compartir esta respuesta incluso un año después. Ahora vengo otro año después e hice este genérico tanto para el tipo de valor como para el tipo de contenedor. Vea aquí (Nota: supongo que mi bucle for basado en rango es tan rápido como su código lambda)
Leemes

2
¿Cuál es la diferencia entre usar std :: end (v) en lugar de v.end ()?
spurra

3
La std::end()función fue agregada por el estándar C ++ 11 para casos en los que no hay nada parecido v.end(). Se std::endpuede sobrecargar para el contenedor menos estándar; consulte en.cppreference.com/w/cpp/iterator/end
pepr

¿Puedes explicar por qué es más rápido?
dev_nut

4
Bueno, por un lado, la respuesta "segura" (que es como mi respuesta) hace 3 pases a través de la matriz: una vez para la suma, una vez para la diferencia media y una vez para el cuadrado. En mi código solo hay 2 pases: está fusionando los segundos dos pases en uno. Y (la última vez que miré, ¡hace bastante tiempo!) Las llamadas inner_product no se optimizaron. Además, el código "seguro" copia v en una matriz de diferencias completamente nueva, lo que agrega más demora. En mi opinión, mi código también es más legible, y se puede migrar fácilmente a JavaScript y a otros idiomas :)
Josh Greifer

5

Mejorando la respuesta de musiphil , puede escribir una función de desviación estándar sin el vector temporal diff, simplemente usando una sola inner_productllamada con las capacidades lambda de 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 ));
}

Sospecho que hacer la resta varias veces es más barato que usar almacenamiento intermedio adicional, y creo que es más legible, pero aún no he probado el rendimiento.


1
Creo que esto está calculando la varianza, no la desviación estándar.
sg_man

La desviación estándar se calcula dividiendo por N y no por N-1. ¿Por qué divide sq_sum por func.size () - 1?
pocjoc

Supongo que estoy calculando la "desviación estándar corregida" (ver, por ejemplo, en.wikipedia.org/wiki/… )
codeling

2

Parece que la siguiente solución recursiva elegante no se ha mencionado, aunque ha existido durante mucho tiempo. Refiriéndose al arte de la programación informática de Knuth,

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

luego, para una lista de n>=2valores, la estimación de la desviación estándar es:

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

¡Espero que esto ayude!


1

Mi respuesta es similar a la de Josh Greifer pero generalizada a la covarianza de muestra. La varianza de la muestra es solo la covarianza de la muestra, pero con las dos entradas idénticas. Esto incluye la correlación 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

2 veces más rápido que las versiones mencionadas anteriormente, principalmente porque los bucles transform () e inner_product () están unidos. Lo siento por mi atajo / typedefs / macro: Flo = float. CR const ref. VFlo - vector. Probado en 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);
}

¿Se puede escribir el bucle Cit () como for( float f : crVec ) { fSqSum += f * f; fSum += f; } ?
Elfen Dew

1
Sí en C ++ 11. Intentando usar macros que lo hagan independiente de la versión. Actualizado el código. PD. Para mayor legibilidad, generalmente prefiero 1 acción por LOC. El compilador debería ver que se trata de iteraciones constantes y unirlas si "cree" que es más rápido iterar una vez. Hacerlo en pequeños pasos cortos (sin usar std :: inner_product () eg), una especie de estilo ensamblador, explica al nuevo lector lo que significa. El binario será más pequeño por efecto secundario (en algunos casos).
slyy2048

"Intentando usar macros que lo hacen independiente de la versión", pero te
limitas al

-3

Crea tu propio contenedor:

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

Tiene algunas limitaciones, pero funciona de maravilla cuando sabes lo que estás haciendo.


3
Para responder a la pregunta: porque no hay absolutamente ninguna necesidad. Crear su propio contenedor no tiene absolutamente ningún beneficio en comparación con escribir una función gratuita.
Konrad Rudolph

1
Ni siquiera sé por dónde empezar con esto. Estás usando una lista como estructura de datos subyacente, ni siquiera almacenas en caché los valores, que sería una de las pocas razones por las que puedo pensar para usar una estructura similar a un contenedor. Especialmente si los valores aleatorios con poca frecuencia y la media / stddev se necesitan con frecuencia.
Creat

-7

// significa desviación en c ++

/ Una desviación que es una diferencia entre un valor observado y el valor real de una cantidad de interés (como la media de una población) es un error y una desviación que es la diferencia entre el valor observado y una estimación del valor real (como una estimación puede ser una media muestral) es un residuo. Estos conceptos son aplicables a los datos en los niveles de medición de intervalo y razón. /

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

}

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.