Hitung mean dan deviasi standar dari vektor sampel di C ++ menggunakan Boost

Jawaban:

52

Menggunakan akumulator adalah cara untuk menghitung rata-rata dan deviasi standar di 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;

 

David Nehme
sumber
5
Perhatikan, tag :: variance menghitung varians dengan rumus perkiraan. tag :: variance (lazy) menghitung dengan rumus yang tepat, khususnya: second moment - squared meanyang akan menghasilkan hasil yang salah jika varians sangat kecil karena kesalahan pembulatan. Ini sebenarnya dapat menghasilkan varian negatif.
panda-34
Gunakan algoritme rekursif (daring) jika Anda tahu Anda akan memiliki banyak angka. Ini akan menangani masalah underflow dan overflow.
Kemin Zhou
219

Saya tidak tahu apakah Boost memiliki fungsi yang lebih spesifik, tetapi Anda dapat melakukannya dengan pustaka standar.

Diberikan std::vector<double> v, ini adalah cara yang naif:

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

Ini rentan terhadap overflow atau underflow untuk nilai besar atau kecil. Cara yang sedikit lebih baik untuk menghitung deviasi standar adalah:

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 untuk C ++ 11:

Panggilan ke std::transformdapat ditulis menggunakan fungsi lambda sebagai ganti std::minusdan std::bind2nd(sekarang tidak digunakan lagi):

std::transform(v.begin(), v.end(), diff.begin(), [mean](double x) { return x - mean; });
musiphil
sumber
1
Iya; Jelas, bagian bawah bergantung pada nilai meanhitung di bagian atas.
musiphil
7
Kumpulan persamaan pertama tidak berfungsi. Saya meletakkan int 10 & 2, dan mendapatkan output 4. Sekilas saya pikir itu b / c diasumsikan bahwa (ab) ^ 2 = a ^ 2-b ^ 2
Charles L.
2
@CharlesL .: Seharusnya berhasil, dan 4 adalah jawaban yang benar.
musiphil
3
@StudentT: Tidak, tapi Anda dapat menggantikan (v.size() - 1)untuk v.size()di baris terakhir di atas: std::sqrt(sq_sum / (v.size() - 1)). (Untuk metode pertama, itu sedikit rumit: std::sqrt(sq_sum / (v.size() - 1) - mean * mean * v.size() / (v.size() - 1)).
musiphil
6
Penggunaan std::inner_productuntuk jumlah kotak sangat rapi.
Paul R
65

Jika kinerja penting bagi Anda, dan kompilator Anda mendukung lambda, penghitungan stdev dapat dibuat lebih cepat dan sederhana: Dalam pengujian dengan VS 2012, saya telah menemukan bahwa kode berikut lebih dari 10 X lebih cepat daripada kode Peningkatan yang diberikan dalam jawaban yang dipilih ; Ini juga 5 X lebih cepat daripada versi jawaban yang lebih aman menggunakan pustaka standar yang diberikan oleh musiphil.

Catatan Saya menggunakan sampel deviasi standar, jadi kode di bawah ini memberikan hasil yang sedikit berbeda ( Mengapa ada Minus Satu dalam Standar Deviasi )

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));
Josh Greifer
sumber
Terima kasih telah membagikan jawaban ini bahkan setahun kemudian. Sekarang saya datang satu tahun kemudian dan membuat yang satu ini generik untuk tipe nilai dan tipe wadah. Lihat di sini (Catatan: Saya rasa perulangan for berbasis jangkauan saya secepat kode lambda Anda.)
leemes
2
apa perbedaan antara menggunakan std :: end (v) daripada v.end ()?
spurra
3
The std::end()Fungsi ditambahkan oleh C ++ 11 standar untuk kasus-kasus ketika tidak ada yang seperti v.end(). The std::enddapat kelebihan beban untuk kontainer kurang standar - lihat en.cppreference.com/w/cpp/iterator/end
pepr
Bisakah Anda menjelaskan mengapa ini lebih cepat?
dev_nut
4
Nah untuk satu hal, jawaban "aman" (yang seperti jawaban saya) membuat 3 melewati array: Sekali untuk jumlah, sekali untuk diff-mean, dan sekali untuk kuadrat. Dalam kode saya hanya ada 2 lintasan - Ini menggabungkan dua lintasan kedua menjadi satu. Dan (ketika saya terakhir melihat, beberapa waktu yang lalu sekarang!) Panggilan produk_ dalam tidak dioptimalkan. Selain itu, kode "aman" menyalin v ke dalam array diff yang benar-benar baru, yang menambahkan lebih banyak penundaan. Menurut pendapat saya, kode saya juga lebih mudah dibaca - dan mudah di-porting ke JavaScript dan bahasa lain :)
Josh Greifer
5

Memperbaiki jawaban musiphil , Anda dapat menulis fungsi deviasi standar tanpa vektor sementara diff, hanya menggunakan satu inner_productpanggilan dengan kapabilitas lambda 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 ));
}

Saya menduga melakukan pengurangan beberapa kali lebih murah daripada menggunakan penyimpanan perantara tambahan, dan menurut saya ini lebih mudah dibaca, tetapi saya belum menguji kinerjanya.

codeling
sumber
1
Saya pikir ini menghitung varians, bukan deviasi standar.
sg_man
Deviasi std dihitung dengan membagi N dan bukan dengan N-1. Mengapa Anda membagi sq_sum dengan func.size () - 1?
pocjoc
Saya kira saya menghitung "deviasi standar yang dikoreksi" (lihat misalnya en.wikipedia.org/wiki/… )
pengkodean
2

Sepertinya solusi rekursif elegan berikut ini belum pernah disebutkan, meski sudah ada sejak lama. Mengacu pada Seni Pemrograman Komputer 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);

kemudian untuk daftar n>=2nilai, perkiraan standar deviasi adalah:

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

Semoga ini membantu!

galaktika
sumber
1

Jawaban saya mirip dengan Josh Greifer tetapi digeneralisasikan ke kovarian sampel. Varians sampel hanyalah kovarians sampel tetapi dengan dua input identik. Ini termasuk korelasi 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);
    }
SmallChess
sumber
0

2x lebih cepat dari versi yang disebutkan sebelumnya - kebanyakan karena loop transform () dan inner_product () digabungkan. Maaf tentang pintasan / typedefs / makro saya: Flo = float. CR const ref. VFlo - vektor. Diuji di 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);
}
slyy2048
sumber
Bisakah loop Cit () ditulis sebagai for( float f : crVec ) { fSqSum += f * f; fSum += f; } ?
Elfen Dew
1
Ya di C ++ 11. Mencoba menggunakan makro yang membuatnya tidak bergantung pada versi. Memperbarui kode. PS. Untuk keterbacaan, saya biasanya lebih suka 1 tindakan per LOC. Compiler harus melihat bahwa itu adalah iterasi yang konstan dan menggabungkannya jika "berpikir" lebih cepat untuk melakukan iterasi sekali. Melakukannya dalam langkah-langkah kecil yang singkat (tanpa menggunakan std :: inner_product () eg), jenis assembly-style, menjelaskan kepada pembaca baru apa artinya. Biner akan menjadi lebih kecil karena efek sampingnya (dalam beberapa kasus).
slyy2048
"Mencoba menggunakan makro yang menjadikannya versi independen" - namun Anda membatasi diri Anda pada Visual C ++ non-standar "untuk setiap" konstruksi ( stackoverflow.com/questions/197375/… )
kode
-3

Buat wadah Anda sendiri:

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

Itu memang memiliki beberapa batasan, tetapi bekerja dengan baik ketika Anda tahu apa yang Anda lakukan.

Sushant Kondguli
sumber
3
Untuk menjawab pertanyaan: karena sama sekali tidak perlu. Membuat wadah Anda sendiri sama sekali tidak memiliki manfaat dibandingkan dengan menulis fungsi gratis.
Konrad Rudolph
1
Aku bahkan tidak tahu harus mulai dari mana. Anda menggunakan daftar sebagai struktur data yang mendasarinya, Anda bahkan tidak menyimpan nilai-nilainya, yang akan menjadi salah satu dari beberapa alasan saya dapat memikirkan untuk menggunakan struktur seperti wadah. Terutama jika nilainya jarang terjadi dan mean / stddev sering dibutuhkan.
Creat
-7

// Berarti deviasi dalam c ++

/ Penyimpangan yang merupakan perbedaan antara nilai yang diamati dan nilai sebenarnya dari sejumlah kepentingan (seperti rata-rata populasi) adalah kesalahan dan penyimpangan yang merupakan perbedaan antara nilai yang diamati dan perkiraan nilai sebenarnya (seperti perkiraan mungkin mean sampel) adalah sisa. Konsep-konsep ini berlaku untuk data pada tingkat interval dan rasio pengukuran. /

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

}

ali
sumber