Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

24 확률분포

Updated: 19 mrt 2026

기본 설정

numpypandas 라이브러리를 각각 nppd로 불러온다.

import numpy as np
import pandas as pd

데이터프레임의 chained indexing을 금지시키기 위한 설정을 지정한다. Pandas 3.0 버전부터는 기본 옵션으로 지정된다.

pd.options.mode.copy_on_write = True

주피터 노트북에서 부동소수점의 출력을 소수점 이하 6자리로 제한한다. 아래 코드는 주피터 노트북에서만 사용하며 일반적인 파이썬 코드가 아니다.

%precision 6
'%.6f'

아래 코드는 데이터프레임 내에서 부동소수점의 출력을 소수점 이하 6자리로 제한한다.

pd.set_option('display.precision', 6)

코드에 사용되는 데이터 저장소의 기본 디렉토리를 지정한다.

data_url = 'https://raw.githubusercontent.com/codingalzi/statsRev/refs/heads/master/data/'

데이터 시각화를 위해 matplotlib.pyplotplt라는 별칭으로 불러온다.

import matplotlib.pyplot as plt

주요 내용

이 장에서는 확률분포와 확률변수의 개념을 캘리포니아 주택가격 데이터셋을 활용해 설명한다. 먼저 %s장에서 생성한 데이터 객체들중에서 이 장에서 다룰 객체들을 간단히 요약한다.

모집단과 표본 (요약)

  • 모집단: 캘리포니아 주택가격 데이터셋. 아래 코드는 캘리포니아 주택가격 데이터셋에서 주택가격이 50만을 초과하는 경우는 삭제하고 인덱스를 초기화한 후 가구소득 특성만을 남긴다. 또한 인덱스의 이름을 district로 지정한다.

housing = pd.read_csv(data_url+"california_housing_mini.csv")

# 주택가격이 50만1달러 이상인 구역 삭제
house_value_max = housing['median_house_value'].max() # 500,001
mask = housing['median_house_value'] >= house_value_max
housing = housing[~mask]

# 인덱스 초기화
housing = housing.reset_index(drop=True)

# 인덱스 이름 지정
housing.index.name = 'district'
housing
Loading...
  • 가구소득 범주 특성 추가

housing["income_cat"] = pd.cut(housing["median_income"],
                               bins=[0., 1.5, 3.0, 4.5, 6., np.inf],
                               labels=[1, 2, 3, 4, 5])

housing
Loading...
  • 가구소득 범주별 그룹화: 그룹별 표본 크기 확인

stratification = housing.groupby('income_cat', observed=True, group_keys=True)
stratified_count = stratification.count()
stratified_count
Loading...
  • 모집단의 가구소득 범주별 상대도수

stratified_ratio = stratified_count/(housing.shape)[0]
stratified_ratio
Loading...
  • 무작위 추출된 표본: 모집단의 10% 무작위 추출

random_sampling = housing.sample(frac=0.1, random_state=42)
random_sampling
Loading...
  • 무작위 추출된 표본의 가구소득 범주별 그룹 크기

random_sampling_count = random_sampling.groupby('income_cat', observed=False).count()
random_sampling_count
Loading...
  • 무작위 추출된 표본의 가구소득 범주별 상대도수

random_total = random_sampling_count.sum()
random_sampling_ratio = random_sampling_count / random_total
random_sampling_ratio
Loading...
  • 가구소득 범주별 상대도수 비교

proportions = pd.concat([stratified_ratio.iloc[:, [1]],
                         random_sampling_ratio.iloc[:, [1]]],
                        axis=1)

proportions.columns = ['전체', '무작위 추출']
proportions.index.name = '가구소득 범주'
proportions
Loading...

24.1확률분포

모집단에서 임의로 표본을 선택하는 무작위 추출의 결과는 일반적으로 매번 다르고 예측할 수 없다. 하지만 무작위 추출을 실행할 때 나올 결과들의 확률을 계산할 수 있는 경우가 있다.

예를 들어, 주사위를 던질 때 나오는 값은 1부터 6까지의 정수 중에서 무작위로 하나의 수를 선택하는 무작위 추출이다. 주사위를 던지면 매번 어떤 값이 나올지 예상할 수는 없지만, 1부터 6까지의 값들이 균등하게 1/6의 확률로 나온다는 사실은 알고 있다.

이처럼, 무작위 실험의 결과는 예측 불가능하지만, 각 결과가 나타날 확률이 알려져 있을 때, 이 결과를 수치화한 변수를 확률변수random variable, 그 확률들을 표로 나타낸 것을 확률분포probability distribution라 한다.

확률변수 XX가 특정한 값 xkx_k를 취할 확률이 pkp_k이면, 다음과 같이 표기한다.

P(X=xk)=pkP(X=x_k) = p_k

예를 들어, 주사위를 던졌을 때 나오는 값을 확률변수 XX라 하면, XX가 1부터 6까지의 정수를 가질 확률은 모두 1/6로 동일하고, 다음과 같이 표현한다.

P(X=1)=P(X=2)==P(X=6)=16P(X=1) = P(X=2) = \cdots = P(X=6) = \frac1 6

따라서 확률변수 XX의 확률분포는 다음과 같고, '확률변수 XX는 이 확률분포를 따른다’고 말한다.

X123456
확률1/61/61/61/61/61/6

24.1.1무작위 추출의 확률분포

이제 housing 데이터셋의 예를 통해서, 모집단의 확률분포와 무작위 추출의 관계를 이해해보자.

모집단인 housing 데이터셋에서 임의로 선택한 구역의 가구소득 범주를 XX라 하면, XX는 아래 확률분포를 따르는 확률변수가 된다.

X12345
확률0.0413720.3330110.3610170.1779920.086607

이유는 앞서 계산한 가구소득 범주별 상대도수가 아래 표의 “전체” 열과 같기 때문이다.

proportions
Loading...

위 표의 “무작위 추출” 특성에서 볼 수 있듯이, 모집단인 19,674개 구역의 10%인 1,968개 구역을 무작위 추출하면 가구소득 범주별 상대도수가 모집단의 상대도수와 유사함을 알 수 있다. 즉, 표본의 크기가 충분히 크면, 무작위 추출된 표본의 분포는 모집단의 확률분포에 점점 가까워진다. 이러한 성질은 상대도수 히스토그램을 통해서도 확인할 수 있다.

plt.hist() 함수 활용

아래 코드는 무작위 추출 표본의 가구소득 범주별 상대도수 히스토그램을 모집단의 가구소득 범주별 상대도수 히스토그램과 비교한다. plt.hist() 함수를 호출할 때 density=True를 지정하면 도수 대신에 상대도수를 막대그래프 형식으로 그려준다. hist() 함수의 자세한 사용법은 이미 7장에서 설명했으므로, 여기서는 생략한다.

income_cat = housing['income_cat']
income_cat_random = random_sampling['income_cat']

fig = plt.figure(figsize=(12,4))
ax1 = fig.add_subplot(1, 2, 1)
ax2 = fig.add_subplot(1, 2, 2)

ax1.hist(income_cat, bins=5, range=(1, 6), density=True, rwidth=0.54)
ax1.set_xticks(np.linspace(1.5, 5.5, 5))
ax1.set_xticklabels(np.arange(1, 6))
ax1.set_xlabel('Income category')
ax1.set_ylabel('Relative frequency')
ax1.set_title('Population')

ax2.hist(income_cat_random, bins=5, range=(1, 6), density=True, rwidth=0.54)
ax2.set_xticks(np.linspace(1.5, 5.5, 5))
ax2.set_xticklabels(np.arange(1, 6))
ax2.set_xlabel('Income category')
ax2.set_ylabel('Relative frequency')
ax2.set_title('Random sampling')

plt.show()
<Figure size 1200x400 with 2 Axes>

24.2균등분포

확률변수가 가질 수 있는 모든 값에 대해 동일한 확률을 갖는 확률분포를 균등분포uniform distribution라 부른다. 앞에서 다룬 주사위를 던졌을 때 나오는 값의 확률분포가 균등분포의 예이고, 여기서는 housing의 index 집합에서 무작위 추출을 할 때의 예를 살펴본다.

다음 코드는 모집단인 housing 데이터프레임에서 무작위 추출로 10%의 표본을 생성할 때 사용한 것이다.

housing.sample(frac=0.1, random_state=42)

여기에 사용된 sample() 메서드는 housing의 인덱스 집합, 즉 0부터 19,674까지의 정수 집합을 모집단으로 해서 무작위 추출로 크기 1,968의 표본을 생성한다.

housing_index = housing.index
housing_index
RangeIndex(start=0, stop=19675, step=1, name='district')

아래 코드는 실제로 sample() 메서드에 의해 추출된 인덱스들이 0부터 19,674까지의 인덱스 영역 전체에 골고루 분포됨을 시각적으로 보여준다. random_sampling 표본의 크기가 1,968이고, 전체 인덱스 범위는 50개 구간으로 나뉘었으므로, 각 구간당 평균적으로 1968/50개의 표본을 포함해야 하며 빨간 점선이 이를 가리킨다.

여기서 사용된 matplotlib 라이브러리의 hlines() 함수는 그래프에 수평선을 그리는 함수로서, 사용법은 plt.hlines(y, xmin, xmax, 기타 키워드 인자)이다.

  • y: 수평선을 그릴 y좌표의 단일값 또는 배열.

  • xmin: 수평선이 시작될 x 좌표의 단일값 또는 배열.

  • xmax: 수평선이 끝날 x 좌표의 단일값 또는 배열.

  • 기타 키워드 인자: colors, linestyles, linewidth, label 등.

freq, edges, _ = plt.hist(random_sampling.index, bins=50, range=(0, 19674), rwidth=0.7)
plt.hlines(1968/50, 0, 20000, color='red', linestyles=':')

plt.xlabel('Index')
plt.ylabel('Frequency')
plt.show()
<Figure size 640x480 with 1 Axes>

실제로 각 구간별 도수와 평균값은 다음과 같다.

  • 구간별 도수

freq
array([42., 30., 34., 34., 23., 49., 44., 46., 45., 38., 39., 38., 35., 28., 42., 36., 38., 34., 35., 41., 50., 42., 39., 34., 31., 45., 40., 44., 38., 42., 38., 36., 44., 50., 45., 37., 45., 39., 41., 42., 41., 35., 57., 42., 39., 32., 35., 34., 41., 39.])
  • 구간별 도수 평균값

np.mean(freq)
39.360000

모집단 housing_index로부터 sample() 메서드에 의해 무작위 추출된 인덱스가 위 히스토그램에 표현한 50개 구간의 어디에 속하는지, 그 구간의 번호를 확률변수 YY라 하자. 그러면 YY가 1부터 50까지의 구간 번호 kk가 될 확률은 거의 동일하게 50/19680.0250/1968 \simeq 0.02 이어야 한다. 즉, 확률변수 YY는 다음과 같은 균등분포를 따른다.

P(Y=k)=50/19680.02,k=1,,50P(Y = k) = 50/1968 \simeq 0.02 , \quad k=1,\cdots, 50

이를 실제 데이터로 확인할 수 있다.

  • 구간별 상대도수

freq/1968
array([0.021341, 0.015244, 0.017276, 0.017276, 0.011687, 0.024898, 0.022358, 0.023374, 0.022866, 0.019309, 0.019817, 0.019309, 0.017785, 0.014228, 0.021341, 0.018293, 0.019309, 0.017276, 0.017785, 0.020833, 0.025407, 0.021341, 0.019817, 0.017276, 0.015752, 0.022866, 0.020325, 0.022358, 0.019309, 0.021341, 0.019309, 0.018293, 0.022358, 0.025407, 0.022866, 0.018801, 0.022866, 0.019817, 0.020833, 0.021341, 0.020833, 0.017785, 0.028963, 0.021341, 0.019817, 0.01626 , 0.017785, 0.017276, 0.020833, 0.019817])
  • 구간별 상대도수 평균값

np.mean(freq/1968)
0.020000

24.2.1넘파이의 random.choice() 함수

데이터프레임의 sample() 메서드는 앞서 설명한대로 모집단 전체 인덱스를 기준으로 균등하게 표본을 추출한다. 넘파이의 random.choice() 함수를 이용해도 동일한 결과를 얻을 수 있는데, 이 함수는 균등분포 외의 임의의 확률분포에서도 무작위 표본추출에 활용될 수 있다.

아래 코드는 0부터 19,674까지의 정수 중에서 무작위로 1,968개의 정수를 중복 없이 추출한다. 무작위 추출의 시드를 sample() 메서드에서의 random_state=42와 동일하게 42로 지정해야 함에 유의한다.

np.random.seed(42)
random_choice = np.random.choice(range(0, 19675), 1968, replace=False)
random_choice
array([14447, 13921, 12981, ..., 5018, 967, 3975])

선택된 정수를 크기순으로 정렬해 처음 10개의 값을 확인하면 다음과 같다.

random_choice.sort()
random_choice[:10]
array([ 3, 6, 17, 35, 42, 57, 59, 103, 115, 127])

데이터프레임 random_sampling의 인덱스도 크기순으로 정렬해 확인해보자.

random_index = np.array(random_sampling.index)
random_index.sort()
random_index[:10]
array([ 3, 6, 17, 35, 42, 57, 59, 103, 115, 127])

처음 10개만이 아니라 두 표본 전체를 확인해도 모든 항목이 동일하다. 넘파이 어레이의 all() 메서드는 모든 항목이 참일 때 True를 반환한다.

(random_choice == random_index).all()
True

24.2.2특정 확률분포를 따르는 무작위 추출

앞서 보았듯이 np.random.choice() 함수는 균등분포를 따른다. 1부터 5까지의 정수에 대해 실행하면 각각의 정수가 1/5의 확률로 선택됨을 간단한 모의실험으로 확인해보자.

아래 코드는 np.random.choice() 함수를 이용하여 1부터 5까지의 정수를 무작위로 1만 개 선택한 후, 선택된 표본을 대상으로 도수분포표를 출력한다.

np.random.seed(42)

random_trial = 10000
sample = np.random.choice(range(1, 6), random_trial)
freq, _ = np.histogram(sample, bins=5, range=(1, 6))
freq
array([2047, 2016, 1943, 1975, 2019])

1부터 5까지의 각 정수별 도수와 상대도수를 데이터프레임으로 확인하면 각 정수가 모두 약 20%씩 선택되었음이 확인된다.

freq10000 = pd.DataFrame({'도수': freq, '상대도수': freq/random_trial},
                         index=range(1, 6))

freq10000
Loading...

p = 확률분포 키워드 인자

앞서 말한대로 np.random.choice() 함수가 특정 확률분포를 따르도록 할 수 있다. 예를 들어, 1부터 5까지의 정수를 무작위 추출하되, 캘리포니아 주택데이터의 가구소득 범주의 확률분포를 따르도록 할 수 있다.

먼저 가구소득 범주를 가르키는 확률변수 XX의 확률분포를 1차원 어레이 prob_X로 지정한다.

prob_X = proportions['전체'].values
prob_X
array([0.041372, 0.333011, 0.361017, 0.177992, 0.086607])

이제 앞의 실험에서 사용한 np.random.choice() 함수 호출에 p = prob_X 인자를 추가하면, 1부터 5까지의 정수 1만개를 추출할 때 확률변수 XX의 확률분포를 따르게 된다. 도수분포표의 상대도수를 확인하면 prob_X와 매우 유사함이 확인된다.

np.random.seed(42)

random_trial = 10000
sample = np.random.choice(range(1, 6), random_trial, p = prob_X)
freq, _ = np.histogram(sample, bins=5, range=(1, 6))
income_cat_freq10000 = pd.DataFrame({'도수': freq, '상대도수': freq/random_trial},
                                    index=range(1, 6))
income_cat_freq10000
Loading...

아래 코드는 위 데이터프레임의 상대도수를 plot() 함수를 통해 히스토그램으로 그리고, prob_X의 확률들도 빨간 선으로 표시한다.

  • plot() 함수의 rot=0 인자는 x축 레이블을 수평(0도)으로 표시하기 위함이고, kind='bar'는 막대 그래프로 그리도록 설정한다.

  • plt.hlines() 함수가 그릴 수평선의 y좌표가 prob_X 배열이므로, x좌표의 시작점과 끝점을 나타내는 인수들도 이와 동일한 크기 5의 배열로 주어져야 한다. plot.bar() 함수가 그리는 각 막대의 중심이 0, 1, 2, 3, 4 등으로 정해지므로, x좌표의 시작점과 끝점은 각각 -0.3과 0.3으로부터 1씩 증가시킨 크기 5의 배열로 지정했다.

fig = plt.figure(figsize=(6,4 ))
hx = fig.add_subplot(1,1,1)

_ = income_cat_freq10000['상대도수'].plot(ax=hx, rot=0, kind='bar') # 막대그래프
hx.hlines(prob_X, np.arange(-0.3, 4, 1), np.arange(0.3, 4.5, 1), colors='red')
hx.set_xlabel('Income category')
hx.set_ylabel('Relative frequency')
plt.show()
<Figure size 600x400 with 1 Axes>
np.arange(-0.3, 4, 1)
array([-0.3, 0.7, 1.7, 2.7, 3.7])

동일한 그림을 추출된 표본 데이터 자체를 이용하여 그릴 수도 있다. plt.hist() 함수의 density=True는 도수가 아닌 상대도수의 히스토그램을 그려준다. 단, plt.hist() 함수가 그리는 각 막대의 중심이 1.5, 2.5, 3.5, 4.5, 5.5 등으로 정해지므로, x좌표의 시작점과 끝점은 각각 1.2와 1.8로부터 1씩 증가시킨 크기 5의 배열로 지정한다.

fig = plt.figure(figsize=(6,4 ))
hx = fig.add_subplot(1,1,1)

hx.hist(sample, bins=5, range=(1, 6), density=True, rwidth=0.54)
hx.hlines(prob_X, np.arange(1.2, 6, 1), np.arange(1.8, 6.3, 1), colors='red')

hx.set_xticks(np.linspace(1.5, 5.5, 5))
hx.set_xticklabels(np.arange(1, 6))
hx.set_xlabel('Income category')
hx.set_ylabel('Relative frequency')
plt.show()
<Figure size 600x400 with 1 Axes>

24.3연습문제