2014년 1월 6일 월요일

평균, 분산, 표준편차, 추세선 구현

프로젝트를 할 때마다 통계에 대한 필요성을 크게 느끼곤 하였습니다.
그래서 가장 기본적인 통계에 관한 모듈 부터 정리하고자 합니다.

데이터를 대표하는 값을 구성할 때 대부분 중앙값이나, 평균을 이용하게 되는 것 같습니다.
경우에 따라 이동평균이나, 최대값, 최소값 등을 이용 할 수도 있어서 그에 해당하는 소스를 구현해 보았습니다.


// 최소값, 중앙값, 최대값
public static List<Double> getMinMedianMaxValue(List<? extends Number> dataList) {
List<Double> result = null;
if ( dataList == null ) {
return result;
}
int size = dataList.size();
if ( size < 1 ) {
return result;
}
List<Double> dList = new ArrayList<Double>();
for ( Number numObj : dataList ) {
if ( numObj == null ) {
continue;
}
double dValue = numObj.doubleValue();
if ( Double.isNaN(dValue) || Double.isInfinite(dValue) ) {
continue;
}
dList.add(dValue);
}

size = dList.size();
if ( size == 0 ) {
return result;
}
Collections.sort(dList);
result = new ArrayList<Double>();
result.add(dList.get(0));
double medValue = Double.NaN;
if ( size%2 == 1 ) {
medValue = dList.get((size-1)/2);
} else {
medValue = (dList.get((size/2)-1) + dList.get(size/2))/2.0;
}
result.add(medValue);
result.add(dList.get(size-1));
return result;
}

public static double getAverageValue(List<? extends Number> dataList) {
double result = Double.NaN;
if ( dataList == null ) {
return result;
}
int size = dataList.size();
int availSize = 0;
if ( size < 1 ) {
return result;
}
double sumValue = 0.0;
for ( Number numObj : dataList ) {
if ( numObj == null ) {
continue;
}
double dValue = numObj.doubleValue();
if ( Double.isNaN(dValue) || Double.isInfinite(dValue) ) {
continue;
}
sumValue += dValue;
availSize++;
}
if ( availSize == 0 ) {
return result;
}
result = sumValue/availSize;
return result;
}

public static List<Double> getMovingAverageValues(List<? extends Number> dataList, int period) {
List<Double> result = null;

if ( dataList == null || period < 1 ) {
return result;
}
int size = dataList.size();
if ( size < period ) {
return result;
}

result = new ArrayList<Double>();
for ( int i = period; i <= size; i++ ) {
double sumValue = 0.0;
int availSize = 0;
for ( int j = (i-period); j < i; j++ ) {
Number numObj = dataList.get(j);
if ( numObj == null ) {
continue;
}
double dValue = numObj.doubleValue();
if ( Double.isNaN(dValue) || Double.isInfinite(dValue) ) {
continue;
}
sumValue += dValue;
availSize++;
}
if ( availSize == 0 ) {
result.add(Double.NaN);
} else {
result.add(sumValue/availSize);
}
}
return result;
}

중앙값은 정렬이 우선되어야 하기 때문에 Collections.sort 메소드를 이용했습니다.
다른 정렬 방식을 구현해서 사용하는것도 고려해 볼  부분입니다.
일반적으로 x,y 축을 기준으로 소팅하게 되면 하나가 다른 하나에도 영향을 미치기 되기 때문에 데이터를 분석할 때는 매트릭스 전체에서 소팅하는 것을  고려해 볼 수 있습니다.

이동 평균은 평균과 유사하지만 기간을 중심으로 반복해서 평균을 구하는 부분만이 차이가 있습니다.

다음은 분산과 편차 입니다.
데이터가 얼마나 평균에서 떨어져 있는지를 확인하게 해주는 값 들입니다.
정규분포 곡선을 기준으로 표본으로 부터 모집단을 추정할 수 있게 해주는 기법을 사용할 때 필수적인 항목 들입니다.


public static List<Double> getVarianceStdValues(List<? extends Number> dataList) {
List<Double> result = null;
double avgValue = getAverageValue(dataList);
if ( Double.isNaN(avgValue) ) {
return result;
}

double sumValue = 0.0;
int size = dataList.size();
int availSize = 0;

result = new ArrayList<Double>();
for ( Number numObj : dataList ) {
if ( numObj == null ) {
continue;
}
double dValue = numObj.doubleValue();
if ( Double.isNaN(dValue) || Double.isInfinite(dValue) ) {
continue;
}
sumValue += Math.pow((dValue-avgValue),2);
availSize++;
}

if ( availSize <= 1 ) {
result.add(Double.NaN);
result.add(Double.NaN);
result.add(Double.NaN);
result.add(Double.NaN);
result.add(Double.NaN);
} else {
result.add(sumValue/availSize);
result.add(sumValue/(availSize-1.0));
result.add(Math.sqrt(result.get(0)));
result.add(Math.sqrt(result.get(1)));
result.add(availSize*1.0);
}
return result;
}

모분산, 표본분산, 모표준편차, 표본표준편차를 계산해서 반환하고 있습니다.
평균과 표준편차를 이용해서 신뢰구간을 추정 할 수 있기 때문에 빈번히 사용될 수 있는 항목들입니다.

다음은 추세선입니다.  
선형관계에서 사용할 수 있는 최소제곱법을 기반으로 구성해 보았습니다.

public static List<Double> getLeastSquaresValues(List<? extends Number> xList, List<? extends Number> yList) {
List<Double> result = null;
if ( xList == null || yList == null || xList.size() == 0 || xList.size() != yList.size() ) {
return result;
}
List<Double> xxList = new ArrayList<Double>();
List<Double> yyList = new ArrayList<Double>();
int size = xList.size();

double xSum = 0.0;
double ySum = 0.0;

double xAvg = 0.0;
double yAvg = 0.0;
result = new ArrayList<Double>();
for ( int i = 0; i < size; i++ ) {
Number xNum = xList.get(i);
Number yNum = yList.get(i);
if ( xNum == null || yNum == null ) {
continue;
}

double xv = xNum.doubleValue();
double yv = yNum.doubleValue();
if ( Double.isNaN(xv) || Double.isNaN(yv) || Double.isInfinite(xv) || Double.isInfinite(yv) ) {
continue;
}
xxList.add(xv);
yyList.add(yv);
xSum += xv;
ySum += yv;
}
size = xxList.size();
if ( size == 0 ) {
return result;
}
xAvg = xSum/size;
yAvg = ySum/size;

double xaSum = 0.0;
double yxSum = 0.0;

for ( int i = 0; i < size; i++ ) {
xaSum += Math.pow((xxList.get(i)-xAvg),2);
yxSum += (xxList.get(i)-xAvg)*(yyList.get(i)-yAvg);
}

if ( xaSum == 0.0 ) {
result.add(0.0);
result.add(0.0);
return result;

double a = yxSum/xaSum;
double b = yAvg-(a*xAvg);
result.add(a);
result.add(b);
return result;
}

데이터가 선형으로 관계를 추정할 수 있을 때 사용할 수 있는 방법입니다.
행렬과 역행렬을 찾는 방식으로(가우스 조던(요르단) 소거법)을 이용해서 찾을 수도 있겠으나 일단 이곳에서는 위의 방식으로 구성해 보았습니다.

데이터가 로그일 경우 먼저 값을 로그로 치환한 후 선형으로 추세선을 구하면 로그 함수에 대한 추세선도 구할 수 있습니다.( 수식은 다시 원래의 값으로 환원 시켜 주어야 합니다.)

상관관계, 다항식등의 관계는 추후 구현해 보겠습니다.
 











댓글 없음:

댓글 쓰기