คำนวณค่าเฉลี่ยและส่วนเบี่ยงเบนมาตรฐานจากเวกเตอร์ของตัวอย่างใน C ++ โดยใช้ Boost


93

มีวิธีคำนวณค่าเฉลี่ยและส่วนเบี่ยงเบนมาตรฐานสำหรับเวกเตอร์ที่มีตัวอย่างโดยใช้Boostหรือไม่?

หรือฉันต้องสร้างตัวสะสมและป้อนเวกเตอร์เข้าไป


ดูวิธีแก้ปัญหาแบบ one pass ได้ที่stackoverflow.com/questions/7616511/…
Gulzar

คำตอบ:


52

ใช้สะสมเป็นวิธีการในการคำนวณวิธีการและค่าเบี่ยงเบนมาตรฐานใน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 :: ความแปรปรวน (ขี้เกียจ) คำนวณโดยใช้สูตรที่แน่นอนโดยเฉพาะsecond moment - squared meanซึ่งจะให้ผลลัพธ์ที่ไม่ถูกต้องหากความแปรปรวนมีค่าน้อยมากเนื่องจากข้อผิดพลาดในการปัดเศษ มันสามารถสร้างความแปรปรวนเชิงลบได้
panda-34

ใช้อัลกอริทึมแบบเรียกซ้ำ (ออนไลน์) หากคุณรู้ว่าคุณกำลังจะมีตัวเลขมากมาย สิ่งนี้จะดูแลปัญหาทั้งน้ำใต้และน้ำล้น
Kemin Zhou

222

ฉันไม่รู้ว่า 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::transformสามารถเขียนได้โดยใช้ฟังก์ชันแลมบ์ดาแทนstd::minusและstd::bind2nd(เลิกใช้แล้วตอนนี้):

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

1
ใช่; เห็นได้ชัดว่าส่วนล่างขึ้นอยู่กับค่าของการmeanคำนวณในส่วนบนสุด
musiphil

7
สมการชุดแรกไม่ทำงาน ฉันใส่ int 10 & 2 และได้ผลลัพธ์เป็น 4 โดยรวมฉันคิดว่ามันเป็น b / c มันอนุมานว่า (ab) ^ 2 = a ^ 2-b ^ 2
Charles L.

2
@CharlesL: ควรใช้งานได้และ 4 คือคำตอบที่ถูกต้อง
musiphil

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

6
การใช้std::inner_productผลรวมของกำลังสองเป็นไปอย่างเรียบร้อย
Paul R

66

หากประสิทธิภาพเป็นสิ่งสำคัญสำหรับคุณและคอมไพเลอร์ของคุณรองรับ lambdas การคำนวณ stdev สามารถทำได้เร็วขึ้นและง่ายขึ้น: ในการทดสอบด้วย VS 2012 ฉันพบว่าโค้ดต่อไปนี้เร็วกว่าโค้ด Boost ที่ระบุไว้ในคำตอบที่เลือกมากกว่า 10 X ; นอกจากนี้ยังเร็วกว่าคำตอบเวอร์ชันที่ปลอดภัยกว่าถึง 5 เท่าโดยใช้ไลบรารีมาตรฐานที่ได้รับจาก musiphil

หมายเหตุฉันใช้ค่าเบี่ยงเบนมาตรฐานตัวอย่างดังนั้นโค้ดด้านล่างจึงให้ผลลัพธ์ที่แตกต่างกันเล็กน้อย ( เหตุใดจึงมีเครื่องหมายลบหนึ่งในค่าเบี่ยงเบนมาตรฐาน )

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

ขอขอบคุณที่แบ่งปันคำตอบนี้แม้ในอีกหนึ่งปีต่อมา ตอนนี้ฉันมาอีกปีต่อมาและทำให้สิ่งนี้เป็นแบบทั่วไปสำหรับทั้งประเภทค่าและประเภทคอนเทนเนอร์ ดูที่นี่ (หมายเหตุ: ฉันเดาว่า range-based for loop ของฉันเร็วพอ ๆ กับรหัสแลมบ์ดาของคุณ)
leemes

2
อะไรคือความแตกต่างระหว่างการใช้ std :: end (v) แทน v.end ()?
spurra

3
std::end()ฟังก์ชั่นได้รับการเพิ่มโดย C ++ 11 v.end()มาตรฐานสำหรับกรณีเมื่อมีอะไรที่ชอบ std::endสามารถจะมากเกินไปสำหรับคอนเทนเนอร์มาตรฐานน้อยกว่า - ดูen.cppreference.com/w/cpp/iterator/end
pepr

คุณช่วยอธิบายได้ไหมว่าทำไมถึงเร็วกว่านี้?
dev_nut

4
ประการหนึ่งคำตอบ "ปลอดภัย" (ซึ่งก็เหมือนกับคำตอบของฉัน) ทำให้ 3 ส่งผ่านอาร์เรย์: ครั้งหนึ่งสำหรับผลรวมหนึ่งครั้งสำหรับค่าเฉลี่ยต่างและอีกครั้งสำหรับกำลังสอง ในรหัสของฉันมีเพียง 2 รอบ - มันรวมสองครั้งที่สองเข้าด้วยกัน และ (เมื่อฉันดูครั้งสุดท้ายเมื่อครู่นี้!) การเรียก inner_product ไม่ได้รับการปรับให้เหมาะสม นอกจากนี้รหัส "ปลอดภัย" ยังคัดลอก v ไปยังอาร์เรย์ของความแตกต่างใหม่ทั้งหมดซึ่งจะเพิ่มความล่าช้ามากขึ้น ในความคิดของฉันรหัสของฉันก็อ่านได้ง่ายขึ้นเช่นกัน - และพอร์ตไปยัง JavaScript และภาษาอื่น ๆ ได้อย่างง่ายดาย :)
Josh Greifer

5

การปรับปรุงคำตอบโดย musiphilคุณสามารถเขียนฟังก์ชันส่วนเบี่ยงเบนมาตรฐานโดยไม่ใช้เวกเตอร์ชั่วคราวdiffเพียงแค่ใช้การinner_productโทรเพียงครั้งเดียวด้วยความสามารถ C ++ 11 lambda:

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
ฉันคิดว่านี่กำลังคำนวณความแปรปรวนไม่ใช่ค่าเบี่ยงเบนมาตรฐาน
sg_man

ค่าเบี่ยงเบนมาตรฐานคำนวณโดยหารด้วย N ไม่ใช่ด้วย N-1 ทำไมคุณแบ่ง sq_sum ด้วย func.size () - 1
pocjoc

2

ดูเหมือนว่าจะไม่มีการกล่าวถึงโซลูชันการเรียกซ้ำที่สวยงามดังต่อไปนี้แม้ว่าจะมีมานานแล้วก็ตาม อ้างถึงศิลปะการเขียนโปรแกรมคอมพิวเตอร์ของ 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ค่าค่าประมาณของส่วนเบี่ยงเบนมาตรฐานคือ:

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

หวังว่านี่จะช่วยได้!


1

คำตอบของฉันคล้ายกับ Josh Greifer แต่เป็นข้อมูลทั่วไปของความแปรปรวนร่วมของตัวอย่าง ความแปรปรวนตัวอย่างเป็นเพียงความแปรปรวนร่วมตัวอย่าง แต่มีอินพุตทั้งสองเหมือนกัน ซึ่งรวมถึงความสัมพันธ์ของ 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

เร็วกว่าเวอร์ชันก่อนหน้านี้ถึง 2 เท่า - ส่วนใหญ่เป็นเพราะลูป transform () และ inner_product () เข้าด้วยกัน ขออภัยเกี่ยวกับทางลัด / typedefs / มาโครของฉัน: Flo = float CR const อ้างอิง 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 () loop สามารถเขียนเป็น for( float f : crVec ) { fSqSum += f * f; fSum += f; } ?
Elfen Dew

1
ใช่ใน C ++ 11 พยายามใช้มาโครที่ทำให้เป็นเวอร์ชันอิสระ อัปเดตรหัส ปล. เพื่อให้อ่านง่ายฉันมักจะชอบ 1 การกระทำต่อ LOC คอมไพเลอร์ควรเห็นว่าสิ่งเหล่านี้เป็นการทำซ้ำอย่างต่อเนื่องและเข้าร่วมถ้ามัน "คิดว่า" มันจะเร็วกว่าที่จะทำซ้ำเพียงครั้งเดียว การทำในขั้นตอนสั้น ๆ สั้น ๆ (โดยไม่ใช้ std :: inner_product () เช่น) ชนิดของรูปแบบการประกอบจะอธิบายให้ผู้อ่านใหม่ทราบว่ามันหมายถึงอะไร ไบนารีจะเล็กลงตามผลข้างเคียง (ในบางกรณี)
slyy2048

"พยายามใช้มาโครที่ทำให้เป็นเวอร์ชันอิสระ" แต่คุณ จำกัด ตัวเองให้ใช้ Visual C ++ ที่ไม่ได้มาตรฐาน "สำหรับแต่ละโครงสร้าง" ( stackoverflow.com/questions/197375/… )
เขียนโค้ด

@codeling เป็นเพียง 1 มาโครสำหรับภาพประกอบสำหรับ C ++ 1 เวอร์ชันสำหรับโพสต์นั้นเท่านั้น คืออัลกอริทึม - ไม่ใช่การเข้ารหัสมาตรฐาน ในตอนนั้นฉันใช้แม้แต่ Cit ที่ไม่เหมาะสม (CFlo, crVec) ซึ่งมีค่าเริ่มต้น const-iter "cit" แต่ระบุประเภทคอนเทนเนอร์อีกครั้ง รายการมาโครเฉพาะของคอมไพเลอร์ / ระบบปฏิบัติการทั้งหมดเป็นสิ่งที่ดีเมื่อมีปัญหาในการพกพา ในตัวอย่างที่มีการเพิ่มการพอร์ตไปยัง std C ++ นั้นไม่ใช่เรื่องง่าย ฉันไม่ได้อธิบาย Flo, VFlo, CR, SZ สั้น ๆ ที่น่าเกลียด -> float, vector <float>, const &, size - สำหรับการย่อบรรทัดการวนซ้ำของ std C ++ สไตล์เดียวกัน Crit (MSZPFlo, crMap) foo (* Crit.second); // rev-iter
slyy2048

-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
ฉันไม่รู้ด้วยซ้ำว่าจะเริ่มจากตรงไหน คุณกำลังใช้รายการเป็นโครงสร้างข้อมูลพื้นฐานคุณไม่ได้แคชค่าด้วยซ้ำซึ่งอาจเป็นหนึ่งในไม่กี่เหตุผลที่ฉันคิดว่าจะใช้โครงสร้างคล้ายคอนเทนเนอร์ โดยเฉพาะอย่างยิ่งถ้าค่ามีโอกาสเกิดขึ้นไม่บ่อยนักและจำเป็นต้องใช้ค่าเฉลี่ย / 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.