정규분포와 표준화

단원 03 확률분포 · 「확률과 통계」 · 1차시(45분)

개념

키, 시험 점수처럼 평균 근처에 자료가 몰리고 양쪽으로 대칭인 분포를 정규분포 \(N(m,\,\sigma^2)\)로 나타냅니다. 곡선의 위치는 평균 \(m\)이, 퍼진 정도는 표준편차 \(\sigma\)가 정합니다. 곡선 아래 구간의 넓이가 곧 확률입니다.

\[Z = \frac{X - m}{\sigma} \;\sim\; N(0,\,1)\]

수능 표준점수가 바로 표준화입니다. 과목마다 \(m\)\(\sigma\)가 달라도 \(Z\)로 바꾸면 같은 자로 잴 수 있습니다.

어떤 정규분포든 표준화하면 표준정규분포 \(N(0, 1)\)이 되므로, 확률 계산은 표준정규분포표 하나로 충분합니다. 아래 앱에서 \(m\), \(\sigma\)를 움직여 곡선의 변화를 보고, 구간의 넓이가 표준화된 \(Z\) 구간의 넓이와 같음을 확인해 보세요.

만지며 배우기

#| '!! shinylive warning !!': |
#|   shinylive does not work in self-contained HTML documents.
#|   Please set `embed-resources: false` in your metadata.
#| standalone: true
#| viewerHeight: 660

# matplotlib·numpy 없이 stdlib + 인라인 SVG 로만 그린다
from math import erf, sqrt, exp, pi
from shiny import App, render, ui

ACCENT = "#2563eb"
INK = "#111827"
MUTED = "#6b7280"

def pdf(x, m, s):
    return exp(-((x - m) ** 2) / (2 * s * s)) / (s * sqrt(2 * pi))

def cdf(x, m, s):
    return 0.5 * (1 + erf((x - m) / (s * sqrt(2))))

def panel(m, s, a, b, title, xlab):
    W, H, ml, mr, mt, mb = 240, 300, 12, 12, 26, 28
    pw, ph = W - ml - mr, H - mt - mb
    x0, x1 = m - 4 * s, m + 4 * s
    def X(v):
        return ml + ((v - x0) / (x1 - x0)) * pw
    ymax = pdf(m, m, s) * 1.12
    def Y(v):
        return mt + ph - (v / ymax) * ph
    def sample(u0, u1, k):
        return [u0 + (u1 - u0) * i / k for i in range(k + 1)]
    curve = ('M' + " L".join(f"{X(x):.1f},{Y(pdf(x,m,s)):.1f}"
                             for x in sample(x0, x1, 200)))
    aa, bb = max(a, x0), min(b, x1)
    fill = ""
    if bb > aa:
        seg = sample(aa, bb, 120)
        d = (f"M{X(aa):.1f},{Y(0):.1f} "
             + " ".join(f"L{X(x):.1f},{Y(pdf(x,m,s)):.1f}" for x in seg)
             + f" L{X(bb):.1f},{Y(0):.1f} Z")
        fill = f'<path d="{d}" fill="{ACCENT}" opacity="0.18"/>'
    return (
        f'<svg viewBox="0 0 {W} {H}" style="width:100%;height:auto;'
        'font-family:-apple-system,sans-serif;">'
        f'<line x1="{ml}" y1="{Y(0):.1f}" x2="{W-mr}" y2="{Y(0):.1f}" stroke="#9ca3af"/>'
        f'<line x1="{X(m):.1f}" y1="{mt}" x2="{X(m):.1f}" y2="{Y(0):.1f}" '
        f'stroke="#9ca3af" stroke-width="0.7" stroke-dasharray="2 3"/>'
        f'{fill}<path d="{curve}" fill="none" stroke="{INK}" stroke-width="1.5"/>'
        f'<text x="{W/2:.0f}" y="16" font-size="11" fill="{INK}" '
        f'text-anchor="middle">{title}</text>'
        f'<text x="{W-mr:.0f}" y="{Y(0)+16:.0f}" font-size="10" fill="{MUTED}" '
        f'text-anchor="end">{xlab}</text>'
        '</svg>'
    )

app_ui = ui.page_sidebar(
    ui.sidebar(
        ui.input_slider("m", "평균 m", min=40, max=100, value=70, step=1),
        ui.input_slider("s", "표준편차 σ", min=2, max=20, value=10, step=1),
        ui.input_slider("ab", "구간 [a, b]", min=20, max=120, value=(65, 75), step=1),
        ui.input_checkbox("show_z", "표준정규분포 Z 겹쳐 보기", False),
        width=240,
    ),
    ui.card(
        ui.card_header("정규분포 곡선 — 색칠한 넓이가 곧 확률"),
        ui.output_ui("curve"),
    ),
    ui.card(ui.output_ui("readout")),
    fillable=True,
)

def server(input, output, session):
    @render.ui
    def curve():
        m, s = input.m(), input.s()
        a, b = input.ab()
        left = panel(m, s, a, b, f"N({m}, {s}²)", "x")
        if input.show_z():
            za, zb = (a - m) / s, (b - m) / s
            right = panel(0, 1, za, zb, "N(0, 1)", "z")
            return ui.HTML(
                '<div style="display:flex; gap:10px;">'
                f'<div style="flex:1;">{left}</div>'
                f'<div style="flex:1;">{right}</div></div>'
            )
        return ui.HTML(left)

    @render.ui
    def readout():
        m, s = input.m(), input.s()
        a, b = input.ab()
        p = cdf(b, m, s) - cdf(a, m, s)
        za, zb = (a - m) / s, (b - m) / s
        return ui.HTML(
            f'<p class="hs-readline">'
            f"P({a} ≤ X ≤ {b}) = {p:.4f} — 표준화하면 "
            f"P({za:.2f} ≤ Z ≤ {zb:.2f})와 같습니다. "
            f"m과 σ를 바꿔도 z가 같으면 확률(넓이)은 변하지 않습니다.</p>"
        )

app = App(app_ui, server)

정규분포 확률은 표준화와 표준정규분포의 누적확률로 계산합니다.

from math import erf, sqrt

def cdf(z):                       # 표준정규분포 P(Z ≤ z)
    return 0.5 * (1 + erf(z / sqrt(2)))

m, s = 70, 10                     # X ~ N(70, 10²)
a, b = 65, 75

za, zb = (a - m) / s, (b - m) / s # 표준화
print(cdf(zb) - cdf(za))          # P(65 ≤ X ≤ 75) = P(-0.5 ≤ Z ≤ 0.5)

스스로 확인

곡선이 옆으로 두 배 퍼지면서 봉우리는 절반 높이로 낮아집니다. 전체 넓이가 항상 1이어야 하기 때문입니다. 같은 구간 \([a, b]\)의 확률(넓이)이 어떻게 변하는지도 함께 관찰해 보세요.

\(Z = (X - 70)/10\)이므로 \(P(-1 \le Z \le 1) \approx 0.6827\) — “평균 ± 1σ 안에 약 68%”라는 정규분포의 기본 성질입니다. 앱에서 구간을 \([60, 80]\)으로 맞춰 확인해 보세요.