성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Modules 창(Ctrl+Shift+U)을 띄워서, 해당 Op...
[정성태] 만드실 수 있습니다. 단지, Unity 엔진 내의 스크립트와 W...
[공진영] 안녕하세요 좋은글 감사합니다. 현재 제가 wpf로 관제 모...
[정성태] The Windows Registry Adventure #1: ...
[정성태] systemd for Developers I ; https:/...
[정성태] 엄밀히 object 타입의 인스턴스가 다른 타입으로 형변환 가능...
[정성태] 아래의 글에서 나오는 "Windows Application Pa...
[정성태] The history of calling conventions,...
[정성태] Secure and Deploy .NET Windows Form...
[정성태] Get Started with Milvus Vector DB i...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>MathNet을 이용한 간단한 통계 정보 처리 - 분산/표준편차</h1> <p> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C# - MathNet.Numerics의 Matrix(행렬) 연산 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11910'>http://www.sysnet.pe.kr/2/0/11910</a> MathNET + OxyPlot을 이용한 간단한 통계 정보 처리 - Histogram ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11916'>http://www.sysnet.pe.kr/2/0/11916</a> </pre> <br /> 이번엔 MathNet의 분산과 표준편차를 위한 메서드를 보겠습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > List<double> dblHeights = LoadData("data.txt"); // dblHeights == 32 27 29 34 33라고 가정 Console.WriteLine($"# of data: {dblHeights.Count}"); // 31 Console.WriteLine($"MathNet - Variance: {Statistics.Variance(dblHeights)}"); // 8.5 Console.WriteLine($"MathNet - Standard Deviation: {Statistics.StandardDeviation(dblHeights)}"); // 2.91547594742265 </pre> <br /> 그런데 값이 좀 이상합니다. 위의 분산값은 8.5라고 나오는데, 실제로 계산해 보면 6.8이기 때문입니다. (분산이 틀리니 표준편차 값도 당연히 틀립니다.) 이유는 간단합니다. Variance와 StandardDeviation 메서드는 통계의 "모집단(population)에 대한 분산/표준편차"가 아니라 "표본(sample)에 대한 분산/표준편차"를 출력해 주는 것이고 표본의 경우 Bessel's correction을 고려한 값을 반환하도록 되어 있습니다.<br /> <br /> 엑셀(Excel)을 해보신 분은 알겠지만 엑셀에서도 다음의 2가지 분산/표준편차 함수가 제공됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > VAR.S 표본에 대한 분산 STDDEV.S 표본에 대한 표준편차 VAR.P 모집단에 대한 분산 STDDEV.P 모집단에 대한 표준편차 * S는 Sample, P는 Population을 의미 </pre> <br /> C# 코드로 분산을 구현하면 이렇게 작성할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public static double Variance(double[] samples, double mean, bool useBesselCorrection) { if (samples.Length <= ((useBesselCorrection == true) ? 1 : 0)) { return double.NaN; } double sum = 0; for (int i = 0; i < samples.Length; i++) { double diff = samples[i] - mean; sum += (diff * diff); } double variance = sum / ((samples.Length - ((useBesselCorrection == true) ? 1 : 0))); return variance; } </pre> <br /> 통계학의 기본을 알지 못하면 어찌 보면 말장난 같기도 합니다. 모집단에 대한 분산을 구할 때는 samples.Length로 나누고, 표본에 대한 분산을 구할 때는 samples.Length - 1을 하게 됩니다. 즉, 동일한 데이터를 samples 배열에 넣어 전달해도 그것이 모집단(전체 집합)의 데이터냐, 부분 샘플에 대한 데이터냐에 따라 결과가 달리 나오는 것입니다. (참고: <a target='tab' href='https://blog.naver.com/dalsapcho/20147545698'>https://blog.naver.com/dalsapcho/20147545698</a>, 개인적으로 이 글에서 "개념 정리"에 나온 그림이 마음에 듭니다. ^^)<br /> <br /> <hr style='width: 50%' /><br /> <br /> 그런데 Math.NET의 분산을 구하는 코드가 재미있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > /* Estimates the unbiased population variance from the provided samples as unsorted array. On a dataset of size N will use an N-1 normalizer (Bessel's correction). Returns NaN if data has less than two entries or if any entry is NaN. */ public static double Variance(double[] samples) { if (samples.Length <= 1) { return double.NaN; } <span style='color: blue; font-weight: bold'>double num = 0.0; double num2 = samples[0]; for (int i = 1; i < samples.Length; i++) { num2 += samples[i]; double num4 = ((i + 1) * samples[i]) - num2; num += (num4 * num4) / ((i + 1.0) * i); }</span> return (num / ((double) (<span style='color: blue; font-weight: bold'>samples.Length - 1</span>))); // <a target='tab' href='https://youtu.be/TckEM-6tdrc'>표본 분산</a>이므로. } </pre> <br /> 제가 만든 C# 분산 코드와 위의 분산을 구하는 코드가 다릅니다. 하지만 (double 연산의 특성으로 소수점 2자리부터 차이가 발생하지만) 결과는 같습니다. 왜 저렇게 어렵게 분산을 구하는 것일까요? 이유가 멋집니다. 제가 작성했던 코드는 2-pass인 반면, Math.NET의 코드는 1-pass입니다. 다시 말해, 제가 작성한 코드는 평균값을 알고 있어야 하는데 그 평균을 구하기 위해 미리 한번 전체 데이터에 대한 루프를 돌아야 하지만, Math.NET의 코드는 평균값을 알지 못해도 분산을 구할 수 있는 것입니다.<br /> <br /> 물론, 평균값을 이미 구했다면 2-pass 코드가 분산을 더 빠르게 구할 수 있습니다. 사실... 통계값을 구한다면 대부분의 경우 평균은 기본적으로 구할 것이므로 현실적으로 효용성이 있느냐는 별개의 문제로 보입니다. ^^<br /> <br /> <hr style='width: 50%' /><br /> <br /> 참고로 Math.NET에서 모집단에 대한 분산/표준편차를 구하려면 Population이 붙은 메서드를 사용하면 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Console.WriteLine($"MathNet - Variance: {<span style='color: blue; font-weight: bold'>Statistics.PopulationVariance</span>(dblHeights)}"); Console.WriteLine($"MathNet - Standard Deviation: {<span style='color: blue; font-weight: bold'>Statistics.PopulationStandardDeviation</span>(dblHeights)}"); </pre> <br /> 또한 구현 코드 역시 Bessel's correction의 차이에 따라 "-1" 교정이 없는 버전의 동일한 코드로 제공됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > /* Evaluates the population variance from the full population provided as unsorted array. On a dataset of size N will use an N normalizer and would thus be biased if applied to a subset. Returns NaN if data is empty or if any entry is NaN. */ public static double PopulationVariance(double[] population) { if (population.Length == 0) { return double.NaN; } double num = 0.0; double num2 = population[0]; for (int i = 1; i < population.Length; i++) { num2 += population[i]; double num4 = ((i + 1) * population[i]) - num2; num += (num4 * num4) / ((i + 1.0) * i); } return <span style='color: blue; font-weight: bold'>(num / ((double) population.Length))</span>; } </pre> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1459&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1385
(왼쪽의 숫자를 입력해야 합니다.)