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.

28 표본분포

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)

데이터 시각화를 위해 matplotlib.pyplotplt로, seabornsns로 불러온다. seaborn 라이브러리는 통계 관련 데이터의 정보를 보다 세련되고 정확하게 전달하는 그래프를 그리는 도구를 제공한다. matplotlib 라이브러리를 바탕으로 만들어져서 함께 사용해도 된다.

import matplotlib.pyplot as plt
import seaborn as sns

그래프 스타일을 seaborn에서 제공하는 white 스타일로 지정한다.

sns.set_style("white")

데이터 저장소 디렉토리

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

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

주요 내용

표본 분포의 주요 개념을 소개한다.

  • 독립동일분포

  • 정규분포 연산

  • 표본평균의 분포

  • 중심극한정리

  • 큰 수의 법칙

28.1독립성과 상관성

28.1.1독립성

확률변수 XXYY가 서로 어떤 영향도 끼치지 않을 때 상호 독립이라 한다. 수식으로 표혀하면 다음 수식이 성립할 때 두 확률변수가 상호 독립이라 정의한다.

fXY(x,y)=fX(x)fY(y)f_{XY}(x, y) = f_X(x) \cdot f_Y(y)

위 수식에서 fXf_XfYf_Y 는 각각 XXYY에 대한 주변확률을 계산하는 함수를 가리킨다. XXYY가 이산확률변수인 경우 주변확률질량함수는 다음과 같이 정의된다.

fX(x)=kfXY(x,yk)fY(y)=nfXY(xn,y)f_X(x) = \sum_k f_{XY}(x, y_k) \qquad f_Y(y) = \sum_n f_{XY}(x_n, y)

xnx_nyky_k는 각각 확률변수 XXYY가 가리킬 수 있는 유한개의 값을 나타낸다.

예제

예를 들어 XX는 공정한 주사위를 던져서 나오는 값을 가리키는 확률변수라 하자. XX의 확률분포는 다음과 같다.

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

반면에 YY는 아래 확률분포를 갖는 불공정한 주사위를 던져서 나오는 값을 가리키는 확률 변수라 하자.

Y123456
확률1/212/213/214/215/216/21

공정한 주사위와 불공정한 주사위를 동시에 던져서 나오는 값으로 구성된 (x,y)(x, y) 가 발생할 확률은 단순히 xx가 발생할 확률과 yy가 발생할 확률의 곱이다. 두 주사위의 결과 xxyy가 서로 어떤 영향도 주고받지 않기 때문에 각각의 경우가 발생할 확률을 곱한다.

xx가 발생할 확률은 항상 1/6이고 yy가 발생할 확률은 y/21y/21이기게 결합확률질량함수는 다음과 같다.

def f_XY(x, y):
    if y in range(1, 7):
        return (1/6) * (y/21) # x의 확률은 항상 1/6
    else:
        return 0

확률변수 각각에 대한 주변확률질량함수를 f_XY()를 이용하여 정의해보자.

  • XX에 대한 주변확률질량함수

def f_X(x):
    y_probs = [f_XY(x, y) for y in range(1, 7)]
    return np.sum(y_probs)
  • YY에 대한 주변확률질량함수

def f_Y(y):
    x_probs = [f_XY(x, y) for x in range(1, 7)]
    return np.sum(x_probs)

아래 코드가 두 확률변수의 독립성을 확인해준다

xy = [f_XY(x, y) for x in range(1, 7) for y in range(1, 7)]
xMy = [f_X(x) * f_Y(y) for x in range(1, 7) for y in range(1, 7)]
np.allclose(xy, xMy)
True

28.1.2독립성 대 무상관성

확률변수 XXYY가 상호 독립이면 두 확률변수 사이에 어떠한 상관성도 존재하지 않는다. 예를 들어, 결합확률분포의 공분산과 상관계수도 0이 된다. 여기서는 두 개의 이산 확률변수 XXYY로 구성된 결합확률변수 (X,Y)(X, Y)를 이용하여 독립성과 무상관성을 설명한다.

(X,Y)(X, Y)의 확률질량함수를 fXYf_{XY} 할 때 각 확률변수에 대한 주변확률질량함수, 기댓값, 분산과 두 확률변수의 선형 상관관계를 측정하는 공분산과 피어슨 상관계수는 아래와 같이 정의된다.

  • 기댓값

E[X]=nxnfX(xn)=nxnkfXY(xn,yk)E[Y]=kykfY(yk)=kyknfXY(xn,yk)\begin{align*} E[X] &= \sum_n x_n\, f_X(x_n) = \sum_n x_n\, \sum_k f_{XY}(x_n, y_k)\\[.5ex] E[Y]&= \sum_k y_k\, f_Y(y_k) = \sum_k y_k\, \sum_n f_{XY}(x_n, y_k)\\[.5ex] \end{align*}
  • 분산

Var(X)=n(xnE[X])2fX(xn)Var(Y)=k(ykE[Y])2fY(yk)\begin{align*} Var(X) &= \sum_n (x_n-E[X])^2\, f_X(x_n)\\[.5ex] Var(Y) &= \sum_k (y_k-E[Y])^2\, f_Y(y_k)\\[.5ex] \end{align*}
  • 공분산과 피어슨 상관계수

Cov(X,Y)=nk(xnE[X])(ykE[Y])fXY(xn,yk)Corr(X,Y)=Cov(X,Y)Var(X)Var(Y) \begin{align*} Cov(X, Y) &= \sum_n\sum_k (x_n-E[X])\, (y_k-E[Y]) \, f_{XY}(x_n, y_k)\\[1ex] Corr(X, Y) &= \frac{Cov(X, Y)}{\sqrt{Var(X)\, Var(Y)}}\ \end{align*}

독립이면 무상관이다!

아래 식은 두 확률변수가 상호 독립이면 공분산이 0임을 증명한다.

Cov(X,Y)=nk(xnE[X])(ykE[Y])fXY(xn,yk)=nk(xnE[X])(ykE[Y])fn(xn)fY(yk)=n((xnE[X])fn(x)k(ykE[Y])fY(yk))=0\begin{align*} Cov(X, Y) &= \sum_n\sum_k (x_n - E[X])\, (y_k - E[Y]) \, f_{XY}(x_n, y_k)\\[1ex] &= \sum_n\sum_k (x_n - E[X])\, (y_k - E[Y]) \, f_n(x_n)\, f_{Y}(y_k)\\[1ex] &= \sum_n\big( (x_n - E[X])\, f_n(x)\, \sum_k (y_k - E[Y]) \, f_{Y}(y_k)\big )\\[1ex] &= 0 \end{align*}

위 수식에서 마지막줄이 0인 이유는 바로 윗줄에 포함된 아래 수식이 0으로 계산되기 때문이다.

k(ykE[Y])fY(yk)=kyfY(yk)kE[Y]fY(yk)=E[Y]E[Y]kfY(yk)=E[Y]E[Y]=0\begin{align*} \sum_k (y_k-E[Y]) \, f_{Y}(y_k) &= \sum_k y \, f_{Y}(y_k) - \sum_k E[Y] \, f_{Y}(y_k) \\[.5ex] &= E[Y] - E[Y] \, \sum_k f_{Y}(y_k) \\[.5ex] &= E[Y] - E[Y] \\[.5ex] &= 0 \end{align*}

실제로 공정한 주사위를 던진 결과와 불공정한 주사위를 던진 결과의 공분산은 0으로 계산된다. 공분산이 0이면 피어슨 상관계수다 당연히 0이다.

  • XX의 기댓값: E[X]E[X]

E_X = np.sum([x * f_XY(x, y) for x in range(1, 7) for y in range(1, 7)])
E_X
3.500000
  • YY의 기댓값: E[Y]E[Y]

E_Y = np.sum([y * f_XY(x, y) for x in range(1, 7) for y in range(1, 7)])
E_Y
4.333333
  • XXYY의 공분산: Cov(X,Y)Cov(X, Y)

Cov_XY = np.sum([(x-E_X)*(y-E_Y)*f_XY(x, y) for x in range(1, 7) for y in range(1, 7)])
Cov_XY
-0.000000

무상관이지만 독립이 아닐 수 있다!

결합확률변수 XYXY가 다음과 같이 정의된다.

x=np.array([1,0,-1,0])
y=np.array([0,1,0,-1])

XY = pd.DataFrame({'X':x, 'Y':y})
XY
Loading...

네 가지 경우가 모두 한 번씩만 사용되기에 XYXY는 균등분포를 따른다.

XY(1, 0)(0, 1)(-1, 0)(0, -1)
확률1/41/41/41/4

함수 f_XY()XYXY의 결합확률질량함수를 가리킨다.

def f_XY(x, y):
    if (x, y) in [(1, 0), (0, 1), (-1, 0), (0, -1)]:
        return 1/4
    else:
        return 0

확률변수 각각에 대한 주변확률질량함수는 다음과 같다.

  • XX에 대한 주변확률질량함수

def f_X(x):
    y_probs = [f_XY(x, y) for y in range(-1, 2)]
    return np.sum(y_probs)
  • YY에 대한 주변확률질량함수

def f_Y(y):
    x_probs = [f_XY(x, y) for x in range(-1, 2)]
    return np.sum(x_probs)

이제 확률변수 XXYY의 공분산을 확인한다. 먼저 각 확률변수에 대한 기댓값을 계산한다.

  • XX의 기댓값: E[X]E[X]

E_X = np.sum([x * f_X(x) for x in range(-1, 2)])
E_X
0.000000
  • YY의 기댓값: E[Y]E[Y]

E_Y = np.sum([y * f_Y(y) for y in range(-1, 2)])
E_Y
0.000000

아래 코드는 XXYY의 공분산으로 0으로 계산한다.

np.sum([(x-E_X)*(y-E_Y)*f_XY(x, y) for x in range(-1, 2) for y in range(-1, 2)])
0.000000

균등분포의 공분산

XYXY가 균등분포를 따르기에 데이터프레임의 cov() 메서드를 이용하여 공분산을 확률을 무시한 채 바로 계산할 수 있으며 앞서 확인한 대로 두 확률변수의 공분산이 0으로 계산된다.

XY.cov(ddof=0)
Loading...

공분산이 0이기에 XXYY 사이에는 선형 상관관계가 없다. 하지만 선형이 아닌 다른 상관관계가 있음을 아래 코드가 그리는 그래프가 보여준다.

# 원 그래프 좌표 생성
# cos(theta)**2 + sin(theta)**2 = 1 
# theta 는 0부터 2*pi 까지 변함.

theta = np.linspace(0, 2*np.pi, 300)
x = np.cos(theta)
y = np.sin(theta)

# 반지름인 1인 원 위에 위치한 정수 좌표 4개
points = [(1, 0), (-1, 0), (0, 1), (0, -1)]

fig, ax = plt.subplots(figsize=(5, 5))

# 원 그리기
ax.plot(x, y, label="")

# 네 정수 좌표 산점도
x_ = [i for (i, j) in points] # points의 x-좌표값들
y_ = [j for (i, j) in points] # points의 y-좌표값들
ax.scatter(x_, y_, color='red', label="Integer points")

# x-축을 그래프의 중앙(y=0)에 배치
ax.spines['bottom'].set_position(('data', 0))
ax.spines['left'].set_position(('data', 0))

# 불필요한 테두리 숨김
ax.spines['top'].set_color('none')
ax.spines['right'].set_color('none')

# 축 범위와 눈금 지정
ax.set_xlim(-1.2, 1.2)
ax.set_ylim(-1.2, 1.2)

# 눈금 위치 및 값 지정
ax.set_xticks([-1.05, 0.05, 1.05])
ax.set_xticklabels([-1, 0, 1])

ax.set_yticks([-1.05, 1.05])
ax.set_yticklabels([-1, 1])

ax.legend()
ax.set_aspect('equal')

plt.show()
<Figure size 500x500 with 1 Axes>

위 그래프에서 네 개의 빨강점은 XYXY가 가리킬 수 있는 네 개의 값을 네 개의 좌표로 보았을 때 그려진 점들이다. 사실 네 개의 점은 반지름이 1인 원에 포함된 점들 중에서 좌표가 정수인 유일한 네 개의 점이다. 즉 다음이 성립한다.

x2+y2=12x^2 + y^2 = 1^2

이를 통해 XXYY가 가리키는 값들 사이에 상관성이 있음을 알 수 있다. 실제로 주변확률질량함수의 곱과 결합확률질량함수가 서로 다름을 아래 코드가 확인해준다,

f_XY(1, 0) == f_X(1) * f_Y(0)
False

28.2확률변수 연산과 선형변환

확률변수 합과 차

XXYY 두 개의 확률변수가 주어졌을 때 X+YX+YXYX-Y는 각각 두 확률변수의 합과 차를 가리키는 확률변수다. 즉, X+YX+YXX가 가리키는 임의의 값과 YY가 가리키는 임의의 원소의 합을, XYX-YXX가 가리키는 임의의 값과 YY가 가리키는 임의의 원소의 차를 가리킨다.

두 확률분포의 합의 분포와 차의 분포에 대한 확률함수를 직접 계산하기는 일반적으로 매우 어렵다. 반면에 합과 차의 분포의 기댓값과 분산은 아래 수식으로 쉽게 계산된다. 증명은 생략한다.

E[X±Y]=E[X]±E[Y]V(X±Y)=V(X)±V(Y)E[X \pm Y] = E[X] \pm E[Y] \qquad V(X \pm Y) = V(X) \pm V(Y)

확률변수 선형변환

확률변수 XX와 임의의 실수 a,ba, b가 주어졌을 때 aX+baX+bXX가 가리키는 값을 aa배한 다음에 bb를 더해준 값들을 가리키는 확률변수다. 선형변환된 확률분포의 기댓값과 분산은 다음과 같이 계산된다. 증명은 생략한다.

E[aX+b]=aE[X]+bV(aX+b)=a2V(X)E[aX + b] = aE[X] + b \qquad V(aX + b) = a^2V(X)

28.2.1정규분포 합과 차

상호 독립인 확률변수 XXYY가 정규분포를 따른다고 가정한다.

XN(μX,σX2)YN(μy,σy2)X \sim N(\mu_X, \sigma_X^2) \qquad Y \sim N(\mu_y, \sigma_y^2)

그러면 두 확률 번수의 합과 차를 확률변수 X+YX+YXYX-Y 모두 정규분포를 따르며 두 분포가 따르는 정규분포는 다음과 같다.

X+YN(μX+μY,σX2+σY2)XYN(μXμY, σX2+σY2)X+Y \sim N(\mu_X+\mu_Y,\sigma_{X}^{2}+\sigma_{Y}^{2}) \qquad X-Y \sim N(\mu_X-\mu_Y,\ \sigma_{X}^{2}+\sigma_{Y}^{2})

즉, X+YX+Y의 평균값과 분산은 두 확률변수 각각의 평균값의 합과 분산의 합으로, XYX-Y의 평균값과 분산은 두 확률변수 각각의 평균값의 차와 분산의 합으로 계산된다. X+YX+YXYX-Y의 분산이 두 확률변수 각각의 분산의 합으로 계산되는 이유를 직관적으로 설명하기 위해 다음 두 예제를 이용한다.

예제: 남성, 여성 체중의 합의 분포

한국 20대 남성, 여성의 체중 분포 XXYY는 다음과 같다고 가정한다.

XN(76.59,11.732)YN(58.17,9.282)X \sim N(76.59, 11.73^2) \qquad Y \sim N(58.17, 9.28^2)

만약 남성과 여성의 체중이 상호 독립이면 남성과 여성의 체중의 합은 다음 분포를 따라야 한다.

X+YN(134.76,14.962)X+Y \sim N(134.76, 14.96^2)

위 사실을 코드로 확인해보기 위해 언급된 평균값과 표준편차를 갖는 정규분포를 이용하여 남성과 여성의 체중 데이터를 각각 1천개씩 무작위로 생성한다.

from scipy.stats import norm
  • 남녀 체중의 평균값과 표준편차 지정

mu_men = 76.59
sigma_men = 11.73

mu_wemen = 58.17
sigma_wemen = 9.28
  • 평균값의 합과 차, 분산 합의 제곱근값 계산

mu_sum = mu_men + mu_wemen
mu_diff = mu_men - mu_wemen

sigma_sum = np.sqrt(sigma_men**2 + sigma_wemen**2)

print("평균값 합:", mu_sum)
print("평균값 차:", mu_diff)
print("분산 합의 제곱근:", round(sigma_sum, 2))
평균값 합: 134.76
평균값 차: 18.42
분산 합의 제곱근: 14.96
  • 남성, 여성, 합, 차의 정규분포 지정

X_men = norm(mu_men, sigma_men)
Y_wemen = norm(mu_wemen, sigma_wemen)

X_sum = norm(mu_sum, sigma_sum)
X_diff = norm(mu_diff, sigma_sum)

지정된 정규분포를 이용하여 남녀 각각 1천 개 데이터 샘플을 무작위로 생성한다. norm 객체의 rvs() 메서드를 이용한다. rvs() 메서드는 누적분포함수의 역함수에 해당한다.

np.random.seed(17)

data_men = X_men.rvs(size=1000)
data_wemen = Y_wemen.rvs(size=1000)

생성된 남녀 데이터는 다음과 같다.

data = pd.DataFrame({'Men': data_men, 'Wemen':data_wemen})
data
Loading...

남녀 데이터의 합과 차를 추가한다.

data['Men+Wemen'] = data_men + data_wemen
data['Men-Wemen'] = data_men - data_wemen
data
Loading...

생성된 데이터셋의 평균값과 표준편차를 확인한다.

data.describe()
Loading...

정규분포를 따르는 데 확률변수의 합과 차 데이터또한 이론적으로 추청된 평균값과 표준편차를 갖는 정규분포를 따른다. 아래 코드는 이를 그래프로 확인해준다.

  • 합 데이터 분포 그래프

fig, ax = plt.subplots()

sns.kdeplot(ax=ax, data=data['Men'], label='Men')
sns.kdeplot(ax=ax, data=data['Wemen'], label='Wemen')
sns.kdeplot(ax=ax, data=data['Men+Wemen'], label='Men+Wemen')

x = np.arange(85, 200, 0.01)
ax.plot(x, X_sum.pdf(x), linestyle=':', label=r'$N(\mu_{sum}, \sigma_{sum}^2)$')

x_ticks = np.append(ax.get_xticks(), [mu_men, mu_wemen, mu_sum])
x_ticks_ = np.append(ax.get_xticks(), [r'$\mu_{men}$', r'$\mu_{wemen}$', r'$\mu_{sum}$']) # 실제로 보이는 눈금 기호

ax.set_xticks(x_ticks, x_ticks_, rotation=90)
ax.get_xticklabels()[-3].set_color("red") # a 눈금 색 지정
ax.get_xticklabels()[-2].set_color("red") # a 눈금 색 지정
ax.get_xticklabels()[-1].set_color("red") # a 눈금 색 지정

ax.set_xlabel('Weight')
ax.legend()

plt.show()
<Figure size 640x480 with 1 Axes>
  • 차 데이터 분포 그래프

fig, ax = plt.subplots()

sns.kdeplot(ax=ax, data=data['Men'], label='Men')
sns.kdeplot(ax=ax, data=data['Wemen'], label='Wemen')
sns.kdeplot(ax=ax, data=data['Men-Wemen'], label='Men-Wemen')

x = np.arange(-35, 80, 0.01)
ax.plot(x, X_diff.pdf(x), linestyle=':', label=r'$N(\mu_{diff}, \sigma_{diff}^2)$')

x_ticks = np.append(ax.get_xticks(), [71.2, 53.7, 17.5])
x_ticks_ = np.append(ax.get_xticks(), [r'$\mu_{men}$', r'$\mu_{wemen}$', r'$\mu_{diff}$']) # 실제로 보이는 눈금 기호

ax.set_xticks(x_ticks, x_ticks_, rotation=90)
ax.get_xticklabels()[-3].set_color("red") # a 눈금 색 지정
ax.get_xticklabels()[-2].set_color("red") # a 눈금 색 지정
ax.get_xticklabels()[-1].set_color("red") # a 눈금 색 지정

ax.set_xlabel('Weight')
ax.legend()

plt.show()
<Figure size 640x480 with 1 Axes>

28.2.2독립성의 중요성

XXYY가 상호 독립이지 않으면 정규분포의 합과 차가 정규분포를 따르지 않을 수 있다. 설명을 위해 %s장에서 소개한 피어슨의 아버지와 아들의 키 데이터셋을 활용한다. 이유는 아들 키는 아버지의 키 유전자에 많은 영향을 받아 절대로 독립적일 수 없기 때문이다.

아래 코드는 피어슨Pearson이 1903년에 실험을 위해 수집한 아버지와 아들의 키로 구성된 데이터셋을 불러온다. 원래 인치 단위를 사용하지만 편의를 위해 센티미터 단위로 변환되었다.

pearson = pd.read_csv(data_url+"pearson_dataset.csv", header=0)

아버지 키와 아들 키 각각 총 1,078개의 부동소수점 값이 포함되었다.

pearson.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1078 entries, 0 to 1077
Data columns (total 2 columns):
 #   Column  Non-Null Count  Dtype  
---  ------  --------------  -----  
 0   Father  1078 non-null   float64
 1   Son     1078 non-null   float64
dtypes: float64(2)
memory usage: 17.0 KB

아버지 키와 아들 키의 상관관계

예상대로 아버지 키와 아들 키 데이터의 공분산이 0이 아니다.

pearson.cov(ddof=0)
Loading...

피어슨 상관계수를 보면 0.5 정도로 상당히 높은 편이다. 아버지 키가 아들 키에 많은 영향을 준다는 것이 확실하다.

pearson.corr()
Loading...

결론적으로 두 데이터는 상호 독립이 절대로 아니다. 실제로 아버지키와 아들 키의 합은 정규분포를 따르지 않음을 아래 코드가 보여준다.

pearson['Father+Son'] = pearson['Father'] + pearson['Son']

pearson_desc = pearson.describe()
pearson_desc
Loading...

평균값의 합은 유지된다.

mu_FS = pearson_desc.loc['mean', 'Father'] + pearson_desc.loc['mean', 'Son']
mu_FS
346.380612

그런데 합의 분산이 두 분산의 합과 다르다.

  • 분산의 합

sigma_FS_squared = pearson_desc.loc['std', 'Father'] ** 2 + pearson_desc.loc['std', 'Son'] ** 2
sigma_FS_squared
99.830524
  • 이론적인 아버지 키와 아들 키의 합의 표준편차

sigma_FS = np.sqrt(sigma_FS_squared)
sigma_FS
9.991523

이론과 실제 계산된 표준편차가 많이 다르다.

  • 실제 아버지 키와 아들 키의 합의 표준편차

sigma_FaddS = pearson_desc.loc['std', 'Father+Son']
sigma_FaddS
12.241158

데이터의 분포를 그래프로 그려보면 합의 분포가 정규분포가 아님을 보다 명확하게 확인할 수 있다.

  • (아버지 키 + 아들 키)의 이론적 정규분포

from scipy.stats import norm

X_FS = norm(mu_FS, sigma_FS)
  • 확률 밀도 함수 그래프로 구분하기

fig, ax = plt.subplots()

# KDE 기법으로 계산된 확률 밀도 함수 그래프
# 아버지 키의 정규분포
sns.kdeplot(ax=ax, data=pearson['Father'], label='Father')
# 아들 키의 정규분포
sns.kdeplot(ax=ax, data=pearson['Son'], label='Son')
# (아버지 키+아들 키)의 실제 분포: 정규분포 아님
sns.kdeplot(ax=ax, data=pearson['Father+Son'], label='Father+Son')

# (아버지 키+아들 키)의 이론적 정규분포
x = np.linspace(300, 400, 100)
ax.plot(x, X_FS.pdf(x), linestyle=':', label=r'$N(\mu_{FS}, \sigma_{FS})$')

ax.set_xlabel('Height')
ax.set_ylabel('Density')

ax.legend()
plt.show()
<Figure size 640x480 with 1 Axes>

28.3표본평균의 분포

독립동일분포

확률론에서 독립동일분포는 특정 확률분포를 따르는 데이터셋에서 무작위로 표본을 독립적으로 여러 번 추출해서 얻어진 값들로 구성된 데이터를 가리키는 다차원 확률변수를 의미한다. 독립동일분포는 보통 'independently and identically distributed’의 표현의 줄임말인 iid로 불린다.

예를 들어 동일한 주사위를 10 번 던져서 나온 값들로 구성된 튜플을 가리키는 iid는 (X1,X2,,X10)(X_1, X_2, \dots, X_{10})으로, 무작위로 선택된 100명에게 물은 동일한 질문에 대한 답으로 구성된 튜플을 가리키는 iid는 (X1,X2,,X100)(X_1, X_2, \dots, X_{100})으로 표기된다.

표본평균의 분포

독립동일분포인 다차원 확률변수 (X1,X2,,Xn)(X_1, X_2, \cdots, X_n)의 평균값이 따르는 분포가 표본평균의 분포다. 즉, 아래와 같이 정의된 확률변수 X\overline{X}의 확률분포를 가리킨다.

X=X1+X2++Xnn\overline{X} = \frac{X_1 + X_2 + \cdots + X_n}{n}

X\overline{X}의 기댓값과 분산은 다음과 같이 계산된다.

  • 표본평균의 기댓값

E[X]=E[X1+X2++Xnn]=E[X1]+E[X2]++E[Xn]n=nμn=μ\begin{align*} E[\overline{X}] &= E \left[ \frac{X_1 + X_2 + \cdots + X_n}{n} \right] \\[2ex] &= \frac{E[X_1] + E[X_2] + \cdots + E[X_n]}{n} \\[1.5ex] &= \frac{n \mu}{n} \\[1ex] &= \mu \end{align*}
  • 표본평균의 분산

V(X)=V(X1+X2++Xnn)=V(X1)+V(X2)++V(Xn)n2=nσ2n2=σ2n\begin{align*} V(\overline{X}) &= V \left( \frac{X_1 + X_2 + \cdots + X_n}{n} \right) \\[2ex] &= \frac{V(X_1) + V(X_2) + \cdots + V(X_n)}{n^2} \\[1.5ex] &= \frac{n \sigma^2}{n^2} \\[1ex] &= \frac{\sigma^2}{n} \end{align*}

28.3.1중심극한정리

확률변수 X1,X2,,XnX_1, X_2, \dots, X_n이 서로 독립이고 모두 기댓값이 μ\mu, 분산이 σ2\sigma^2인 동일한 확률분포를 따른다고 가정하자. 그러면 nn이 충분히 클 때 표본평균 X\overline X의 분포는 다음 정규분포를 따른다.

XN(μ,σ2n)\overline{X} \sim N \left( \mu, \frac{\sigma^2}{n} \right)

예제: 캘리포니아 주택가격 데이터셋

%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'

10% 표본추출

전체 데이터셋의 10%를 표본으로 추출한다. 총 인덱스에서 무작위로 10%의 인덱스를 선택한다.

np.random.seed(17)
total_number = housing.shape[0]
percent = 0.1
sample_size = int(total_number * percent)
random_indices = np.random.choice(total_number, sample_size)

추출된 인덱스를 갖는 샘플만 선택하여 표본으로 지정한다.

sampling = housing.iloc[random_indices]
sampling
Loading...

선택된 표본의 평균값은 다음과 같다.

sampling.mean()
median_income 3.670131 median_house_value 192042.297407 dtype: float64

모평균과 매우 유사하다.

housing.mean()
median_income 3.676717 median_house_value 192477.921017 dtype: float64

표본추출 반복

아래 코드의 X_income10pX_value10p는 각각 캘리포니아 데이터셋에서 무작위로 10%의 샘플을 선택하는 과정을 1만번 반복할 때마다 계산된 중위소득 평균값과 중위주택가격의 평균값을 포함한다.

np.random.seed(17)
sampling_count = 10000

X_income10p = np.zeros(sampling_count)
X_value10p = np.zeros(sampling_count)

total_number = housing.shape[0]
percent = 0.1
sample_size = int(total_number * percent)

for i in range(sampling_count):
    random_indices = np.random.choice(total_number, sample_size)
    sampling = housing.iloc[random_indices]
    X_income10p[i] = sampling['median_income'].mean()
    X_value10p[i] = sampling['median_house_value'].mean()    

두 리스트를 모아 데이터프레임을 생성한다.

X_10p = pd.DataFrame({'중위소득 표본평균':X_income10p, '중위주택가격 표본평균':X_value10p})
X_10p
Loading...

모아진 표본평균 자체로 새로운 분포를 가지며 두 특성의 표본평균의 평균값과 다음과 같다.

X_10p.mean()
중위소득 표본평균 3.676474 중위주택가격 표본평균 192458.915808 dtype: float64

두 특성 모두 모평균과 매우 유사하다.

housing.mean()
median_income 3.676717 median_house_value 192477.921017 dtype: float64

반면에 표본평균의 분산은 모분산을 표본의 크기인 sample_size10로 나눈 값과 유사하다.

X_10p.var(ddof=0)
중위소득 표본평균 1.239007e-03 중위주택가격 표본평균 4.815000e+06 dtype: float64
housing.var(ddof=0) / sample_size
median_income 1.253496e-03 median_house_value 4.853612e+06 dtype: float64

아래 코드는 중위소득 표본평균의 분포가 전체 데이터셋의 중위소득의 평균값을 평균값으로, 모분산을 샘플크기로 나눈 값을 분산으로 갖는 정규분포를 따름을 그래프로 보인다.

from scipy.stats import norm

fig, ax = plt.subplots()

# 구간별 밀도 히스토그램
ax.hist(X_10p['중위소득 표본평균'], bins=50, density=True, label='Sample means')

# 모평균을 평균값으로 ,모분산을 샘플크기로 나눈 값을 분산으로 갖는 정규분포 그래프
mu_income = housing.mean().iloc[0]
sigma_income = np.sqrt((housing.var().iloc[0]) / sample_size)

X10_normal = norm(loc=mu_income, scale=sigma_income)
x = np.linspace(3.55, 3.80, 100)
ax.plot(x, X10_normal.pdf(x), label=r'$N(\mu_{income}, \sigma_{income})$')

ax.set_xlabel('Median income sample mean')
ax.set_ylabel('Density')

ax.legend()

plt.show()
<Figure size 640x480 with 1 Axes>

아래 코드는 중위주택가격 표본평균의 분포가 전체 데이터셋의 중위주택가격의 평균값을 평균값으로, 모분산을 샘플크기로 나눈 값을 분산으로 갖는 정규분포를 따름을 그래프로 보인다.

from scipy.stats import norm

fig, ax = plt.subplots()

# 구간별 밀도 히스토그램
ax.hist(X_10p['중위주택가격 표본평균'], bins=50, density=True, label='Sample means')

# 모평균을 평균값으로 ,모분산을 샘플크기로 나눈 값을 분산으로 갖는 정규분포 그래프
mu_value = housing.mean().iloc[1]
sigma_value = np.sqrt((housing.var().iloc[1]) / sample_size)

Y10_normal = norm(loc=mu_value, scale=sigma_value)
y = np.linspace(185000, 200000, 100)
ax.plot(y, Y10_normal.pdf(y), label=r'$N(\mu_{value}, \sigma_{value})$')

ax.set_xlabel('Median house value sample mean')
ax.set_ylabel('Density')

ax.legend()

plt.show()
<Figure size 640x480 with 1 Axes>

28.3.2큰 수의 법칙

확률변수 X1,X2,,XnX_1, X_2, \dots, X_n이 서로 독립이고 모두 기댓값이 μ\mu, 분산이 σ2\sigma^2인 동일한 확률분포를 따른다고 가정하자. 그러면 nn이 증가할 때 표본평균 X\overline X는 모평균 μ\mu에 수렴한다.

캘리포니아 데이터셋에서 선택한 표본의 크기를 최대 30%까지 키울 때 표본평균의 변화를 살펴본다. 아래 코드는 캘리포니아 데이터셋의 30% 정도 크기의 표본을 무작위로 선택한다.

np.random.seed(1)

total_number = housing.shape[0]
percent = 0.3
sample_size = int(total_number * percent)
random_indices = np.random.choice(total_number, sample_size)
sampling = housing.iloc[random_indices]

아래 코드는 인덱스 i가 1부터 1만까지 변할 때 슬라이싱을 이용하여 두 특성에 대한 i개의 샘플의 평균값을 계산한다. 즉, 누적 평균값을 계산하며 계산된 누적 평균값으로 구성된 데이터프레임을 생성한다.

income_means = np.zeros(sample_size)
value_means = np.zeros(sample_size)

for i in range(sample_size):
    sampling_i = sampling.iloc[:i+1]
    income_means[i] = sampling_i.mean().iloc[0]
    value_means[i] = sampling_i.mean().iloc[1]    

sample_means = pd.DataFrame({'중위소득 표본평균':income_means, '중위주택가격 표본평균':value_means})
sample_means
Loading...

아래 코드는 중위소득 표본평균의 누적 평균이 표본의 크기가 커짐에 따라 중위소득 모평균에 수렴함을 보여준다.

fig = plt.figure(figsize=(6,4 ))
ax = fig.add_subplot(111)

sample_means['중위소득 표본평균'].plot()

# 표본평균의 평균값
mu_population = housing.mean().iloc[0]
ax.hlines(mu_population, 0, sample_size, color='red')

ax.set_xlabel('Sample size')
ax.set_ylabel('Sample mean')

plt.show()
<Figure size 600x400 with 1 Axes>

반면에 아래 코드는 중위주택가격 표본평균의 누적 평균이 표본의 크기가 커짐에 따라 중위주택가격 모평균에 수렴함을 보여준다.

fig = plt.figure(figsize=(6,4 ))
ax = fig.add_subplot(111)

sample_means['중위주택가격 표본평균'].plot(color='tab:green')

# 표본평균의 평균값
mu_population = housing.mean().iloc[1]
ax.hlines(mu_population, 0, sample_size, color='red')

ax.set_xlabel('Sample size')
ax.set_ylabel('Sample mean')

plt.show()
<Figure size 600x400 with 1 Axes>

28.4연습문제