Boostを使用してC ++でサンプルのベクトルから平均と標準偏差を計算する


回答:


52

アキュムレータを使用することであるに平均と標準偏差を計算する方法ブースト

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::transformstd::minusstd::bind2nd以下の代わりにラムダ関数を使用して記述できます(現在は非推奨):

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

1
はい; 明らかに、下部meanは上部で計算された値に依存します。
ムシフィル

7
方程式の最初のセットは機能しません。私はint 10と2を入れて、4の出力を取得しました。一見すると、(ab)^ 2 = a ^ 2-b ^ 2と仮定したb / cだと思います
Charles L.

2
@CharlesL .:動作するはずです。4が正解です。
ムシフィル

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二乗和に使用するのは非常に便利です。
ポールR

65

パフォーマンスが重要であり、コンパイラがラムダをサポートしている場合は、stdev計算をより速く簡単にすることができます。VS2012でのテストでは、次のコードは、選択した回答で指定された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));

この回答を1年後も共有していただきありがとうございます。今、私はもう1年後に来て、これを値の型とコンテナーの型の両方にジェネリックにしました。ここを参照してください(注:私の範囲ベースのforループはラムダコードと同じくらい速いと思います。)
leemes

2
v.end()の代わりにstd :: end(v)を使用することの違いは何ですか?
シュプレーラ2014年

3
このようなstd::end()関数は、C ++ 11標準によって、のようなものがない場合に追加されましたv.end()std::end参照-レス標準コンテナのために、オーバーロードすることができますen.cppreference.com/w/cpp/iterator/end
PEPrと

なぜこれが速いのか説明できますか?
dev_nut 2015

4
1つには、「安全な」答え(私の答えのようなもの)が配列を3回通過します。1回は合計、1回は差分平均、2回は平方です。私のコードでは2つのパスしかありません-2番目の2つのパスを1つに統合しています。そして、(私が最後に見たとき、かなり前に今!)inner_product呼び出しは最適化されませんでした。さらに、「安全な」コードは、vをまったく新しいdiffの配列にコピーします。私の意見では、私のコードも読みやすく、JavaScriptや他の言語に簡単に移植できます:)
Josh Greifer

5

musiphilによる答えを改善し、C ++ 11ラムダ機能でdiff単一のinner_product呼び出しを使用するだけで、一時ベクトルなしで標準偏差関数を記述できます。

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

標準偏差は、N-1ではなくNで割って計算されます。sq_sumをfunc.size()-1で除算するのはなぜですか?
pocjoc

私は「修正された標準偏差」を計算していると思います(たとえば、en.wikipedia.org
wiki /…を

2

次のエレガントな再帰ソリューションは、長い間使用されてきましたが、言及されていないようです。KnuthのArt of Computer Programmingを参照すると、

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

私の答えはジョシュグライファーと似ていますが、共分散のサンプルに一般化されています。サンプル分散はサンプルの共分散ですが、2つの入力は同じです。これにはベッセルの相関が含まれます。

    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 const ref。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

1
C ++ 11では可能です。バージョンに依存しないマクロを使用しようとしています。コードを更新しました。PS。読みやすさのために、私は通常、LOCごとに1つのアクションを好みます。コンパイラーは、それらが一定の反復であることを確認し、1回反復する方が高速であると「考える」場合、それらを結合します。アセンブリスタイルのような小さな短いステップで(std :: inner_product()を使用せずに)実行すると、その意味が新しい読者に説明されます。バイナリは、副作用によって小さくなります(場合によっては)。
slyy2048 2018

「バージョンに依存しないようにするマクロを使用しようとしています」-ただし、「for each」コンストラクト(stackoverflow.com/questions/197375/…)を非標準のVisual C ++に制限する
コーディング

-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
質問に答えるには:まったく必要がないからです。独自のコンテナを作成することには、無料の関数を作成することと比較してまったくメリットがありません。
Konrad Rudolph

1
私はこれからどこから始めればよいのかもわかりません。基になるデータ構造としてリストを使用している場合、値をキャッシュすることすらありません。これは、コンテナーのような構造を使用すると考えられるいくつかの理由の1つです。特に、値がたまにしか発生せず、mean / 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;

}

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.