연속확률변수와 확률분포

단원 03 확률분포 · 「확률과 통계」 · 이산에서 연속으로

개념

주사위 눈처럼 값이 뚝뚝 떨어지는 변수는 이산확률변수였습니다. 반면 대기 시간·키·무게처럼 어떤 구간의 값을 연속적으로 가질 수 있는 변수가 연속확률변수입니다. 연속확률변수는 값이 무수히 많아 한 점의 확률을 따질 수 없으므로, 확률을 넓이로 나타냅니다.

이때 쓰는 함수가 확률밀도함수 \(f(x)\)이고, 다음 두 성질을 만족합니다.

\[f(x)\ge 0,\qquad \int_{-\infty}^{\infty} f(x)\,dx = 1\]

막대그래프의 “높이=확률”이 연속에서는 “넓이=확률”로 바뀝니다. 그래서 \(f(x)\) 자체는 확률이 아니라 밀도이고, 값이 1보다 클 수도 있습니다. 확률은 언제나 구간에 대한 넓이로만 정해집니다.

확률변수 \(X\)가 구간 \([a,\,b]\)의 값을 가질 확률은 그 구간에서 밀도함수 아래의 넓이입니다.

\[\mathrm{P}(a\le X\le b) = \int_{a}^{b} f(x)\,dx\]

아래 앱은 삼각형 모양의 밀도함수를 씁니다. 구간 \([a,\,b]\)를 움직이면 색칠된 넓이가 곧 그 구간의 확률이고, 전체 넓이는 항상 1임을 확인해 보세요.

만지며 배우기

#| '!! 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 shiny import App, render, ui

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

L = 6.0          # X 의 범위: 0 ~ 6
PEAK = 3.0       # 삼각형의 꼭짓점

def density(x):
    # 밑변 L, 넓이 1 인 이등변 삼각형 밀도 (꼭짓점 높이 = 2/L)
    if x < 0 or x > L:
        return 0.0
    h = 2.0 / L
    return h * (x / PEAK) if x < PEAK else h * ((L - x) / (L - PEAK))

def area(a, b, steps=400):
    # 사다리꼴 적분 (stdlib 만으로)
    if b <= a:
        return 0.0
    dx = (b - a) / steps
    s = 0.0
    for i in range(steps):
        x0, x1 = a + i * dx, a + (i + 1) * dx
        s += (density(x0) + density(x1)) * 0.5 * dx
    return s

def dens_svg(a, b):
    W, H, ml, mr, mt, mb = 480, 300, 40, 14, 16, 34
    pw, ph = W - ml - mr, H - mt - mb
    ymax = 2.0 / L * 1.2
    N = 120
    def X(v):
        return ml + (v / L) * pw
    def Y(fv):
        return mt + ph - (fv / ymax) * ph
    # 밀도 곡선 (꼭짓점에서 꺾이므로 0·PEAK·L 만 이어도 충분하지만 촘촘히)
    pts = [(X(L * i / N), Y(density(L * i / N))) for i in range(N + 1)]
    curve = "M" + " L".join(f"{x:.1f},{y:.1f}" for x, y in pts)
    # 색칠 구간 [a,b] 아래 영역
    aa, bb = max(0.0, a), min(L, b)
    fill = ""
    if bb > aa:
        fpts = [(X(aa), Y(0))]
        M = 60
        for i in range(M + 1):
            xv = aa + (bb - aa) * i / M
            fpts.append((X(xv), Y(density(xv))))
        fpts.append((X(bb), Y(0)))
        d = "M" + " L".join(f"{x:.1f},{y:.1f}" for x, y in fpts) + " Z"
        fill = f'<path d="{d}" fill="{ACCENT}" opacity="0.35"/>'
    xaxis = ""
    for t in range(0, int(L) + 1):
        xaxis += (f'<text x="{X(t):.1f}" y="{mt+ph+16:.1f}" font-size="10" '
                  f'fill="{MUTED}" text-anchor="middle">{t}</text>')
    return (
        f'<svg viewBox="0 0 {W} {H}" style="width:100%;height:auto;'
        'font-family:-apple-system,sans-serif;">'
        f'<line x1="{ml}" y1="{mt+ph}" x2="{W-mr}" y2="{mt+ph}" stroke="#9ca3af"/>'
        f'{fill}<path d="{curve}" fill="none" stroke="{INK}" stroke-width="1.8"/>'
        f'{xaxis}'
        f'<text x="{ml+pw/2:.0f}" y="{H-4}" font-size="10" fill="{MUTED}" '
        'text-anchor="middle">x</text>'
        f'<text x="10" y="{mt+8}" font-size="11" fill="{MUTED}">f(x)</text>'
        '</svg>'
    )

app_ui = ui.page_sidebar(
    ui.sidebar(
        ui.input_slider("rng", "구간 [a, b]", min=0.0, max=6.0,
                        value=[2.0, 4.0], step=0.1),
        width=250,
    ),
    ui.card(
        ui.card_header("확률밀도함수 — 색칠한 넓이가 곧 확률입니다"),
        ui.output_ui("dens"),
    ),
    ui.card(ui.output_ui("readout")),
    fillable=True,
)

def server(input, output, session):
    @render.ui
    def dens():
        a, b = input.rng()
        return ui.HTML(dens_svg(a, b))

    @render.ui
    def readout():
        a, b = input.rng()
        prob = area(a, b)
        total = area(0.0, L, steps=800)
        return ui.HTML(
            '<p class="hs-readline">'
            f"P({a:.1f} ≤ X ≤ {b:.1f}) = 색칠한 넓이 ≈ <b>{prob:.3f}</b> 입니다. "
            f"밀도함수 전체 넓이는 {total:.3f} ≈ 1 로, 어떤 구간을 잡아도 확률의 "
            "합은 1을 넘지 않습니다. 구간을 넓히면 넓이가 커지고, a와 b를 붙이면 "
            "넓이가 0에 가까워집니다 — 연속확률변수에서 한 점의 확률이 0인 이유입니다.</p>"
        )

app = App(app_ui, server)

넓이(확률)는 표준 라이브러리만으로 사다리꼴 적분해 구합니다(numpy 불필요).

L, PEAK = 6.0, 3.0
def f(x):
    if x < 0 or x > L: return 0.0
    h = 2.0 / L
    return h * x / PEAK if x < PEAK else h * (L - x) / (L - PEAK)

def area(a, b, steps=400):           # 사다리꼴 적분
    dx = (b - a) / steps
    return sum((f(a+i*dx) + f(a+(i+1)*dx)) * 0.5 * dx for i in range(steps))

print(area(2, 4))                    # P(2 ≤ X ≤ 4) ≈ 0.556
print(area(0, L, 800))               # 전체 넓이 ≈ 1

스스로 확인

정확히 0입니다. 한 점은 폭이 없어 넓이가 0이기 때문입니다. 연속확률변수의 확률은 언제나 구간(넓이)으로만 정해지므로, \(\mathrm{P}(X=3)\)이 아니라 \(\mathrm{P}(2.9\le X\le 3.1)\)처럼 구간으로 물어야 의미가 있습니다.

됩니다. \(f(x)\)는 확률이 아니라 밀도이기 때문입니다. 이 앱의 삼각형은 꼭짓점 높이가 \(\tfrac{2}{6}\approx0.33\)이지만, 범위가 좁은 분포라면 높이가 1을 훌쩍 넘을 수도 있습니다. 제약은 오직 “\(f(x)\ge0\)이고 전체 넓이가 1”이라는 것뿐입니다.