부트스트래핑으로 신뢰구간과 불확실성 추정하기
부트스트래핑, 잭나이프, 스무딩 부트스트랩의 선택 기준과 신뢰구간 구성, 운영 시 점검할 계산 전략을 정리합니다.
2026-08-14 · 최초 발행 2025-10-14
표본을 다시 뽑아 불확실성을 계산하는 방법
표본의 분포를 명확히 가정하기 어렵거나 표본이 제한된 상황에서는 통계량 자체의 불확실성을 직접 다루기 쉽지 않다. 부트스트래핑은 현재 표본을 반복해서 재표집하고, 그때마다 계산한 통계량의 분포로 신뢰구간·표준오차·편향을 근사하는 방법이다.
모수적 부트스트랩(Parametric Bootstrap)은 정규나 포아송처럼 특정 분포를 따른다는 모형을 전제로 한다. 먼저 모수를 추정한 뒤, 그 분포에서 모의표본을 생성해 통계량 분포를 구한다.
비모수 부트스트랩(Nonparametric Bootstrap)은 분포 모형을 두지 않는다. 관측치를 복원추출해 새 표본을 만들고 통계량을 반복 계산한다.
Jackknife는 한 번에 관측치 하나를 제외하는 leave-one-out 방식이다. 통계량의 변동을 통해 편향과 분산을 추정하며, 영향도 분석에도 맞는다.
Smoothed Bootstrap은 동일값이 많거나 계단형 분포처럼 불연속성이 두드러질 때 쓴다. 리샘플에 커널 잡음(jitter)을 더하거나 KDE 기반으로 표본을 생성해 매끄러움을 확보한다.
데이터와 통계량에 맞춰 재표집 방식을 고른다
선택의 출발점은 데이터의 성격이다. 분포 가정이 타당한지, 이상치가 있는지, 연속형인지 이산형인지, 0~1처럼 경계가 존재하는지를 함께 봐야 한다.
분위수나 최대치처럼 비분화·비매끄럼 통계량은 BCa, m-out-of-n, Smoothed 방식을 우선 검토할 수 있다. 시계열·패널·클러스터 자료처럼 관측치가 독립적이지 않다면 블록 부트스트랩이나 클러스터 부트스트랩이 필요하다.
IID 자료는 단순 복원추출로 처리할 수 있으며, 재현성을 위해 RNG 시드를 고정한다. 종속 자료에는 Moving Block Bootstrap, Stationary Block Bootstrap, Circular Block을 적용할 수 있다. 블록 길이 l은 l≈n^(1/3) 규칙이나 데이터 구동 최적화로 정한다.
신뢰구간 방식도 통계량의 분포와 계산 예산에 따라 달라진다. Percentile CI는 구현이 간단하지만 왜도에 민감하다. BCa(Bias-Corrected and Accelerated)는 편향과 왜도를 보정하며, Jackknife 기반 가속도(a) 추정이 필요하다. Studentized 또는 Bootstrap-t는 표준오차의 부트스트랩까지 포함하므로 강건하지만 계산량이 늘어난다.
반복 수 B는 일반적인 신뢰구간에 B≥1,000, BCa나 분위수·극값 신뢰구간에는 B≥5,000~10,000이 권장된다. 벡터화와 배치 처리, 난수 스트림 분할을 적용하고, NaN 같은 수치 불안정에 대응하는 필터링도 절차에 포함한다.
입력부터 진단까지 이어지는 흐름
입력에는 데이터 X, 통계량 함수 T(·), 반복 수 B, 선택한 방법(Parametric/Nonparametric/Jackknife/Smoothed), 종속성과 경계 조건이 포함된다.
모수적 방식에서는 모형을 추정하고, 비모수 방식에서는 복원추출한다. 필요하면 Smoothed 방식의 커널 잡음이나 종속 자료용 블록 구성을 더한다. 각 반복에서 통계량을 계산한 뒤 Percentile, BCa, Studentized 방식으로 신뢰구간을 구성한다. 실패한 표본의 Inf·NaN 여부와 수렴 상태를 확인하고, 점추정·표준오차·신뢰구간·커버리지 추정 및 민감도 진단을 보고한다.
NaN이나 Inf 통계량이 나오면 해당 리샘플을 제외하거나 재시도하고 로그를 남긴다. 동일값이 많거나 희귀 구간의 표본 다양성이 부족할 때는 Smoothed, m-out-of-n, BCa를 검토한다. 종속성 가정이 깨졌다면 블록 부트스트랩으로 전환하고 블록 길이를 조정한다. 0-1 자료처럼 경계가 있는 경우에는 반사(reflection)나 로지트 변환을 적용한다.
Python으로 구현하는 재표집 추정
전제: Python 3.10+, numpy>=1.24, scipy>=1.10
import numpy as np
from scipy import stats
rng = np.random.default_rng(42)
def bootstrap_ci(data, stat_func=np.mean, B=5000, ci=0.95, random_state=None):
rng_local = np.random.default_rng(random_state)
x = np.asarray(data)
n = x.size
# 리샘플 인덱스 생성
idx = rng_local.integers(0, n, size=(B, n))
samples = x[idx]
# 통계량 계산 (벡터화 가능 통계량 우선)
stats_b = np.apply_along_axis(stat_func, 1, samples)
theta_hat = stat_func(x)
alpha = 1 - ci
lo, hi = np.quantile(stats_b, [alpha/2, 1 - alpha/2])
se = stats_b.std(ddof=1)
return {"theta_hat": theta_hat, "se": se, "ci": (lo, hi), "boot_stats": stats_b}
def parametric_bootstrap_normal_mean(data, B=5000, ci=0.95, random_state=None):
rng_local = np.random.default_rng(random_state)
x = np.asarray(data)
n = x.size
mu_hat = x.mean()
sigma_hat = x.std(ddof=1)
sims = rng_local.normal(loc=mu_hat, scale=sigma_hat, size=(B, n))
means = sims.mean(axis=1)
alpha = 1 - ci
lo, hi = np.quantile(means, [alpha/2, 1 - alpha/2])
return {"theta_hat": mu_hat, "se": means.std(ddof=1), "ci": (lo, hi)}
def jackknife(data, stat_func=np.mean):
x = np.asarray(data)
n = x.size
theta_hat = stat_func(x)
thetas = np.array([stat_func(np.delete(x, i)) for i in range(n)])
theta_bar = thetas.mean()
# 분산/SE
var = (n - 1) * np.mean((thetas - theta_bar)**2)
se = np.sqrt(var)
# 편향 추정 및 교정
bias = (n - 1) * (theta_bar - theta_hat)
theta_bias_corrected = theta_hat - bias
return {
"theta_hat": theta_hat,
"se": se,
"bias": bias,
"theta_bias_corrected": theta_bias_corrected,
"loo_stats": thetas
}
def smoothed_bootstrap_ci(data, stat_func=np.median, B=5000, ci=0.95, h=None, random_state=None):
# Silverman rule-of-thumb for h (연속형 자료 가정)
rng_local = np.random.default_rng(random_state)
x = np.asarray(data)
n = x.size
std = x.std(ddof=1)
if h is None:
h = 1.06 * std * n ** (-1/5)
idx = rng_local.integers(0, n, size=(B, n))
samples = x[idx] + rng_local.normal(0, h, size=(B, n))
stats_b = np.apply_along_axis(stat_func, 1, samples)
theta_hat = stat_func(x)
alpha = 1 - ci
lo, hi = np.quantile(stats_b, [alpha/2, 1 - alpha/2])
return {"theta_hat": theta_hat, "se": stats_b.std(ddof=1), "ci": (lo, hi), "h": h}
# 사용 예시
x = rng.normal(0, 1, size=200)
print("Nonparametric mean:", bootstrap_ci(x, np.mean))
print("Parametric (Normal) mean:", parametric_bootstrap_normal_mean(x))
print("Jackknife mean:", jackknife(x, np.mean))
print("Smoothed median:", smoothed_bootstrap_ci(x, np.median))
BCa CI에는 Jackknife 기반 가속도(a)와 편향보정(z0) 계산이 필요하므로 arch, scikits-bootstrap 같은 라이브러리 의존을 권장한다. 비율·확률처럼 경계가 있는 자료는 로지트 변환 후 부트스트랩을 적용하고 역변환한다.
각 방법이 감수하는 가정과 계산 비용
| 방법 | 성능(정확도/커버리지) | 확장성(계산) | 일관성(이론적) | 안정성(가정 민감도) | 운영 편의 | 적합 상황 |
|---|---|---|---|---|---|---|
| Nonparametric Bootstrap | 중~고, 왜도에 민감, BCa로 개선 | 높음, 벡터화 용이 | IID, 매끄러운 통계량에서 일치 | 이상치/동일값에 민감 | 매우 높음 | 분포 가정 곤란, 표준 케이스 |
| Parametric Bootstrap | 매우 높음(모형 정합 시) | 매우 높음 | 올바른 모형 하 일치 | 모형 미스펙 민감 | 중간(추정 필요) | 이론 분포 신뢰 가능 |
| Jackknife | 중, 선형 통계량 유리 | 매우 높음(O(n)) | 가분/매끄러운 통계량 | 극값·비매끄럼에 취약 | 높음 | 편향/SE 빠른 근사 |
| Smoothed Bootstrap | 중, 불연속 완화 | 중간(KDE/잡음) | 적절한 밴드폭에서 개선 | h 선택에 민감 | 중간 | 동일값 다수, 분위수/엔트로피 |
운영 환경에서 확인할 제약 조건
대칭성이나 정규 근사가 가능하면 Basic 또는 Studentized를, 왜도가 있거나 소표본이면 BCa를 선택한다. 클러스터 자료는 클러스터 단위로 재표집하고, 블록 길이는 데이터 구동 방식으로 추정한다.
이상치가 있다면 Huber 또는 Trimmed 통계량을 병행할 수 있고, 분위수에는 m-out-of-n을 고려한다. NaN·Inf를 필터링하며 실패율이 >1%이면 B를 늘리거나 통계량을 다시 정의한다. RNG 시드와 스트림을 관리하고, 실행 환경과 버전을 고정해 재현성을 확보한다. 0-1이나 양의 실수처럼 지지집합이 제한된 데이터는 로지트·로그 변환과 역변환을 함께 적용한다.
비정규·소표본에서는 BCa 기준으로 기준 95% CI 대비 2~8%p 커버리지 오차 감소가 가능하며, 데이터에 따라 달라진다. 분위수와 AUC 같은 복잡 통계량에는 비모수 표준오차를 제공한다. 벡터화와 병렬화를 적용하면 10^4~10^5 리샘플을 1~10초 내에 처리할 수 있으며, 이는 단일 노드·NumPy 기준이고 통계량 복잡도에 의존한다.
모형 가정 의존도를 낮추면서 불확실성을 설명 가능한 형태로 보고할 수 있다는 점도 실무적인 장점이다. 실험과 의사결정의 위험 관리, 이상치와 비정규성 아래의 추정 품질을 함께 다룰 수 있다. 일반적인 경우에는 Nonparametric과 BCa를, 분포를 신뢰할 수 있을 때는 Parametric을, 빠른 편향·SE 근사에는 Jackknife를, 동일값과 계단형 분포에는 Smoothed를 선택한다.