조건부확률과 독립

단원 02 경우의 수와 확률 · 「확률과 통계」 · 1차시(45분)

개념

사건 \(B\)가 일어났다는 것을 알 때 사건 \(A\)가 일어날 확률을 조건부확률이라 하고, 다음과 같이 정의합니다.

\[P(A \mid B) = \frac{P(A \cap B)}{P(B)} \quad (P(B) > 0)\]

“비 오는 날”이라는 정보를 알면 “지각”의 확률이 달라지듯, 조건부확률은 새 정보로 확률을 다시 재는 것입니다.

이 정의를 정리하면 두 사건이 함께 일어날 확률을 곱으로 구하는 확률의 곱셈정리가 됩니다.

\[P(A \cap B) = P(B)\,P(A \mid B) = P(A)\,P(B \mid A)\]

카드를 연달아 뽑을 때처럼 앞 사건이 뒤 사건의 확률을 바꾸는 경우, 두 사건이 잇따라 일어날 확률은 이 곱셈정리로 구합니다. 한편 조건 \(B\)를 알아도 \(A\)의 확률이 변하지 않으면, 즉 \(P(A \mid B) = P(A)\)이면 두 사건은 서로 독립이라 하고, 이때 곱셈정리는 \(P(A \cap B) = P(A)\,P(B)\)로 간단해집니다. 그렇지 않으면 서로 종속입니다. 아래 앱은 학생 100명을 정사각형 100칸으로 나타냅니다. 사건 \(A\)는 “아침 운동을 함”, 사건 \(B\)는 “아침밥을 먹음”입니다. \(P(A)\), \(P(B \mid A)\), \(P(B \mid A^c)\)를 움직여 네 영역의 칸 수가 어떻게 변하는지, 그리고 언제 두 사건이 독립이 되는지 살펴보세요.

만지며 배우기

#| '!! 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"

def counts(pa, pba, pbac):
    n_a = round(100 * pa)
    n_ab = min(round(100 * pa * pba), n_a)
    n_acb = min(round(100 * (1 - pa) * pbac), 100 - n_a)
    return n_a, n_ab, n_acb

def grid_svg(n_a, n_ab, n_acb):
    cell, gap, ox, oy = 24, 3, 6, 6
    body = ""
    for i in range(100):
        c, r = i % 10, i // 10
        x, y = ox + c * (cell + gap), oy + r * (cell + gap)
        if i < n_ab:
            fill, stroke = ACCENT, ACCENT
        elif i < n_a:
            fill, stroke = "none", ACCENT
        elif i < n_a + n_acb:
            fill, stroke = "#9ca3af", "#9ca3af"
        else:
            fill, stroke = "none", "#d1d5db"
        body += (f'<rect x="{x}" y="{y}" width="{cell}" height="{cell}" rx="3" '
                 f'fill="{fill}" stroke="{stroke}" stroke-width="1.4"/>')
    gx = ox + 10 * (cell + gap) + 18
    legend = [
        (f"A∩B ({n_ab})", ACCENT, ACCENT),
        (f"A만 ({n_a - n_ab})", "none", ACCENT),
        (f"B만 ({n_acb})", "#9ca3af", "#9ca3af"),
        (f"둘 다 아님 ({100 - n_a - n_acb})", "none", "#d1d5db"),
    ]
    for j, (lab, fill, stroke) in enumerate(legend):
        yy = oy + 8 + j * 30
        body += (f'<rect x="{gx}" y="{yy}" width="18" height="18" rx="3" '
                 f'fill="{fill}" stroke="{stroke}" stroke-width="1.4"/>'
                 f'<text x="{gx+26}" y="{yy+14}" font-size="12" fill="{INK}">{lab}</text>')
    W, H = gx + 170, oy + 10 * (cell + gap)
    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("pa", "P(A) — 아침 운동을 하는 비율",
                        min=0.1, max=0.9, value=0.4, step=0.05),
        ui.input_slider("pba", "P(B|A) — 운동하는 학생 중 아침밥",
                        min=0.0, max=1.0, value=0.8, step=0.05),
        ui.input_slider("pbac", "P(B|Aᶜ) — 운동 안 하는 학생 중 아침밥",
                        min=0.0, max=1.0, value=0.5, step=0.05),
        width=240,
    ),
    ui.card(
        ui.card_header("학생 100명 — A: 아침 운동, B: 아침밥"),
        ui.output_ui("grid"),
    ),
    ui.card(ui.output_ui("readout")),
    fillable=True,
)

def server(input, output, session):
    @render.ui
    def grid():
        n_a, n_ab, n_acb = counts(input.pa(), input.pba(), input.pbac())
        return ui.HTML(grid_svg(n_a, n_ab, n_acb))

    @render.ui
    def readout():
        pa, pba, pbac = input.pa(), input.pba(), input.pbac()
        pb = pa * pba + (1 - pa) * pbac
        if pb == 0:
            return ui.HTML(
                '<p class="hs-readline">P(B) = 0이라 조건부확률 '
                "P(A|B)는 정의되지 않습니다. P(B|A)나 P(B|Aᶜ)를 "
                "0보다 크게 올려 보세요.</p>"
            )
        pab = pa * pba / pb
        if abs(pba - pb) < 0.005:
            indep = ("P(B|A)와 P(B)가 같으므로 두 사건은 서로 "
                     "독립입니다 — 운동 여부가 아침밥 확률을 바꾸지 않습니다.")
        elif pba > pb:
            indep = (f"P(B|A) = {pba:.2f} > P(B) = {pb:.2f}이므로 두 사건은 "
                     "서로 종속입니다 — 운동하는 학생이 아침밥을 더 잘 먹습니다.")
        else:
            indep = (f"P(B|A) = {pba:.2f} < P(B) = {pb:.2f}이므로 두 사건은 "
                     "서로 종속입니다 — 운동하는 학생이 아침밥을 덜 먹습니다.")
        return ui.HTML(
            f'<p class="hs-readline">'
            f"전체에서 아침밥을 먹을 확률은 P(B) = P(A)P(B|A) + P(Aᶜ)P(B|Aᶜ) = "
            f"{pb:.2f}입니다. 아침밥을 먹은 학생이 운동도 했을 조건부확률은 "
            f"P(A|B) = P(A)P(B|A)/P(B) = {pab:.2f}입니다. {indep}</p>"
        )

app = App(app_ui, server)

앱의 핵심 계산은 이 몇 줄이 전부입니다.

pa = 0.4            # P(A): 아침 운동
pba = 0.8           # P(B|A)
pbac = 0.5          # P(B|Aᶜ)

pb = pa * pba + (1 - pa) * pbac   # 전체확률 P(B) = 0.62
pab = pa * pba / pb               # P(A|B) ≈ 0.516 (베이즈)

print(pb, pab)
print("independent" if abs(pba - pb) < 1e-9 else "dependent")

스스로 확인

두 값이 같으면 \(P(B) = P(B \mid A) = P(B \mid A^c)\)가 되어 \(A\)\(B\)서로 독립이 됩니다. 운동을 하든 안 하든 아침밥을 먹을 확률이 같다는 뜻입니다. 격자에서 \(A\) 영역과 \(A^c\) 영역의 색칠 비율이 같아지는 것을 확인해 보세요.

전체확률로 \(P(B) = 0.4 \times 0.8 + 0.6 \times 0.5 = 0.62\), 따라서 \(P(A \mid B) = \dfrac{P(A \cap B)}{P(B)} = \dfrac{0.32}{0.62} \approx 0.52\) 입니다. 아침밥을 먹었다는 정보만으로 운동했을 확률이 \(0.4\)에서 \(0.52\)로 올라갑니다 — 정보가 확률을 갱신한 것입니다.