Boost를 사용하여 C ++에서 샘플 벡터의 평균 및 표준 편차 계산


답변:


52

축전지를 사용하는 것은 입니다 수단과 표준 편차를 계산하는 방법 부스트 .

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 :: variance는 대략적인 공식으로 분산을 계산합니다. tag :: variance (lazy)는 정확한 공식으로 계산 second moment - squared mean합니다. 특히 반올림 오류로 인해 분산이 매우 작은 경우 잘못된 결과를 생성합니다. 실제로 음의 분산을 생성 할 수 있습니다.
panda-34

많은 숫자가있을 것이라는 것을 알고 있다면 재귀 (온라인) 알고리즘을 사용하십시오. 이것은 언더 및 오버플로 문제를 모두 처리합니다.
Kemin Zhou

217

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 업데이트 :

and std::transform대신 람다 함수를 사용하여에 대한 호출을 작성할 수 있습니다 (현재 사용되지 않음).std::minusstd::bind2nd

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

1
예; 분명히 하단 부분은 mean상단 부분에서 계산 된 값에 따라 달라집니다 .
musiphil 2011

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

65

성능이 중요하고 컴파일러가 람다를 지원하는 경우 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));

1 년 후에도이 답변을 공유해 주셔서 감사합니다. 이제 나는 또 다른 해에 와서 이것을 값 유형과 컨테이너 유형 모두에 대해 제네릭으로 만들었습니다. 여기를 참조하십시오 (참고 : 범위 기반 for 루프가 람다 코드만큼 빠르다고 생각합니다.)
leemes

2
v.end () 대신 std :: end (v) 사용의 차이점은 무엇입니까?
spurra

3
std::end()함수는 .NET과 같은 것이없는 경우 C ++ 11 표준에 의해 추가되었습니다 v.end(). std::end덜 표준 컨테이너에 대한 오버로드 될 수 - 참조 en.cppreference.com/w/cpp/iterator/end
pepr

왜 이것이 더 빠른지 설명 할 수 있습니까?
dev_nut

4
한 가지는 "안전한"대답 (내 대답과 같음)은 배열을 통해 3 회 통과합니다. 한 번은 합계, 한 번은 차이 평균, 한 번은 제곱입니다. 내 코드에는 2 개의 패스 만 있습니다. 두 번째 2 개의 패스를 하나로 병합합니다. 그리고 (마지막으로 보았을 때, 꽤 오래전 지금!) inner_product 호출은 최적화되지 않았습니다. 또한 "안전한"코드는 v를 완전히 새로운 diff 배열로 복사하여 더 많은 지연을 추가합니다. 내 생각에 내 코드가 너무 많은 읽을 수 있습니다 - 쉽게 : 자바 스크립트와 다른 언어로 포팅
조쉬 Greifer

5

musiphil의 답변을 개선 하면 C ++ 11 람다 기능이 diff있는 단일 inner_product호출을 사용하여 임시 벡터없이 표준 편차 함수를 작성할 수 있습니다 .

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

뺄셈을 여러 번 수행하는 것이 추가 중간 저장소를 사용하는 것보다 저렴하다고 생각하고 더 읽기 쉽다고 생각하지만 아직 성능을 테스트하지 않았습니다.


1
나는 이것이 표준 편차가 아니라 분산을 계산하는 것이라고 생각합니다.
sg_man

표준 편차는 N-1이 아닌 N으로 나누어 계산됩니다. sq_sum을 func.size ()-1로 나누는 이유는 무엇입니까?
pocjoc

나는 "수정 된 표준 편차"를 계산하고있는 것 같다 (예 : en.wikipedia.org/wiki/… 참조 )
코딩

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

Cit () 루프를 다음과 같이 작성할 수 for( float f : crVec ) { fSqSum += f * f; fSum += f; } 있습니까?
Elfen Dew

1
예 C ++ 11에서. 버전을 독립적으로 만드는 매크로를 사용하려고합니다. 코드를 업데이트했습니다. 추신. 가독성을 위해 일반적으로 LOC 당 하나의 작업을 선호합니다. 컴파일러는 이것이 일정한 반복임을 확인하고 한 번 반복하는 것이 더 빠르다고 "생각하면"결합해야합니다. 어셈블리 스타일의 일종 인 std :: inner_product ()를 사용하지 않고 작은 단계로 수행하면 새로운 독자에게 그것이 의미하는 바를 설명합니다. 바이너리는 부작용으로 인해 더 작습니다 (일부 경우).
slyy2048

"버전 독립적 인 매크로를 사용하려고 시도"-그러나 비표준 Visual C ++ "for each"구문 ( stackoverflow.com/questions/197375/… )으로 제한합니다
코딩

-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
어디서부터 시작해야할지 모르겠습니다. 목록을 기본 데이터 구조로 사용하고 값을 캐시하지도 않습니다. 이것이 컨테이너와 같은 구조를 사용하는 몇 가지 이유 중 하나입니다. 특히 값이 드물게 발생하고 평균 / 표준 편차가 자주 필요한 경우.
Creat

-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.