yunsh206

회원가입로그인

들어가기 전에 – 여기서 쓰인 SQL 은 쓸 만한 게 못된다. 그냥 이렇게 저렇게 삽질한 시행착오의 과정을 보면서 배워보자는 거지, 쓰라고 하는 거 아니다. 실용적인 건 다음, 다음 글에 나온다.

 

다시 한 번 말한다. 바로 쓸 SQL을 원한다면, Back 하자.

 


 

앞의 정규분포 확률밀도 함수를 구하는 건 간단하게 되었는데, 정규분포 함수를 만드는 건 왜 쉽지 않냐라고 하면, 실제로는 저거 외에 누적 정규분포 확률 함수를 구현해야 한다는 거다.

 

앞의 글에서 본 그래프를 먼저 보자.

 

정규분포 확률 밀도.jpg

이건 확률 밀도 함수(라고 한다. 설명 못한다)이며 구하는 것은 저 높이 하나만 구하는 거다. 하지만 저거 말고 진짜 구해야 하는 건 바로 마이너스 무한대에서부터 x 까지의 넓이를 구해야 한다는 거.

 

넓이? 내가 수학에서 싫어하기로 손꼽히는 분야 적분이 들어가야 한다는 거.

 

누적 정규분포 확률.jpg

니미럴 인테그럴...

 

인터넷을 열심히 뒤지고 뒤져서 저 적분식이 있는지 (y = x 를 적분하면 y = 1/2 x2 + C 되는 식) 찾아봤으나 결론은 '그런 거 엄따.'

 

그렇다면...

 

내가 저 넓이를 근사치로라도 구하기 위해서 대충 7σ (표준편차의 7배로 이 정도면 확률밀도가 거의 0에 가깝기 때문에 굳이 마이너스 무한대로부터 시작할 필요는 없다.)정도에서부터 x 까지 값을 일일이 더하는 걸로 해볼까... 생각해서 아래와 같이 컴퓨터를 무지막지하게 고생하는 SQL을 한 번 만들어 봤다.

 

일단 앞에서 봤던, 누적(넓이)가 아닌 확률밀도 (선 높이)을 구하는 SQL을 다시 한 번 보자.

SELECT 1 /
          ( :stdv * SQRT( 2 * ACOS( -1 ) ) )
       *  EXP( - POWER( ( :InpVal - :mean ), 2 )
                 / ( 2 * POWER( :stdv, 2 ) )
             )
FROM DUAL
;

 

저 중에서 :InpVal 을 CONNECT BY LEVEL 의 값으로 대체하여 SUM 을 씌우도록 하자.

SELECT SUM(
           1 /
              ( :stdv * SQRT( 2 * ACOS( -1 ) ) )
           *  EXP( - POWER( ( (LEVEL / :DtlDgr + B.START_VALUE) - :mean ), 2 )
                     / ( 2 * POWER( :stdv, 2 ) )
                 )
          ) / :DtlDgr
FROM    DUAL
      , (SELECT   :mean
                  - SQRT(
                          -2
                        * POWER(:stdv, 2)
                        * LN(  :stdv
                             * SQRT(2 * ACOS(-1))
                             / POWER(10, 5)
                             * 5
                            )
                        ) AS START_VALUE
         FROM   DUAL
        ) B
CONNECT BY LEVEL <= (:InpVal - B.START_VALUE) * :DtlDgr

:InpVal 을 LEVEL / :DtlDgr + B.START_VALUE 로 대체 했는데, START_VALUE 라는 부분은 위에서 말한 7σ 정도로, 평균을 중심으로 확률밀도 (선의 높이)가 0에 매우 가까워지는 x 값을 말한다.

 

그냥 아래 그림의 맨 왼쪽에 X0 지점의 그래프 높이가 0에 가까운데, 저기를 START_VALUE 로 보면 된다. (위에서 POWER(10,5) 로 나누는 부분이, 값이 0에 가까운 10-5 정도면 0으로 본다는 뜻이다.)

정규분포 확률밀도 0인 x 값.jpg

원래 – ∞ (무한대)부터 값을 더해 가야 하는데, 그건 불가능하니까 적당한 선에서(어차피 0에 매우 가까운 값이니 더해봐야 큰 차이 없는 거) 타협한 거. 결국 저 START_VALUE 에서 시작해서 오른쪽으로 높이들을 다 더해서 넓이를 구한다는 의미.

 

:DtlDgr 변수는 정밀도를 뜻하는데, 수치를 올릴수록 좀 더 잘 게 쪼개서 높이를 구하며 좀 더 근사치에 가깝게 한다. 하지만 수치가 늘어날수록 계산 속도가 엄청 늘어나게 되며, 심한 경우 몇 십 초씩 걸리게 되어서 실용적이지 못하다.

 

사실 이 방식으로는 소수점 3~4자리 정도까지의 정밀도밖에 보장하지 못한다. 더 높은 정밀도를 원하면 이후에 쓰여지는 글들을 보도록 하자.

 

여튼...

 

이 방식을 좀 더 속도를 빠르게, 그리고 그 덕분에 정밀도를 높일 여지가 있는 방식을 보자.

 

일반적인 누적 정규분포는 평균과 표준편차가 천차만별이고, 표준편차가 커질수록 계산해야 하는 X 값의 범위가 넓어져서 속도가 느려진다. 표준편차가 10인 거랑, 100인 거의 차이는 10배 가까이 나게 된다.

 

이때 앞에서 본 표준정규분포, 즉 평균은 0이고, 표준편차는 1인 걸 쓰게 되면 어떤 경우에도 X 값의 범위가 일정해져서 누적 분포를 구하기가 나아진다.

 

표준정규분포의 공식은 이전 글에서 나온 걸 다시 한 번 인용하는 걸로 한다.

 

그리고 일반 정규분포를 표준정규분포로 변환시키는 공식은 아래와 같다. (μ 는 평균, σ 는 표준편차, x 는 입력값)

 

표준정규분포를 구현하는 SQL 역시 지난 글에서 봤던 SQL을 다시 확인한다.

SELECT 1 /
          ( SQRT( 2 * ACOS( -1 ) ) )
       *  EXP( - POWER( ( :InpVal ), 2 )
                 / ( 2 )
             )
       AS STD_RGL
FROM DUAL
;

여기에 똑같이 SUM 씌우고, :InpVal 을 CONNECT BY LEVEL 을 가공한 값으로 한다.

 

근데 :InpVal 이 평균(:mean) 일 경우 누적분포값이 0.5 이기 때문에, 아래 그림 부분의 넓이를 구한 후에 0.5에서 저 넓이를 빼거나 더하는 것으로 할 수 있다.

 

누적 정규분포 02.jpg

 

누적 정규분포 03.jpg

이 경우 넓이를 구해야 할 가로축의 수가 줄어들어서 속도 향상에 도움이 된다.(표준정규분포의 경우 x가 -7부터 1까지 넓이 구하기보다는 (–∞에서 0까지 면적인) 0.5 더하고, 나머지 0부터 1까지의 넓이를 구하는 게 계산을 덜 한다는 건 자명하다.)

 

SELECT   0.5
       +   SIGN(:InpVal - :mean) /* 평균보다 크면 +, 작으면 - */
         * SUM( 1 / (SQRT( 2 * ACOS(-1) ) )
                  * EXP( -POWER( X , 2) / 2 )
              )                 /* 표준정규분포 확률밀도 함수의 SUM */
         / (:DtlDgr + 1)
       AS REGULAR_DISTRIBUTE
FROM    
    (
        SELECT  ( ( LEVEL - 1) / :DtlDgr ) AS X
        FROM    DUAL
        CONNECT BY LEVEL <=   ABS(:InpVal - :mean) / :stdv  /* 표준정규분포 변환식 z = (x - μ) / σ */
                            * :DtlDgr
                            + 1
    )
;

 

SQL을 돌린 결과와 EXCEL을 비교해보자.

누적정규분포 확률 결과 01.jpg

 

엑셀에서 NORMDIST 를 사용하고, 누적여부를 TRUE 로 하면 된다.

누적정규분포 확률 결과 01 (엑셀).jpg

소수 4째자리까지는 얼추 맞아졌다. 정밀도를 5,000에서 50,000 으로더 높이면 아래와 같이 나온다.

 

누적정규분포 확률 결과 01 (정밀도 향상).jpg

계산을 10배 더 하니 5째자리까지 비슷해졌다. 시간은 238 mSecs... 이거 한 건 조회할 때에는 몰라도 여러 건 조회할 때에는 분명 부담을 줄 거다.

 

마지막으로 평균보다 적은 케이스 하나만 더 올려서 얼추 맞다는 정도만 확인하자. (이번에는 525 mSec 걸렸다. 위험하다.)

누적정규분포 확률 결과 02 (평균보다 작은 값).jpg

 

엑셀은 아래와 같다.

누적정규분포 확률 결과 02 (평균보다 작은 값 - 엑셀).jpg

 

자, 이렇게 할 수도 있으나 무식한 방법이라고만 알고 넘어가자.

번호 제목 글쓴이 날짜 조회 수 추천 수
835 리눅스 새 하드 장착하기 RanomA 2021.12.01 599 0
834 리눅스 SSH Network error: Software caused connection abort RanomA 2021.05.29 2017 0
833 인터넷 기본 설정 복원 창 띄우는 방법들 file RanomA 2021.05.01 1022 0
832 리눅스 디렉토리별 압축 파일 생성 쉘 스크립트 RanomA 2021.04.04 830 0
831 Javascript 숫자에 끊어 읽기 컴마(,) 추가 RanomA 2020.11.27 751 0
830 Javascript 문자열을 포맷 맞춰 날짜로, 날짜를 포맷 맞춘 문자열로 변환 RanomA 2020.11.26 718 0
829 Javascript 날짜 더하기 RanomA 2020.11.26 450 0
828 Javascript 해당 연월의 마지막 날짜 가져오기 RanomA 2020.11.25 364 0
827 Javascript isNullOrEmpty RanomA 2020.11.25 685 0
826 Javascript 전화번호 포매팅 RanomA 2020.11.25 2308 0
825 Javascript IN RanomA 2020.11.18 260 0
824 Javascript LPAD RanomA 2020.11.17 581 0
823 Adobe Creative Cloud 업데이트 실패 184(혹은 C184) 오류 file RanomA 2020.09.08 1070 0
822 오라클 테이블 레이아웃 조회 Query RanomA 2020.01.13 2063 0
821 SQL Developer의 폰트 설정에서 폰트 목록이 안나타날 때 [6] file RanomA 2019.12.30 4869 0
820 Underscore( _ 문자)를 카멜 케이스(Camel Case)로 변환하기 RanomA 2019.12.06 1814 0
819 라이트룸(Adobe Lightroom) 한자/일본어용 폰트 변경 file RanomA 2018.06.06 2142 0
818 마녀의 빗자루와 악마와의 파티 RanomA 2018.03.23 890 0
817 크레인 안전 점검이 허술해진 게 이명박 때가 아니다? RanomA 2018.01.06 708 0
816 인도 중국의 분쟁 지역 file RanomA 2017.08.17 1293 0