밑바닥부터 시작하는 데이터 과학 ch.06 확률

밑바닥부터 시작하는 데이터 과학

책표지
위 책을 보면서 필자가 직접 코딩하면서 정리한 내용입니다.
책의 모든 내용을 다 포함하고 있지는 않으며, 책에 없는 부가적인 설명이 들어 갈 수 있습니다.
필자가 작성한 Jupyter notebook은 다음 Link에서 다운로드하여 실행이 가능합니다.

06 확률 (probability)

확률이란 어떤 사건 공간에서 특정 사건이 선택될 때 발생하는 불확실성을 수치적으로 나타내는 것입니다.
  • E : 사건
  • P(E) : 사건E가 일어날 확률

1 종속성과 독립성

사건 E의 발생여부가 사건 F의 발생여부에 영향을 미친다면 두 사건은 종속 사건(dependent evnets)라 하며 그렇지 않다면 두 사건은 독립 사건(independent events)라고 합니다.
사건 E와 F가 동시에 일어날 확률이 각각 사건이 발생할 확률의 곱과 같으면 독립 사건을 의미합니다.
P(E, F) = P(E)P(F)
예를 들어서 동전 던지기의 경우 앞에 동전이 뭐가 나왔는냐에 상관없이 뒤에 던질 동전은 1/2확률로 앞면이 나오고 1/2확률로 뒷면이 나옵니다.
두 사건이 반드시 독립 사건이라는 보장이 없고 사건 F의 확률이 0이 아닌 경우, 사건 E가 발생할 조건부 확률(conditional probability는 다음과 같이 정의할 수 있습니다.
P(E|F) = P(E,F) / P(F)
이 식은 P(E,F) = P(E|F)P(F)로 전개가 가능합니다. 만약 E와 F가 독립 사건이라면 P(E,F) = P(E)P(F) 이므로 위 수식은 P(E|F) = P(E)가 됩니다.
예를 들어 보겠습니다. 다음의 두 가지 조건을 가정하겠습니다.
  1. 각 아이가 딸이거나 아들일 확률은 1/2로 동일하다.
  2. 둘째의 설별은 첫째의 성별과 독립이다.
이 경우
  • 두 아이가 모두 딸이 아닐 확률 : 1/4
  • 딸 한 명과 아들 한 명일 확률 : 1/2
  • 두 아이가 모두 딸일 확률 : 1/4
의 확률로 발생합니다.
이 것을 random을 이용해서 수 많은 가족들을 만들어서 검증해 보도록 하겠습니다.
In [4]:
import random

def random_kid():
    return random.choice(['boy', 'girl'])

families = 100000

both_girls = 0
both_boys = 0
one_boy_one_girl = 0

random.seed(0)
for _ in range(families + 1):
    younger = random_kid()
    older = random_kid()
    if younger == 'girl' and older == 'girl':
        both_girls += 1
    elif younger == 'boy' and older == 'boy':
        both_boys += 1
    else:
        one_boy_one_girl += 1

both_girls /= families
both_boys /= families
one_boy_one_girl /= families

print(both_girls)
print(both_boys)
print(one_boy_one_girl)
0.24951
0.2502
0.5003
그렇다면 첫째가 딸인 경우(G), 두 아이가 모두 딸일 확률(B)은 다음과 같이 계산할 수 있습니다.
P(B|G) = P(B,G)/P(G) = P(B)/P(G) = 1/2
In [5]:
P_G = 0
P_B = both_girls

for _ in range(families + 1):
    if random_kid() == 'girl':
        P_G += 1
        
P_G /= families

print(P_G)
print(P_B)
print(P_B / P_G)
0.50016
0.24951
0.4988603646833013
그렇다면 딸이 최소한 한명인 경우(L), 두 아이 모두 딸일 확률은 어떻게 될까요 ?
P(B/L) = P(B,L)/P(L) = P(B)/P(L) = 1/3
In [6]:
P_L = 0

for _ in range(families + 1):
    kids = [random_kid(), random_kid()]
    if 'girl' in kids:
        P_L += 1

P_L /= families

print(P_B)
print(P_L)
print(P_B / P_L)
0.24951
0.7495
0.33290193462308204

2. 베이즈 정리 (Bayes's Theorem)

사건 F가 발생했다는 가정하에 사건 E가 발생할 확률이 필요한데, 사건 E가 발생했을 때 사건 F가 발생한 확률만 주어졌을 경우
P(E|F) = P(E,F)/P(F) = P(F|E)P(E)/P(F)
이 경우 사건 F를 F와 E가 동시에 일어난 경우와 E가 발생하지 않았지만 F가 일어난 확률의 두 경우로 나눈다면,
P(F) = P(F,E) + P(F,┐E)
이를 이용해서 전개해본다면,
P(E|F) = P(F|E)P(E) / [P(F|E)P(E) + P(F,┐E)P(┐E)]
예를 들어보겠습니다.
1만명 중에 1명이 걸리는 질병이 있다고 가정하겠습니다. 이 질병에 대해 병이 있는 경우 '양성' 없는 경우 '음성'이라 판단하는 검사가 99% 정확하다고 할 경우
양성 판정을 받았을 경우를 생각해 보겠습니다. 사건 T는 양성 판정을 나타내고, 사건 D는 질병에 걸린것을 나타낼 때, 양성 판정을 받은 경우, 실제 병에 걸릴 확률은
P(D|T) = P(T|D)P(D) / [P(T|D)P(D) + P(T,┐D)P(┐D)]
In [9]:
P_T_D = 0.99
P_T_not_D = 1 - P_T_D

P_D = 1 / 10000
P_not_D = 1 - P_D

P_D_T = P_T_D * P_D / (P_T_D * P_D + P_T_not_D * P_not_D)
print(P_D_T)
0.009803921568627442
이를 보면 양성 판정을 받은 사람 중 실제로 병에 걸린 사람은 1%도 안된다는 사실을 확인 할 수 있습니다. 이는 질병 증상과 상관없이 무작위의 사람들을 상대로 검사했을 경우의 결과이므로, 실제 증상을 보인 사람을 상대로 검사를 했을 경우와는 차이가 있을 수 있습니다.

3. 확률 변수 (random variable)

사건에서 나올 수 있는 샘플링 결과를 의미합니다.
ex. 동전 던지기 = { 앞, 뒤 }
확률 변수에 값을 곱한 것을 기대값이라 합니다.

4. 연속 분포 (continuous distribution)

  • 이산형 분포(discrete distribution) : 동전 던지기와 같이 각각의 사건이 떨어져 있는 경우
  • 균등 분포(uniform distribution) : 0과 1 사이의 모든 값에 동등한 비중을 준 분포
숫자 0 과 1 사이에는 무한히 많은 수가 존재하기 때문에 특정 한 숫자에서의 비중은 극한값 계산으로 인해 0 이 나올 것입니다. 그렇기 때문에 확률을 구하기 위해서는 특정 구간에서 적분한 값으로 확률을 나타내는 확률밀도함수(probability density function, pdf)로 연속 분포(continuous distribution)를 표현합니다.
  • 누적분포함수(cumulative distribution function, cdf) : 균등 분포에 대한 누적분포를 나타낸 함수

5. 정규 분포 (Normal Distribution)

  • 정규분포 밀도 함수
f(x|μ,σ)=12πσexp((xμ)22σ2)
  • mu : 평균(mean)
  • sigma : 표준편차(standard deviation)
In [11]:
import math

def normal_pdf(x, mu=0, sigma=1):
    sqrt_two_pi = math.sqrt(2 * math.pi)
    return (math.exp(-(x-mu)**2 / (2 * sigma**2)) / (sqrt_two_pi * sigma))
In [27]:
import matplotlib.pyplot as plt

def draw_funcs(func, title, legend_loc = 1):
    xs = [ x / 10 for x in range(-50, 50)]
    plt.plot(xs, [func(x, sigma=1) for x in xs], '-', label='mu=0, sigma=1')
    plt.plot(xs, [func(x, sigma=2) for x in xs], '--', label='mu=0, sigma=2')
    plt.plot(xs, [func(x, sigma=0.5) for x in xs], ':', label='mu=0, sigma=0.5')
    plt.plot(xs, [func(x, mu=-1) for x in xs], '-', label='mu=-1, sigma=1')
    plt.legend(loc=legend_loc)
    plt.title(title)
    plt.show()

draw_funcs(normal_pdf, "Various Normal pdfs")
  • 표준정규분포(standard normal distribution) : mu= 0, sigma= 1인 정규분포
Z가 표준정규분포의 확률변수일 경우 X=σZ+μ 로 X도 정규분포로 표현이 가능하며, 반대로 계산을 하면 Z=(Xμ)/σ 로 표현이 가능합니다.
정규분포의 누적분포함수는 math.erf()함수를 이용해서 쉽게 구현이 가능합니다.
In [28]:
def normal_cdf(x, mu=0, sigma=1):
    return (1 + math.erf((x - mu) / math.sqrt(2) / sigma)) / 2

draw_funcs(normal_cdf, "Various Normal cdfs", 4)
  • Normal CDF의 역함수 : 특정 확률을 갖는 확률변수의 값을 찾음
누적분포함수의 역함수를 쉽게 계산은 못하지만, 이진 검색(binary search)을 이용해서 찾을 수 있음
In [29]:
def inverse_normal_cdf(p, mu=0, sigma=1, tolerance=0.00001):
    
    # 무조건 표준정규분포로 검색
    if mu != 0 or sigma !=1:
        return mu + sigma * inverse_normal_cdf(p, tolerance=tolerance)
    
    low_z, low_p = -10, 0 # normal_cdf(-10) = 0
    hi_z, hi_p = 10, 1    # normal_cdf(10) = 1
    
    while hi_z - low_z > tolerance:
        mid_z = (low_z + hi_z) / 2 # 중간값
        mid_p = normal_cdf(mid_z)  # 중간값의 누적분포
        
        if mid_p < p:
            low_z, low_p = mid_z, mid_p
        elif mid_p > p:
            hi_z, hi_p = mid_z, mid_p
        else:
            break
            
    return mid_z

6. 중심극한정리 (Central Limit Theorem)

동일한 분포에 대한 독립적인 확률변수의 평균을 나타내는 확률변수가 대력적으로 정규분포를 따른다는 정리입니다.
즉, 무한히 계속해서 사건을 일으키다 보면 정규분포를 따른다는 뜻입니다.
  • 이항 확률변수(binomial random variable) : n, p의 2개의 인자로 이루어짐. n개의 독립적인 베르누이 확률변수(Bernoulli random variable)을 더한 것. 각 베르누이 확률변수의 값은 p의 확률로 1, 1-p의 확률로 0이 됨
In [31]:
def bernouli_trial(p):
    return 1 if random.random() < p else 0
    
def binomial(n, p):
    return sum(bernouli_trial(p) for _ in range(n))
베르누이 확률변수의 평균은 p 이며, 표준편차는 p(1p) 입니다. n이 커지면 커질수록 대략 평균 μ=np 이고 표준편차 σ=np(1p)인 정규분포의 확률변수와 비슷하게 됩니다.
In [34]:
from collections import Counter

def make_hist(p, n, num_points):
    data = [binomial(n, p) for _ in range(num_points)]
    
    histogram = Counter(data)
    plt.bar([x - 0.4 for x in histogram.keys()], [v / num_points for v in histogram.values()], 0.8, color='0.75')
    
    mu = p * n
    sigma = math.sqrt(n*p*(1-p))
    
    xs = range(min(data), max(data) + 1)
    ys = [normal_cdf(i + 0.5, mu, sigma) - normal_cdf(i - 0.5, mu, sigma) for i in xs]
    plt.plot(xs, ys)
    plt.title("Binomial Distribution vs. Normal Approximation")
    
    plt.show()

make_hist(0.75, 100, 10000)
  • scipy.stats : 대부분의 유명한 확률분포의 확률밀도함수와 누적분포 함수들이 구현되어 있습니다.

댓글

이 블로그의 인기 게시물

AWS Lambda 사용에 관련된 Tip

AWS Lambda에 Python Slack Chatbot을 통해서 미세먼지 대기정보 알림이 만들기

Jupyter notebook markdown에 내가 원하는 css 적용하기 (Linux / Mac OS)