Boost kullanarak C ++ 'da örneklerin bir vektöründen ortalama ve standart sapmayı hesaplayın


Yanıtlar:


52

Akümülatörleri kullanma olduğu anlamına gelir ve standart sapmaları hesaplamak için bir yol 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 :: varyce'ın varyansı yaklaşık bir formülle hesapladığını unutmayın. tag :: varyce (lazy), özellikle: second moment - squared meanyuvarlama hataları nedeniyle varyans çok küçükse yanlış sonuç verecek şekilde tam bir formülle hesaplar . Aslında negatif varyans üretebilir.
panda-34

Çok sayıda sayıya sahip olacağınızı biliyorsanız, yinelemeli (çevrimiçi) algoritmayı kullanın. Bu, hem alt hem de taşma sorunlarını çözecektir.
Kemin Zhou

219

Boost'un daha spesifik işlevleri olup olmadığını bilmiyorum, ancak bunu standart kitaplıkla yapabilirsiniz.

Verilen std::vector<double> v, saf yol budur:

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

Bu, çok büyük veya küçük değerler için taşmaya veya yetersizliğe karşı hassastır. Standart sapmayı hesaplamanın biraz daha iyi bir yolu şudur:

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 için GÜNCELLEME :

Çağrı std::transform, std::minusve yerine lambda işlevi kullanılarak yazılabilir std::bind2nd(artık kullanımdan kaldırılmıştır):

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

1
Evet; açıkçası, alt kısım meanüst kısımda hesaplanan değere bağlıdır .
musiphil

7
İlk denklem seti çalışmıyor. İnt 10 & 2 koydum ve 4 çıktısı aldım. Bir bakışta b / c olduğunu varsayarım (ab) ^ 2 = a ^ 2-b ^ 2
Charles L.

2
@CharlesL .: Çalışmalı ve 4 doğru cevap.
musiphil

3
@StudentT: Hayır, ama yerini alabilir (v.size() - 1)için v.size()yukarıdaki son satırında: std::sqrt(sq_sum / (v.size() - 1)). (İlk yöntem için, biraz karışık bir durum: std::sqrt(sq_sum / (v.size() - 1) - mean * mean * v.size() / (v.size() - 1)).
musiphil

6
std::inner_productKareler toplamı için kullanmak çok düzgün.
Paul R

65

Performans sizin için önemliyse ve derleyiciniz lambdaları destekliyorsa, stdev hesaplaması daha hızlı ve daha basit yapılabilir: VS 2012 ile yapılan testlerde, aşağıdaki kodun, seçilen yanıtta verilen Boost kodundan 10 kat daha hızlı olduğunu buldum ; ayrıca musiphil tarafından verilen standart kitaplıkları kullanan daha güvenli yanıt sürümünden 5 kat daha hızlıdır.

Not Örnek standart sapma kullanıyorum, bu nedenle aşağıdaki kod biraz farklı sonuçlar veriyor ( Neden Standart Sapmalarda Eksi Bir var )

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

Bu cevabı bir yıl sonra bile paylaştığınız için teşekkürler. Şimdi bir yıl sonra geliyorum ve bunu hem değer türü hem de konteyner türü için genel yaptım. Buraya bakın (Not: Aralık temelli for
döngüsümün

2
v.end () yerine std :: end (v) kullanmak arasındaki fark nedir?
spurra

3
std::end()Gibi bir şey olduğunda işlev durumlar için C ++ 11 standardına göre ilave edilmiştir v.end(). Daha std::endaz standart konteyner için aşırı yüklenebilir - bkz. En.cppreference.com/w/cpp/iterator/end
pepr

Bunun neden daha hızlı olduğunu açıklayabilir misin?
dev_nut

4
Birincisi, "güvenli" cevap (benim cevabım gibi) dizide 3 geçiş yapar: Bir kez toplam için, bir kez diff-ortalama için ve bir kez de kare için. Benim kodumda sadece 2 geçiş var - İkinci iki geçişi bire birleştiriyor. Ve (son baktığımda, epey bir zaman önce!) İç_ürün çağrıları optimize edilmemişti. Ayrıca "güvenli" kod, v'yi tamamen yeni bir fark dizisine kopyalar, bu da daha fazla gecikme sağlar. Benim düşünceme göre kodum da daha okunabilir - ve kolayca JavaScript ve diğer dillere aktarılabilir :)
Josh Greifer

5

Musiphil'in cevabını geliştirerek , geçici vektör olmadan diff, sadece inner_productC ++ 11 lambda yetenekleriyle tek bir çağrı kullanarak standart bir sapma işlevi yazabilirsiniz :

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

Çıkarma işlemini birden çok kez yapmanın, ek ara depolama kullanmaktan daha ucuz olduğunu düşünüyorum ve daha okunabilir olduğunu düşünüyorum, ancak performansı henüz test etmedim.


1
Bunun standart sapmayı değil, varyansı hesapladığını düşünüyorum.
sg_man

Std sapma, N-1'e değil, N'ye bölünerek hesaplanır. Sq_sum'u neden func.size () - 1'e böldünüz?
pocjoc

Ben "düzeltilmiş standart sapmasını" (bkz örn bilgisayar ediyordum en.wikipedia.org/wiki/... )
codeling

2

Görünüşe göre aşağıdaki zarif özyinelemeli çözüm, uzun zamandır var olmasına rağmen bahsedilmemiştir. Knuth'un Bilgisayar Programlama Sanatına atıfta bulunarak,

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

daha sonra bir n>=2değerler listesi için standart sapmanın tahmini şu şekildedir:

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

Bu yardımcı olur umarım!


1

Cevabım Josh Greifer ile benzer ancak kovaryansı örneklemek için genelleştirilmiştir. Örnek varyansı yalnızca örnek kovaryansıdır, ancak iki giriş aynıdır. Bu, Bessel'in korelasyonunu içerir.

    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

Daha önce bahsedilen sürümlerden 2 kat daha hızlı - çoğunlukla transform () ve inner_product () döngüleri birleştirildiği için. Kısayolum / typedefs / makrom için üzgünüm: Flo = float. CR sabit referansı VFlo - vektör. VS2010'da test edildi

#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 () döngüsü olarak yazılabilir for( float f : crVec ) { fSqSum += f * f; fSum += f; } mi?
Elfen Dew

1
Evet, C ++ 11'de. Onu sürümden bağımsız yapan makrolar kullanmaya çalışıyorum. Kodu güncelledi. PS. Okunabilirlik için genellikle LOC başına 1 eylemi tercih ederim. Derleyici bunların sürekli yinelemeler olduğunu görmeli ve bir kez yinelemenin daha hızlı olduğunu "düşünüyorsa" onlara katılmalıdır. Bunu küçük kısa adımlarla yapmak (örneğin std :: inner_product () kullanmadan), bir tür montaj tarzı, yeni okuyucuya ne anlama geldiğini açıklar. İkili değer, yan etkiye göre daha küçük olacaktır (bazı durumlarda).
slyy2048

"Onu sürümden bağımsız yapan makroları kullanmaya çalışmak" - yine de kendinizi her yapı için standart olmayan Visual C ++ ile sınırlandırıyorsunuz ( stackoverflow.com/questions/197375/… )
kodlama

-3

Kendi kapsayıcınızı oluşturun:

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

Bazı sınırlamaları vardır, ancak ne yaptığınızı bildiğinizde çok güzel çalışır.


3
Soruyu cevaplamak için: çünkü kesinlikle gerek yok. Kendi kabınızı oluşturmanın, ücretsiz bir işlev yazmaya kıyasla kesinlikle hiçbir faydası yoktur.
Konrad Rudolph

1
Bununla nereden başlayacağımı bile bilmiyorum. Temel veri yapısı olarak bir liste kullanıyorsunuz, değerleri önbelleğe bile almıyorsunuz, bu da kap benzeri bir yapı kullanmak için düşünebileceğim birkaç nedenden biri. Özellikle değerler nadiren şansa bağlıysa ve ortalama / stddev sık sık gerekliyse.
Creat

-7

// c ++ 'da sapma anlamına gelir

/ Gözlenen bir değer ile bir ilgi miktarının gerçek değeri arasındaki fark olan bir sapma (popülasyon ortalaması gibi), bir hata ve gözlemlenen değer ile gerçek değerin tahmini arasındaki fark olan bir sapmadır (böyle bir tahmin bir örnek ortalama olabilir) bir kalıntıdır. Bu kavramlar, ölçüm aralığı ve oran seviyelerindeki veriler için geçerlidir. /

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

}

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.