使用Boost计算C ++中样本向量的均值和标准差


Answers:


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(lazy)通过精确的公式进行计算,特别是:second moment - squared mean如果由于舍入误差而导致的方差很小,则会产生不正确的结果。它实际上可以产生负方差。
panda-34

如果您知道会有很多数字,请使用递归(在线)算法。这将同时解决欠载和溢出问题。
Kemin Zhou

216

我不知道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可以使用lambda函数而不是std::minusstd::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,2015年

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

5
使用std::inner_product的平方和非常整齐。
Paul R

65

如果性能对您很重要,并且您的编译器支持lambda,则可以使stdev计算更快,更简单:在VS 2012的测试中,我发现以下代码比所选答案中给出的Boost代码快10倍以上。 ; 使用musiphil提供的标准库,它比安全版本的答案快5倍。

请注意,我使用的是标准偏差示例,因此以下代码给出的结果略有不同(为什么标准偏差中为负1

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循环和您的lambda代码一样快。)
leemes

2
使用std :: end(v)代替v.end()有什么区别?
spurra 2014年

3
对于std::end()没有类似的情况,该函数由C ++ 11标准添加v.end()std::end对于不太标准的容器,可以将其过载-参见en.cppreference.com/w/cpp/iterator/end
pepr 2014年

您能解释一下为什么这样更快吗?
dev_nut

4
一方面,“安全”答案(类似于我的答案)使3次遍历数组:一次是求和,一次是diff-mean,一次是平方。在我的代码中,只有两遍-将第二两遍混为一遍。并且(当我上一次看时,现在已经有一段时间了!)inner_product调用并未被优化。另外,“安全”代码将v复制到一个全新的diff数组中,这增加了更多延迟。我认为我的代码也更具可读性-并且很容易移植到JavaScript和其他语言中:)
Josh Greifer 2015年

5

提高对通过musiphil的答案,你可以写一个标准偏差功能,无需临时vector 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/...
codeling

2

尽管已经存在了很长时间,但似乎没有提到以下优雅的递归解决方案。提到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);

那么对于n>=2值列表,标准偏差的估计为:

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

希望这可以帮助!


1

我的答案与Josh Greifer相似,但可以概括为样本协方差。样本方差只是样本协方差,但两个输入相同。这包括贝塞尔的相关性。

    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倍-主要是因为transform()和inner_product()循环已合并。对不起,我的快捷方式/ typedefs /宏:Flo = float。CR常量参考。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 '18

1
是的,在C ++ 11中。尝试使用使其版本独立的宏。更新了代码。PS。为了提高可读性,我通常希望每个LOC采取1个操作。编译器应该看到它们是恒定的迭代,如果“认为”迭代一次更快,则将它们加入。只需很短的步骤即可完成操作(例如,不使用std :: inner_product()),这是一种汇编样式,它向新读者介绍了它的含义。二进制文件的副作用会更小(在某些情况下)。
slyy2048 '18

“试图使用宏,使其独立于版本” -但是你自己限制非标准的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;

}

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.