web-dev-qa-db-ja.com

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

Boost を使用してサンプルを含むベクトルの平均と標準偏差を計算する方法はありますか?

または、アキュムレータを作成し、それにベクトルを供給する必要がありますか?

82
user393144

アキュムレータの使用は、 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;

49
David Nehme

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

UPDATE C++ 11の場合:

std::transformの呼び出しは、std::minusおよびstd::bind2ndの代わりにラムダ関数を使用して記述できます(現在は非推奨)。

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

パフォーマンスが重要であり、コンパイラがラムダをサポートしている場合、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));
61
Josh Greifer

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

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

減算を複数回行うことは、追加の中間ストレージを使用するよりも安価であると思われ、読みやすいと思いますが、パフォーマンスをまだテストしていません。

2
codeling

前述のバージョンの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);
}
1
slyy2048

私の答えはJosh Greiferに似ていますが、サンプル共分散に一般化されています。サンプル分散は単なるサンプル共分散ですが、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);
    }
1
SmallChess

長い間存在していましたが、次のエレガントな再帰的ソリューションは言及されていないようです。 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値のリストの場合、標準偏差の推定値は次のとおりです。

std = variance_n / (n-1). 

お役に立てれば!

0
galactica