모평균의 신뢰구간
개념
모표준편차 \(\sigma\)를 아는 정규 모집단에서 크기 \(n\)인 표본을 뽑았을 때, 모평균 \(m\)의 신뢰구간은 표본평균 \(\bar{x}\) 주위로 다음과 같이 만듭니다.
\[\bar{x} - z\frac{\sigma}{\sqrt{n}} \;\le\; m \;\le\; \bar{x} + z\frac{\sigma}{\sqrt{n}} \qquad (z = 1.645,\ 1.96,\ 2.576)\]
구간의 폭은 \(2z\dfrac{\sigma}{\sqrt{n}}\)입니다. \(n\)을 4배로 하면 \(\sqrt{n}\)이 2배가 되어 폭이 절반으로 줄어듭니다.
신뢰수준 95%의 뜻에 주의해야 합니다. “이 구간에 \(m\)이 있을 확률이 95%”가 아니라, 같은 방법으로 조사를 반복하면 만들어지는 구간들 중 약 95%가 \(m\)을 포함한다는 뜻입니다. 아래 앱은 (우리만 모르는) 진짜 모평균을 두고 100번 조사를 반복해 이 뜻을 눈으로 보여 줍니다. \(m\)을 놓친 구간이 평균적으로 몇 개쯤 나오는지 세어 보세요.
만지며 배우기
#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
#| standalone: true
#| viewerHeight: 640
# matplotlib·numpy 없이 stdlib + 인라인 SVG 로만 그린다
import random
from math import sqrt
from shiny import App, reactive, render, ui
ACCENT = "#2563eb"
INK = "#111827"
MUTED = "#6b7280"
M, SIGMA = 70, 10 # 진짜 모평균(조사자는 모른다고 가정)
Z = {"90": 1.645, "95": 1.96, "99": 2.576}
def ci_svg(intervals):
W, H, ml, mr, mt, mb = 470, 320, 16, 16, 10, 26
pw, ph = W - ml - mr, H - mt - mb
xmin = min(lo for lo, hi in intervals)
xmax = max(hi for lo, hi in intervals)
pad = (xmax - xmin) * 0.05 or 1.0
xmin, xmax = xmin - pad, xmax + pad
def X(v):
return ml + ((v - xmin) / (xmax - xmin)) * pw
N = len(intervals)
body = ""
for k, (lo, hi) in enumerate(intervals):
y = mt + (k + 0.5) * (ph / N)
hit = lo <= M <= hi
col = "#9ca3af" if hit else ACCENT
w = 1 if hit else 2.2
body += (f'<line x1="{X(lo):.1f}" y1="{y:.1f}" x2="{X(hi):.1f}" '
f'y2="{y:.1f}" stroke="{col}" stroke-width="{w}"/>')
body += (f'<line x1="{X(M):.1f}" y1="{mt}" x2="{X(M):.1f}" y2="{mt+ph}" '
f'stroke="{INK}" stroke-width="1" stroke-dasharray="5 3"/>'
f'<text x="{X(M):.1f}" y="{mt+ph+16:.0f}" font-size="10" fill="{INK}" '
f'text-anchor="middle">m = {M}</text>')
return (f'<svg viewBox="0 0 {W} {H}" style="width:100%;height:auto;'
f'font-family:-apple-system,sans-serif;">{body}</svg>')
app_ui = ui.page_sidebar(
ui.sidebar(
ui.input_slider("n", "표본 크기 n", min=10, max=100, value=25, step=10),
ui.input_select("level", "신뢰수준", {"90": "90%", "95": "95%", "99": "99%"}),
ui.input_action_button("redraw", "새로 100번 조사"),
width=240,
),
ui.card(
ui.card_header("100개의 신뢰구간 — 모평균을 놓친 구간은 파랑"),
ui.output_ui("ci_plot"),
),
ui.card(ui.output_ui("readout")),
fillable=True,
)
def server(input, output, session):
@reactive.calc
def intervals():
input.redraw() # 버튼을 누르면 새로 100번 조사
n = input.n()
z = Z[input.level()]
half = z * SIGMA / sqrt(n)
out = []
for _ in range(100):
xbar = sum(random.gauss(M, SIGMA) for _ in range(n)) / n
out.append((xbar - half, xbar + half))
return out
@render.ui
def ci_plot():
return ui.HTML(ci_svg(intervals()))
@render.ui
def readout():
ivs = intervals()
hits = sum(1 for lo, hi in ivs if lo <= M <= hi)
return ui.HTML(
f'<p class="hs-readline">'
f"이번 100번의 조사 중 <b>{hits}개</b> 구간이 모평균 m을 포함했습니다. "
f"신뢰수준 {input.level()}%의 뜻: 같은 방법으로 반복하면 약 "
f"{input.level()}%의 구간이 모평균을 포함한다 — 특정 구간 하나가 "
f"맞을 확률이 {input.level()}%라는 뜻이 아닙니다. "
f"신뢰수준을 높이거나 n을 줄이면 구간은 넓어집니다.</p>"
)
app = App(app_ui, server)
신뢰구간 계산은 공식 그대로 몇 줄이면 충분합니다.
import random
from math import sqrt
sigma, n, z = 10, 25, 1.96 # σ를 아는 경우, 95% → z = 1.96
sample = [random.gauss(70, sigma) for _ in range(n)] # 조사 한 번
xbar = sum(sample) / n
half = z * sigma / sqrt(n) # 오차의 한계
print(xbar - half, xbar + half) # 95% 신뢰구간 [x̄−1.96σ/√n, x̄+1.96σ/√n]스스로 확인
구간이 넓어지고(\(z\)가 \(1.645 \to 2.576\)), 모평균을 놓치는 파란 구간이 줄어듭니다(평균 10개 → 1개). 더 확실하게 잡으려면 더 넓게 던져야 한다는 맞바꿈 관계입니다.
폭은 \(2z\sigma/\sqrt{n}\)이므로 \(n\)이 4배가 되면 폭은 절반이 됩니다. 95% 기준으로 \(2 \times 1.96 \times 10/\sqrt{25} = 7.84\)에서 \(2 \times 1.96 \times 10/\sqrt{100} = 3.92\)로 줄어듭니다. 정밀도를 2배 높이려면 조사 인원은 4배 필요합니다.