이항분포
개념
한 번의 시행에서 어떤 사건이 일어날 확률이 \(p\)로 일정할 때, 독립적으로 \(n\)번 반복한 시행에서 그 사건이 일어나는 횟수 \(X\)의 분포를 이항분포 \(B(n,\,p)\)라고 합니다.
\[P(X = k) = \binom{n}{k} p^k (1-p)^{n-k}, \qquad E(X) = np, \qquad \sigma = \sqrt{np(1-p)}\]
자유투 성공률이 \(p\)인 선수가 \(n\)번 던질 때의 성공 횟수, \(n\)문항 오지선다를 전부 찍었을 때의 정답 개수가 모두 이항분포입니다.
\(n\)이 충분히 크면 이항분포는 정규분포 \(N(np,\; np(1-p))\)에 가까워집니다. 아래 앱에서 \(n\)을 키우며 막대들이 종 모양 곡선에 붙는 모습을 확인해 보세요.
만지며 배우기
#| '!! 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 로만 그린다
from math import comb, sqrt, floor, ceil, exp, pi
from shiny import App, render, ui
ACCENT = "#2563eb"
INK = "#111827"
MUTED = "#6b7280"
GRID = "#e5e7eb"
def pmf(n, p):
return [comb(n, k) * p**k * (1 - p)**(n - k) for k in range(n + 1)]
def pmf_svg(n, p, probs, mean, sd, approx):
lo = max(0, floor(mean - 4 * sd))
hi = min(n, ceil(mean + 4 * sd))
W, H, ml, mr, mt, mb = 470, 320, 50, 12, 16, 34
pw, ph = W - ml - mr, H - mt - mb
span = max(1, hi - lo)
def X(k):
return ml + ((k - lo) / span) * pw
window = probs[lo:hi + 1]
ymax = max(window)
peak = 1.0 / (sd * sqrt(2 * pi)) if sd > 0 else ymax
if approx:
ymax = max(ymax, peak)
def Y(v):
return mt + ph - (v / ymax) * ph
grid = ""
for t in range(5):
v = ymax * t / 4
yy = Y(v)
grid += (f'<line x1="{ml}" y1="{yy:.1f}" x2="{W-mr}" y2="{yy:.1f}" stroke="{GRID}"/>'
f'<text x="{ml-6}" y="{yy+3:.1f}" font-size="9" fill="{MUTED}" '
f'text-anchor="end">{v:.3f}</text>')
bw = pw / (span + 1) * 0.8
bars = ""
tick = max(1, span // 10)
for k in range(lo, hi + 1):
h = Y(0) - Y(probs[k])
bars += (f'<rect x="{X(k)-bw/2:.1f}" y="{Y(probs[k]):.1f}" width="{bw:.1f}" '
f'height="{h:.1f}" fill="{ACCENT}" opacity="0.85"/>')
if (k - lo) % tick == 0:
bars += (f'<text x="{X(k):.1f}" y="{mt+ph+14:.1f}" font-size="9" '
f'fill="{MUTED}" text-anchor="middle">{k}</text>')
mline = (f'<line x1="{X(mean):.1f}" y1="{mt}" x2="{X(mean):.1f}" y2="{mt+ph}" '
f'stroke="{INK}" stroke-width="0.9" stroke-dasharray="2 3"/>'
f'<text x="{X(mean)+3:.1f}" y="{mt+10}" font-size="9" fill="{INK}">E(X)=np</text>')
curve = ""
if approx and sd > 0:
pts = []
for i in range(121):
xv = lo - 0.5 + (hi + 0.5 - (lo - 0.5)) * i / 120
yv = exp(-((xv - mean) ** 2) / (2 * sd * sd)) / (sd * sqrt(2 * pi))
pts.append((X(xv), Y(yv)))
curve = ('<path d="M' + " L".join(f"{x:.1f},{y:.1f}" for x, y in pts)
+ f'" fill="none" stroke="{INK}" stroke-width="1.5"/>')
return (f'<svg viewBox="0 0 {W} {H}" style="width:100%;height:auto;'
f'font-family:-apple-system,sans-serif;">{grid}{bars}{mline}{curve}'
f'<text x="{ml+pw/2:.0f}" y="{H-4}" font-size="10" fill="{MUTED}" '
f'text-anchor="middle">k</text>'
f'<text x="6" y="{mt+8}" font-size="10" fill="{MUTED}">P(X=k)</text></svg>')
app_ui = ui.page_sidebar(
ui.sidebar(
ui.input_slider("n", "시행 횟수 n", min=1, max=80, value=20, step=1),
ui.input_slider("p", "확률 p", min=0.05, max=0.95, value=0.5, step=0.05),
ui.input_checkbox("approx", "정규분포 근사 곡선 겹쳐 보기", False),
width=240,
),
ui.card(
ui.card_header("이항분포 B(n, p)"),
ui.output_ui("pmf_plot"),
),
ui.card(ui.output_ui("readout")),
fillable=True,
)
def server(input, output, session):
@render.ui
def pmf_plot():
n, p = input.n(), input.p()
probs = pmf(n, p)
mean, sd = n * p, sqrt(n * p * (1 - p))
return ui.HTML(pmf_svg(n, p, probs, mean, sd, input.approx()))
@render.ui
def readout():
n, p = input.n(), input.p()
mean, sd = n * p, sqrt(n * p * (1 - p))
probs = pmf(n, p)
within = sum(probs[max(0, ceil(mean - sd)):floor(mean + sd) + 1])
return ui.HTML(
f'<p class="hs-readline">'
f"E(X) = np = {mean:.1f} · σ = √np(1−p) = {sd:.2f} — "
f"X가 평균 ± σ 안에 있을 확률은 약 {within:.2f}입니다. "
f"n을 키우면 막대가 종 모양에 가까워집니다.</p>"
)
app = App(app_ui, server)
from math import comb, sqrt
n, p = 20, 0.5
# P(X = k), k = 0..n
probs = [comb(n, k) * p**k * (1 - p)**(n - k) for k in range(n + 1)]
E = n * p # 기댓값
sd = sqrt(n * p * (1 - p)) # 표준편차
print(E, sd, sum(probs)) # 확률의 합은 항상 1스스로 확인
힌트문제 1 — p = 0.5일 때와 p = 0.1일 때 분포의 모양은 어떻게 다른가?
\(p = 0.5\)이면 평균 \(np\)를 중심으로 좌우 대칭이고, \(p = 0.1\)이면 왼쪽으로 치우친 비대칭이 됩니다. 다만 \(n\)을 충분히 키우면 어느 쪽이든 다시 종 모양에 가까워집니다.
힌트문제 2 — 정규근사가 잘 맞으려면 n과 p가 어떤 조건이면 좋을까?
경험적으로 \(np \ge 5\), \(n(1-p) \ge 5\) 정도면 근사가 쓸 만합니다. \(p\)가 0이나 1에 가까울수록 더 큰 \(n\)이 필요합니다 — 앱에서 \(p = 0.05\)로 두고 \(n\)을 움직이며 확인해 보세요.