Tính toán trung bình và độ lệch chuẩn từ một vectơ mẫu trong C ++ bằng cách sử dụng Boost


Câu trả lời:


52

Sử dụng bộ tích lũy cách để tính toán phương tiện và độ lệch chuẩn trong 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
Lưu ý rằng thẻ :: phương sai tính toán phương sai bằng một công thức gần đúng. tag :: phương sai (lười biếng) tính toán bằng một công thức chính xác, cụ thể: second moment - squared meansẽ tạo ra kết quả không chính xác nếu phương sai rất nhỏ do lỗi làm tròn. Nó thực sự có thể tạo ra phương sai âm.
panda-34

Sử dụng thuật toán đệ quy (trực tuyến) nếu bạn biết mình sắp có nhiều số. Điều này sẽ giải quyết cả vấn đề dưới và tràn.
Kemin Zhou

217

Tôi không biết liệu Boost có các chức năng cụ thể hơn hay không, nhưng bạn có thể làm điều đó với thư viện tiêu chuẩn.

Giả sử std::vector<double> v, đây là cách ngây thơ:

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

Điều này dễ bị tràn hoặc tràn đối với các giá trị lớn hoặc nhỏ. Một cách tốt hơn một chút để tính độ lệch chuẩn là:

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ẬP NHẬT cho C ++ 11:

Lệnh gọi tới std::transformcó thể được viết bằng hàm lambda thay vì std::minusstd::bind2nd(hiện không được dùng nữa):

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

1
Đúng; rõ ràng, phần dưới cùng phụ thuộc vào giá trị được meantính toán ở phần trên cùng.
musiphil

7
Bộ phương trình đầu tiên không hoạt động. Tôi đặt int 10 & 2, và nhận được kết quả là 4. Trong nháy mắt, tôi nghĩ đó là b / c, nó giả định rằng (ab) ^ 2 = a ^ 2-b ^ 2
Charles L.

2
@CharlesL: Nó sẽ hoạt động và 4 là câu trả lời đúng.
musiphil

3
@StudentT: Không, nhưng bạn có thể thay thế (v.size() - 1)cho v.size()trong dòng cuối cùng trên: std::sqrt(sq_sum / (v.size() - 1)). (Đối với phương pháp đầu tiên, đó là một chút phức tạp: std::sqrt(sq_sum / (v.size() - 1) - mean * mean * v.size() / (v.size() - 1)).
musiphil

6
Sử dụng std::inner_productcho tổng các hình vuông là rất gọn gàng.
Paul R

65

Nếu hiệu suất là quan trọng đối với bạn và trình biên dịch của bạn hỗ trợ lambdas, thì việc tính toán stdev có thể được thực hiện nhanh hơn và đơn giản hơn: Trong các thử nghiệm với VS 2012, tôi nhận thấy rằng mã sau nhanh hơn 10 X so với mã Boost được đưa ra trong câu trả lời đã chọn ; nó cũng nhanh hơn 5 X so với phiên bản an toàn hơn của câu trả lời bằng cách sử dụng các thư viện tiêu chuẩn do musiphil cung cấp.

Lưu ý rằng tôi đang sử dụng độ lệch chuẩn mẫu, vì vậy đoạn mã dưới đây cho kết quả hơi khác ( Tại sao lại có điểm trừ một trong độ lệch chuẩn )

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

Cảm ơn vì đã chia sẻ câu trả lời này ngay cả một năm sau đó. Bây giờ tôi đến một năm sau và làm cho cái này chung cho cả loại giá trị và loại vùng chứa. Xem tại đây (Lưu ý: Tôi đoán rằng vòng lặp for dựa trên phạm vi của tôi cũng nhanh như mã lambda của bạn.)
leemes

2
sự khác biệt giữa việc sử dụng std :: end (v) thay vì v.end () là gì?
spurra

3
Các std::end()chức năng được bổ sung theo tiêu chuẩn 11 C ++ đối với trường hợp khi không có gì giống như là v.end(). Các std::endthể bị quá tải cho container ít tiêu chuẩn - xem en.cppreference.com/w/cpp/iterator/end
PEPR

Bạn có thể giải thích tại sao điều này nhanh hơn?
dev_nut

4
Vâng, một điều, câu trả lời "an toàn" (giống như câu trả lời của tôi) tạo ra 3 lần đi qua mảng: Một lần cho tổng, một lần cho giá trị trung bình khác và một lần cho bình phương. Trong mã của tôi chỉ có 2 đường chuyền - Nó ghép hai đường chuyền thứ hai thành một. Và (khi tôi xem lần cuối, cách đây khá lâu!), Các lệnh gọi inner_product không được tối ưu hóa. Ngoài ra, mã "an toàn" sao chép v vào một mảng khác biệt hoàn toàn mới, làm tăng thêm độ trễ. Theo tôi mã của tôi là dễ đọc hơn nữa - và có thể dễ dàng chuyển sang JavaScript và ngôn ngữ khác :)
Josh Greifer

5

Cải thiện câu trả lời bằng musiphil , bạn có thể viết một hàm độ lệch chuẩn mà không cần vectơ tạm thời diff, chỉ cần sử dụng một inner_productlệnh gọi với các khả năng lambda của 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 ));
}

Tôi nghi ngờ việc thực hiện phép trừ nhiều lần sẽ rẻ hơn so với việc sử dụng thêm bộ nhớ trung gian và tôi nghĩ nó dễ đọc hơn, nhưng tôi chưa kiểm tra hiệu suất.


1
Tôi nghĩ đây là tính toán phương sai, không phải độ lệch chuẩn.
sg_man

Độ lệch std được tính chia cho N và không chia cho N-1. Tại sao bạn chia sq_sum cho func.size () - 1?
pocjoc

Tôi đoán tôi đang tính toán "độ lệch chuẩn đã sửa" (xem ví dụ: en.wikipedia.org/wiki/… )
viết mã

2

Có vẻ như giải pháp đệ quy thanh lịch sau đây đã không được đề cập đến, mặc dù nó đã có từ lâu. Đề cập đến Nghệ thuật lập trình máy tính của 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);

thì đối với danh sách các n>=2giá trị, ước tính của độ lệch chuẩn là:

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

Hi vọng điêu nay co ich!


1

Câu trả lời của tôi tương tự như Josh Greifer nhưng được tổng quát hóa thành hiệp phương sai mẫu. Phương sai mẫu chỉ là hiệp phương sai mẫu nhưng với hai đầu vào giống hệt nhau. Điều này bao gồm mối tương quan của 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);
    }

0

Nhanh hơn gấp 2 lần so với các phiên bản được đề cập trước đây - chủ yếu là do các vòng lặp biến đổi () và nội_sản_phẩm () được kết hợp với nhau. Xin lỗi về phím tắt / typedefs / macro: Flo = float của tôi. CR const ref. VFlo - vectơ. Đã thử nghiệm trong 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);
}

Vòng lặp Cit () có thể được viết dưới dạng for( float f : crVec ) { fSqSum += f * f; fSum += f; } không?
Elfen Dew

1
Có trong C ++ 11. Cố gắng sử dụng các macro làm cho phiên bản độc lập. Đã cập nhật mã. Tái bút. Để dễ đọc, tôi thường thích 1 hành động trên mỗi LOC. Trình biên dịch sẽ thấy rằng đó là những lần lặp lại liên tục và nối chúng nếu nó "nghĩ rằng" lặp lại một lần sẽ nhanh hơn. Thực hiện theo các bước ngắn nhỏ (không sử dụng std :: inner_product () vd), kiểu hợp ngữ, giải thích cho người đọc mới biết ý nghĩa của nó. Số nhị phân sẽ nhỏ hơn do tác dụng phụ (trong một số trường hợp).
slyy2048

"Đang cố gắng sử dụng các macro làm cho phiên bản độc lập" - nhưng bạn tự giới hạn mình ở cấu trúc Visual C ++ không chuẩn "cho mỗi" ( stackoverflow.com/questions/197375/… )
viết mã

-3

Tạo vùng chứa của riêng bạn:

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

Nó có một số hạn chế, nhưng nó hoạt động tốt khi bạn biết mình đang làm gì.


3
Để trả lời câu hỏi: bởi vì hoàn toàn không cần. Tạo vùng chứa của riêng bạn hoàn toàn không có lợi ích gì so với việc viết một hàm miễn phí.
Konrad Rudolph

1
Tôi thậm chí không biết bắt đầu từ đâu. Bạn đang sử dụng một danh sách làm cấu trúc dữ liệu cơ bản, bạn thậm chí không lưu vào bộ nhớ cache các giá trị, đó sẽ là một trong số ít lý do tôi có thể nghĩ đến để sử dụng cấu trúc giống như vùng chứa. Đặc biệt nếu các giá trị cơ hội không thường xuyên và giá trị trung bình / stddev là cần thiết thường xuyên.
Tạo

-7

// có nghĩa là độ lệch trong c ++

/ Độ lệch là sự khác biệt giữa giá trị quan sát và giá trị thực của một đại lượng quan tâm (chẳng hạn như giá trị trung bình tổng thể) là một sai số và độ lệch là sự khác biệt giữa giá trị quan sát được và ước tính của giá trị thực (chẳng hạn một ước tính có thể là trung bình mẫu) là phần dư. Các khái niệm này có thể áp dụng cho dữ liệu ở các mức đo khoảng cách và tỷ lệ. /

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

}

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.