1 도입 — 부트스트랩의 두 가지 결정
부트스트랩 적용 시 두 가지 핵심 결정이 있다.
- 시뮬레이션 표본 수 \(B\): 1000? 10000? 100000?
- 통계량 선택: 평균? 중앙값? 회귀 계수? 비표준 측정?
이 글은 두 결정의 가이드와 실무 권장을 다룬다.
2 시뮬레이션 표본 수 \(B\)
2.1 왜 \(B\) 가 중요한가
부트스트랩은 Monte Carlo 시뮬레이션이다. 즉 유한 시뮬레이션 자체 가 추가 오차 (Monte Carlo error) 를 만든다. \(B\) 가 작으면 같은 자료에 부트스트랩을 두 번 실행해도 다른 CI 가 나옴.
부트스트랩의 유한 반복 횟수 로 인한 추정 변동.
\[ \text{MC error} \propto \frac{1}{\sqrt{B}} \]
따라서 \(B = 100 \to 1000\) 으로 늘리면 MC error 가 약 1/3.16 로 감소. \(B = 1000 \to 10000\) 으로 늘리면 1/3.16 로 추가 감소.
2.2 Buisson 의 권고
“최소 \(B = 1000\), 권장 \(B = 5000 \sim 10000\).” (Buisson 2021, Ch.7)
상황별 권고:
| 상황 | \(B\) |
|---|---|
| 탐색 분석 | 500 ~ 1000 |
| 표준 보고 | 1000 ~ 5000 |
| 정밀 추정 (논문) | 5000 ~ 10000 |
| 매우 정밀 (의사결정 critical) | 10000 ~ 50000 |
| 계산 비용 무한 | 100000+ |
2.3 \(B\) 와 CI 정확도
\(B\) 가 CI 끝의 위치 에 미치는 영향:
| \(B\) | CI 끝 위치의 표준 오차 | 실용적 의미 |
|---|---|---|
| 100 | ± 5 % 변동 | 매우 부정확 |
| 1000 | ± 1.5 % 변동 | 보통 |
| 5000 | ± 0.7 % 변동 | 권장 |
| 10000 | ± 0.5 % 변동 | 정밀 |
| 50000 | ± 0.2 % 변동 | 매우 정밀 |
A/B 테스트의 일반 보고: \(B = 5000\) 충분. 의사결정 critical 시 \(B = 10000\).
\(B\) 를 두 배 로 하면 MC error 가 \(1/\sqrt{2} \approx 0.71\) 배. 즉 30 % 정도 감소.
따라서 \(B\) 를 키우는 비용 대비 효익이 수확 체감. \(B = 1000 \to 10000\) (10 배) 으로 늘려도 정확도는 3.16 배만 향상.
권장 절차: \(B = 1000\) 으로 시작 → CI 가 결정 임계값에 가깝 으면 \(B\) 증가.
A/B 테스트 사례: 매출 효과 CI 가 \((0.005, 0.020)\) 이고 비즈니스 임계값이 \(0.01\) 이면, 경계가 임계값에 걸쳐 있어 \(B\) 증가가 의미. 반대로 CI 가 \((0.05, 0.20)\) 이면 결정이 명확해 \(B\) 늘릴 필요 없음.
2.4 MC error 의 정량화
부트스트랩 자체의 변동을 측정하려면 부트스트랩의 부트스트랩 (이중 부트스트랩) 또는 복수 random seed 비교.
# 단순 — 다른 seed 로 5 번 실행하여 CI 변동 측정
import numpy as np
times = np.array([300, 12, 18, 25, 30, 35, 22, 28, 31, 19])
n = len(times)
cis = []
for seed in range(5):
np.random.seed(seed)
boot_means = np.array([
np.random.choice(times, size=n, replace=True).mean()
for _ in range(1000)
])
cis.append(np.percentile(boot_means, [2.5, 97.5]))
print("CI 변동 (B=1000, 5 seed):")
for i, ci in enumerate(cis):
print(f" Seed {i}: ({ci[0]:.2f}, {ci[1]:.2f})")
print(f" CI lower 변동: {np.std([c[0] for c in cis]):.3f}")
print(f" CI upper 변동: {np.std([c[1] for c in cis]):.3f}")이 코드가 CI 의 안정성 을 직접 보여줌. 변동이 크면 \(B\) 증가.
3 임의 통계량의 부트스트랩
3.1 부트스트랩의 일반성
전통 공식은 특정 통계량 (평균, 분산, 비율 등) 에 한정. 부트스트랩은 어떤 통계량 에도 적용 가능.
임의 통계량 \(\hat{\theta}\) 에 대해:
- 원 표본 \(X\) 에서 복원 추출로 \(X^*\) 생성
- \(X^*\) 에서 \(\hat{\theta}^* = T(X^*)\) 계산
- 1, 2 를 \(B\) 회 반복
- \(B\) 개 \(\hat{\theta}^*\) 의 분포로 추론
여기서 \(T\) 는 어떤 함수도 가능. 평균, 중앙값, 회귀 계수, ROC AUC, 비선형 모형 모수 등.
3.2 사례 1 — 중앙값
평균은 이상치에 민감. 매출 자료처럼 우편향 자료에서 중앙값 이 robust.
median_boot = np.array([
np.median(np.random.choice(times, size=n, replace=True))
for _ in range(B)
])
ci_median = np.percentile(median_boot, [2.5, 97.5])
print(f"중앙값 95 % CI: ({ci_median[0]:.2f}, {ci_median[1]:.2f})")전통 공식 (“중앙값의 표준 오차”) 도 존재하지만 복잡한 가정 필요. 부트스트랩이 직접적.
3.3 사례 2 — 분위수
상위 95 % 분위수의 CI. 예: 매출 분포의 상위 5 % 사용자의 매출.
q95_boot = np.array([
np.quantile(np.random.choice(times, size=n, replace=True), 0.95)
for _ in range(B)
])
ci_q95 = np.percentile(q95_boot, [2.5, 97.5])A/B 테스트의 고매출 사용자 분석에 유용.
3.4 사례 3 — 상관 계수
Pearson 상관 \(r\) 의 CI. 전통 공식 (Fisher’s z 변환) 도 있지만 부트스트랩이 더 일반.
import scipy.stats as stats
# 두 변수 자료 가정
x = np.random.normal(0, 1, 50)
y = 2*x + np.random.normal(0, 1, 50)
corr_boot = []
for _ in range(B):
idx = np.random.choice(50, size=50, replace=True)
r, _ = stats.pearsonr(x[idx], y[idx])
corr_boot.append(r)
ci_corr = np.percentile(corr_boot, [2.5, 97.5])비선형 변환 자료에서 Spearman 상관 으로 대체 가능 (자료를 변경하지 않고).
3.5 사례 4 — 회귀 계수
회귀 계수의 CI. 전통 공식 (OLS 의 SE) 이 오차의 등분산성 가정. 위반 시 부트스트랩 robust.
자세한 내용은 다음 글 A-BUI7-3 에서.
3.6 사례 5 — ROC AUC
분류 모형 평가의 Area Under ROC Curve (AUC). 전통 공식 (Hanley-McNeil) 도 있지만 부트스트랩이 더 정확.
from sklearn.metrics import roc_auc_score
# 가상 자료
y_true = np.random.choice([0, 1], 100)
y_pred = np.random.uniform(0, 1, 100)
auc_boot = []
for _ in range(B):
idx = np.random.choice(100, size=100, replace=True)
auc = roc_auc_score(y_true[idx], y_pred[idx])
auc_boot.append(auc)
ci_auc = np.percentile(auc_boot, [2.5, 97.5])머신러닝 모형 평가의 표준 도구.
3.7 사례 6 — Gini 계수
소득·매출 불평등 측정. 전통 공식 복잡. 부트스트랩이 표준.
def gini(x):
"""Gini coefficient"""
x = np.sort(x)
n = len(x)
return 2 * np.sum((np.arange(1, n+1) - (n+1)/2) * x) / (n * np.sum(x))
gini_boot = np.array([
gini(np.random.choice(income_data, size=n, replace=True))
for _ in range(B)
])
ci_gini = np.percentile(gini_boot, [2.5, 97.5])A/B 테스트의 매출 불평등 분석에 유용.
3.8 사례 7 — Kullback-Leibler Divergence
분포 간 거리 측정. 두 그룹의 분포 차이 정량화.
def kl_divergence(p, q):
"""KL divergence"""
p = p[p > 0]
q = q[q > 0]
return np.sum(p * np.log(p / q))
# 두 그룹의 매출 분포 비교
kl_boot = []
for _ in range(B):
p_sample = np.random.choice(group_a, size=len(group_a), replace=True)
q_sample = np.random.choice(group_b, size=len(group_b), replace=True)
p_hist, _ = np.histogram(p_sample, bins=20, density=True)
q_hist, _ = np.histogram(q_sample, bins=20, density=True)
kl_boot.append(kl_divergence(p_hist + 1e-10, q_hist + 1e-10))
ci_kl = np.percentile(kl_boot, [2.5, 97.5])전통 통계학은 수학적으로 다루기 쉬운 통계량 (평균, 분산) 에 집중. 그러나 비즈니스 의사결정 에 필요한 통계량은 다양:
- 매출 중앙값 (이상치 robust)
- 사용자 상위 5 % 매출 (고가치 사용자 분석)
- Gini 계수 (사용자 매출 불평등)
- ROC AUC (모형 성능)
- RMSE (예측 오차)
이 통계량들의 전통 CI 공식 이 없거나 매우 복잡. 부트스트랩은 동일한 절차 로 이 모두에 적용 가능.
이 일반성 이 부트스트랩의 가장 큰 장점. 통계학자가 새 공식 도출하기 전에 부트스트랩으로 즉시 추론 가능.
4 다중 통계량 동시 부트스트랩
여러 통계량을 동시에 추정 + 상관 구조 보존.
def multi_stat(sample):
return {
'mean': sample.mean(),
'median': np.median(sample),
'q95': np.quantile(sample, 0.95),
'sd': sample.std(ddof=1)
}
results = {k: [] for k in ['mean', 'median', 'q95', 'sd']}
for _ in range(B):
sample = np.random.choice(times, size=n, replace=True)
stats_dict = multi_stat(sample)
for k, v in stats_dict.items():
results[k].append(v)
print("다중 통계량 95 % CI:")
for k, vs in results.items():
ci = np.percentile(vs, [2.5, 97.5])
print(f" {k}: ({ci[0]:.2f}, {ci[1]:.2f})")이 절차로 동일 부트스트랩 표본 에서 여러 통계량 동시 추출. 통계량 간 상관 구조 보존.
5 계산 효율성과 병렬화
5.1 단일 코어 시간
큰 자료 (\(n = 10^6\)) + \(B = 10000\) 의 계산 비용:
- 평균: \(n \cdot B = 10^{10}\) 연산. 단일 코어에서 약 1 분.
- 회귀: 더 많음. \(n^2 \cdot B\) 또는 \(\text{SVD}\) 비용. 단일 코어 수 시간.
5.2 병렬화
부트스트랩은 완벽한 embarrassingly parallel 구조. 각 시뮬레이션 독립.
from joblib import Parallel, delayed
def one_bootstrap(data, n):
sample = np.random.choice(data, size=n, replace=True)
return sample.mean()
# 병렬 부트스트랩
results = Parallel(n_jobs=-1)(
delayed(one_bootstrap)(times, n)
for _ in range(10000)
)8 코어 → 8 배 가속. 클라우드 (AWS, GCP) 에서 수십 코어 사용 시 더 빠름.
5.3 Subsampling — 큰 자료의 가속
매우 큰 자료 (\(n = 10^9\)) 에서 부트스트랩 비용 큼. 대안:
- m-out-of-n bootstrap: 표본 크기 \(m < n\) 으로 추출. 큰 자료에서 정확.
- Subagging: 무작위 부분 표본 + 통계량 합산.
- Bag of Little Bootstraps (BLB): 큰 자료를 청크 분할 + 각 청크 부트스트랩.
A/B 테스트의 빅데이터 분석에 유용.
6 사용 도구
6.1 Python
scipy.stats.bootstrap— 표준 도구arch.bootstrap— 시계열 부트스트랩joblib— 병렬화
6.2 R
boot패키지 — 표준bootstrap패키지 — Efron 의 원조 코드parallel— 병렬화
6.3 Julia
Bootstrap.jl— 매우 빠름
7 부트스트랩의 함정
7.1 함정 1 — 의존성 자료
시계열 또는 클러스터 자료에서 단순 부트스트랩 부적절. 자기상관 무시 → CI 부정확.
해법: Block bootstrap (시계열) 또는 Cluster bootstrap.
7.2 함정 2 — 매우 작은 표본
\(n = 5\) 같은 매우 작은 표본에서 가능한 부트스트랩 표본 종류 한정 (\(n^n = 3125\)). 추정 부정확.
해법: 베이즈 분석 또는 모수 가정 사용.
7.3 함정 3 — 한정된 정보 통계량
자료에 모든 정보 가 없는 통계량 (예: 분포 꼬리) 에서 부트스트랩 부정확.
7.4 함정 4 — Plug-in 추정의 편향
복잡한 통계량 (예: ROC AUC) 의 plug-in 추정 이 편향. BCa 나 jackknife 보정 필요.
8 권장 절차 — 부트스트랩 의사결정
부트스트랩 사용 결정 트리:
↓
표본 크기 적절 (n ≥ 30)?
No → 베이즈 또는 모수 가정 (부트스트랩 한계)
Yes → ↓
자료 의존성 (시계열, 클러스터)?
Yes → Block / Cluster bootstrap
No → 일반 bootstrap
통계량 형태:
평균, 비율, 회귀 → BCa 권장
복잡 (AUC, Gini, KL) → BCa
분위수 → BCa 또는 percentile
극단값 (max, min) → 부트스트랩 부적절
계산 비용:
B 결정: 5000 (표준), 10000 (정밀)
병렬화: joblib, Parallel
9 A/B 테스트의 적용
9.1 사례 — 매출 효과 분석
import numpy as np
# 두 그룹 매출 자료 (가상)
np.random.seed(42)
n_per = 5000
control_revenue = np.random.lognormal(mean=4, sigma=1.0, size=n_per)
treatment_revenue = np.random.lognormal(mean=4.05, sigma=1.0, size=n_per)
# Bootstrap 차이 분석
B = 5000
diff_boot = []
for _ in range(B):
c_sample = np.random.choice(control_revenue, n_per, replace=True)
t_sample = np.random.choice(treatment_revenue, n_per, replace=True)
diff_boot.append(t_sample.mean() - c_sample.mean())
ci = np.percentile(diff_boot, [2.5, 97.5])
print(f"평균 매출 차이 95 % CI: (${ci[0]:.2f}, ${ci[1]:.2f})")
# Median 차이
median_diff_boot = []
for _ in range(B):
c_sample = np.random.choice(control_revenue, n_per, replace=True)
t_sample = np.random.choice(treatment_revenue, n_per, replace=True)
median_diff_boot.append(np.median(t_sample) - np.median(c_sample))
ci_med = np.percentile(median_diff_boot, [2.5, 97.5])
print(f"중앙값 매출 차이 95 % CI: (${ci_med[0]:.2f}, ${ci_med[1]:.2f})")
# 전환율 (lift) — 매출 > 0 비율
prob_diff_boot = []
for _ in range(B):
c_sample = np.random.choice(control_revenue, n_per, replace=True)
t_sample = np.random.choice(treatment_revenue, n_per, replace=True)
prob_diff_boot.append(np.mean(t_sample > 0) - np.mean(c_sample > 0))
ci_prob = np.percentile(prob_diff_boot, [2.5, 97.5])
print(f"전환율 차이 95 % CI: ({ci_prob[0]:.4f}, {ci_prob[1]:.4f})")이 코드가 3 가지 메트릭 (평균·중앙값·전환율) 의 차이 CI 를 동시에 계산. 모두 부트스트랩 일반 절차.
10 후속 — Regression Bootstrap
다음 글 A-BUI7-3 는 회귀 모형의 부트스트랩 과 부트스트랩 사용 결정 트리 를 다룬다.
11 관련 주제
선행 지식
후속 주제 (Phase A)
- A-BUI7-3 Regression Bootstrap
- A-WOO14-2, 3 (Computer-intensive bootstrap 자세히)
다른 카테고리 연결