확률과 통계는 데이터 사이언스의 기본 토대입니다. 나아가, 머신 러닝 그리고 인공지능의 바탕이 되는 원리들도 다른 게 아닌 통계학과 선형대수 이론들이죠. 언젠가 데이터를 다루거나 데이터 사이언스 분야의 논문들을 읽어야 할 때, 수 없이 많은 통계 기초 지식들이 요구될 것입니다.
앞으로 제가 작성하려고 하는 [데이터 사이언스 in Python] 포스트는 기초적인 확률/통계 개념 정리 노트와 이를 코드 상에서 시각화하고 테스트해볼 수 있는 Python 튜토리얼들이 수록될 예정입니다.
Data Scientist 또는 유관 전공자를 희망하는 분들에게 많은 도움이 될 수 있도록, 저 역시 열심히 공부하고 배운 내용을 공유해보겠습니다.
1. 확률 변수(Random Variable)
확률을 따지는 목적은 불확실성, 즉 아직 결과를 들여다 보지 않은 이벤트가 어디로 튈 지 예측하고자 하는 데에 있습니다. 그리고 예상되는 결과물의 가짓 수와 각각이 일어날 확률, 이 전체를 수학적으로 표현한 개념을 확률 변수(Random Variable)라고 합니다.
주사위를 던지면 1부터 6까지 총 6 가지의 결과가 나올 수 있는 것처럼, 결과물의 가짓수가 유한한 확률 모형은 이산 확률 변수(Discrete random variable)라고 부릅니다. 그와는 반대로 연속 확률 변수(Continuous random variable)는 결과물의 범위가 무한한(실수 전체거나, 소숫점 자릿수까지 모두 고려하는) 이벤트를 모델링합니다.
많은 전공 서적이나 자료들이 Random variable을 표기할 때, 대문자 $X$를 주로 사용합니다. $X$가 가령 $a$일 확률이라고 한다면 $P(X = a)$로 표현합니다.
2. 확률 분포(Probability Distribution)
우리는 월드컵에서 대한민국이 우승할 확률이라는 하나의 수치에 관심있을 수 있지만, "그럼 다른 나라들은 우승 가능성이 어느정도 될까?" 라는 궁금중도 갖기 마련입니다.
확률 모형에서 알고 싶은 것은 전체적인 경우의 수와 각 확률에 대한 상대적인 우위입니다. 통계 분야에서 발생할 수 있는 모든 경우에 대해 각각의 확률을 표현한 함수를 확률 분포(Probability Distribution)라고 정의합니다.
앞의 주사위 예시는 각 눈이 나올 확률이 $1/6$로 동일한 매우 간단한 확률 분포를 갖고 있습니다. 이런 기본적인 예시 말고, 현재 각 공학/이학 분야에는 아주 다양한 확률 분포들이 사용되고 있는데요. 이들을 쭉 나열하면 내용이 수도 없이 많아 아지므로 본 포스트에서는 이산 확률 분포와 연속 확률 분포에서 대표적인 예시 3가지씩을 추려 정리하였습니다.
3. 이산 확률 분포의 예시
이산 확률 분포(Descrete probability distribution)는 앞 단어 그대로 이산 확률 변수가 가질 수 있는 확률 분포를 의미하며, 대표적인 이산 확률 분포는 아래 3가지입니다.
- 베르누이 분포 (Bernoulli Distribution)
- 이항 분포 (Binomial Distribution)
- 푸아송 분포 (Possion Distribution)
베르누이 분포 (Bernoulli Distribution)
베르누이 분포(Bernoulli distribution)에서 발생할 수 있는 이벤트는 성공(1)과 실패(0), 두 가지로만 표현됩니다. 동전 던지기가 대표적인 예시인데, 동전을 던져서 앞면이 위로 가면 '1', 뒷면이 위로 나오면 '0'으로 나타낼 수 있습니다. 동전 던지기야 앞/뒷면이 나올 확률이 50:50이겠지만, 일반적으로 성공(1) 이벤트 발생 확률이 '$p$'인 베르누이 확률 모형을 아래와 같이 표현합니다.
$$P(X = 1) = p, \qquad P(X = 0) = 1 - p$$
$$f(x;\ p) = p^{x}(1-p)^{1-x} \qquad \text{for} \; x \in \{0, 1\}$$
Python scipy 패키지로 간단하게 베르누이 확률 모형을 테스트해보았습니다. bernoulli 클래스를 이용하여 동전 던지기 예시($p = 0.5$) 그대로 코드로 옮겨보았습니다. bernoulli.rvs() 메서드를 사용하면 첫번째 인자(예시에서는 250)만큼의 샘플을 추출할 수 있습니다.
from scipy.stats import bernoulli
COIN_TOSS = bernoulli(p=0.5)
outcomes = COIN_TOSS.rvs(250, random_state=0)
250번의 시도 중 1(동전 앞면), 그리고 0(동전 뒷면)이 각각 몇번씩 등장하였는지 도표로 그려줍니다. 어디까지나 표본이기 때문에 정확히 50:50 비율은 아니지만, 앞면 뒷면 모두 비슷하게 나왔네요.
import numpy as np
import matplotlib.pyplot as plt
val, cnt = np.unique(outcomes, return_counts=True)
plt.bar(val, cnt)
plt.xticks(val)
plt.ylabel("Counts")
plt.xlabel("Outcome")
plt.show()
이항 분포 (Binomial Distribution)
이항 분포 (Binomial Distribution)은 베르누이 시행을 여러 번 반복하였을 때 성공 횟수를 모델링하는 확률 분포 모형입니다. 동전을 $n$번 던졌을 때, 앞면이 얼마나 많이 나왔냐가 관심사인 것이죠. 베르누이 시행 $f(x; p)$을 $n$번 반복 수행했을 때, $x = 1$인 경우가 $k$번 등장할 확률은 아래와 같습니다.
$$f(k; n, p) = \begin{pmatrix} n\\k \end{pmatrix} p^{x}(1-p)^{n-k},$$
$$\text{where} \; \begin{pmatrix} n\\k \end{pmatrix} = \frac{n!}{k!(n-k)!}, \;\; 0 \leq k \leq n$$
동전 던지기를 2번 했을 때, 앞면(H)이 몇 번 나올지를 확률적으로 정리하였습니다. $k = 1$인 경우같으면, 1번째 시도에서만 앞면이 나오거나 또는, 첫 시도에서는 뒷면(T)이 나오고 마지막 시도에서만 앞면이 위로 가거나입니다. $k = 2$, 즉 앞면이 총 2번 나올 확률은 $\frac{1}{2} \times \frac{1}{2} = 0.25$ 로 계산됩니다.
위에서 계산된 이론상 수치를 Python에서도 확인해볼 수 있습니다. binom이라는 클래스로 전체 시행 수 $n$을 2로 설정하고, 아래와 같이 확률 분포 그래프를 그려보았습니다.
from scipy.stats import binom
# How many times we get heads when flipping a coin two times?
COIN_TOSS = binom(n=2, p=0.5)
X = [0, 1, 2] # possible outcomes
plt.bar(X, COIN_TOSS.pmf(X))
plt.ylabel("Probability")
plt.xlabel("number of heads")
plt.xticks(X)
plt.ylim(0, 1)
plt.show()
푸아송 분포(Poisson Distribution)
푸아송 확률 변수(Poisson random variable)은 일정 시간 안에 어떤 이벤트가 몇 번 발생하는지를 예측하는 확률 모델입니다. 예를 들어 특정 웹 사이트를 그 날 하루에 몇명이 방문할 지를 푸아송 분포를 통해 확률적으로 추론할 수 있습니다.
어떤 사건이 특정 시간 간격(time interval)에서 평균적으로 $\lambda$ 번 발생한다고 하면, 추후 같은 시간 간격에서 사건이 $k$번 발생할 확률은 다음과 같습니다 (자세한 유도방식이나 활용 예는 추후 다른 포스트에서 다룰 예정입니다).
$$P(k\;\text{event in interval}) = e^{-\lambda}\frac{{\lambda}^k}{k!}$$
아래는 poisson 클래스를 이용하여 '$\lambda$'(mu)가 $5$일 때 총 $10000$번의 푸아송 시행을 한 시뮬레이션 결과입니다. 대부분 평균 발생 건수(mu $= 5$) 근처에서 집계가 되지만, 간혹 가다가 평상시보다 2~3배 이상의 빈도로 사건이 발생하는 경우들도 보입니다.
from scipy.stats import poisson
data = poisson.rvs(mu=5, size=10000)
val, cnt = np.unique(data, return_counts=True)
plt.bar(val, cnt)
plt.xticks(np.arange(1, 26, 2))
plt.ylabel("Counts")
plt.xlabel("Outcome")
plt.show()
4. 연속 확률 분포의 예시
연속 확률 분포(Continuous probability distribution)로는 아래의 3가지 확률 분포를 정리하였습니다.
- 균일 분포 (Uniform Distribution)
- 정규 분포 (Normal Distribution)
- 지수 분포 (Exponential Distribution)
균일 분포 (Uniform Distribution)
연속확률분포에서 가장 간단한 예시라면 균일 분포를 들 수 있겠습니다. 균일 분포는 확률이 일정한 구간이 길게 늘어져 있는 형태를 가집니다. 아래 함수와 그림은 $(a, b)$ 구간의 균일 분포를 표현한 것입니다. 확률의 총합은 무조건 1이므로 $a$와 $b$ 사이에 아치형으로 된 사각형 넓이도 1이 되어야 하며, 따라서 $(a, b)$ 구간이 좁을수록 구간 내 특정 사건 point의 발생확률이 증가합니다.
$$
f(x) = \begin{cases}
\frac{1}{b-a}, & \text{for } a \leq x \leq b,\\
0, & \text{otherwise}
\end{cases}
$$
균일 분포 모형은 아래 코드처럼 테스트해볼 수 있습니다. 연속확률분포라서 표본 값들은 실수 범위에 속하므로 'bin'을 이용하여 0.25 마다 구간을 잘라 히스토그램으로 시각화하였습니다.
from scipy.stats import uniform
# size = '표본 수', loc = 'a'에 해당, scale = 구간 길이 (b-a)
data = uniform.rvs(size=10000, loc = 3, scale=4)
bin = np.arange(0, 10, 0.25)
plt.hist(data, bins=bin, edgecolor='blue')
plt.show()
정규 분포 (Normal Distribution)
데이터 사이언스에서 매우 광범위하게 사용되는 확률분포이며, 가우시안 분포(Gaussian distribution)라는 또 다른 명칭을 갖고 있습니다. 정규 분포는 평균값($\mu$)을 기준으로 종 형태의 모양을 띠는 것이 특징이며, 종 모양이 위로 쏠릴지, 옆으로 늘어지는 형태일지는 표준 편차($\sigma$)에 의해 결정됩니다. 정규분포함수는 아래와 같이 정의됩니다.
$$f(x|\mu, {\sigma}^2) = \frac{1}{\sqrt{2\pi{\sigma}^2}} e^{-\frac{(x-\mu)^2}{2{\sigma}^2}}$$
정규 분포의 시뮬레이션 코드입니다.
from scipy.stats import norm
# size = '표본 수', loc = 평균, scale = 표준편차 제곱
data = norm.rvs(size=10000, loc = 0, scale=1) # N(0, 1)
bin = np.arange(-3, 3, 0.25)
plt.hist(data, bins=bin, edgecolor='blue')
plt.show()
지수 분포 (Exponential Distribution)
지수 분포는 앞서 살펴본 푸아송 분포와 관련이 깊습니다. 일정 시간동안 발생하는 사건의 횟수가 푸아송 분포를 따른다면, 지수 분포는 바로 그 다음 사건이 일어나기까지의 대기 시간을 모델링해주는 확률 분포입니다. 평균 발생 빈도가 $\lambda$인 사건의 지수 분포식은 다음과 같이 표현됩니다.
$$
f(x; \lambda) = \begin{cases}
\lambda e^{-\lambda x}, & x \ge 0,\\
0, & x < 0
\end{cases}
$$
expon 는 지수 분포 관련 scipy 클래스입니다. 분포 함수를 생성할 때, 'scale' 인자에 $\lambda$의 역수가 들어가는 점을 주의하세요.
from scipy.stats import expon
# size = '표본 수', loc = 1/λ
data = expon.rvs(size=10000, scale=1) # N(0, 1)
bin = np.arange(0, 5, 0.25)
plt.hist(data, bins=bin, edgecolor='blue')
plt.show()