Bootstrap 의 실무 이슈와 추가 사례

Woodward 14.4+14.5 — 회귀·분류·시계열·복잡 자료에서의 부트스트랩

Woodward Ch.14.4 (Practical Issues) 와 14.5 (Further Examples) 를 정리한다. 회귀 분석의 부트스트랩, 분류 모형 평가, 시계열 자료의 block bootstrap, 복잡 자료 (cluster, 가중 표본) 의 부트스트랩 변형을 자세히 다룬다.

Experimentation
Fundamentals
저자

Kwangmin Kim

공개

2026년 05월 08일

1 도입 — 단순 평균을 넘어서

지금까지 부트스트랩의 기본 절차 (단일 표본 평균의 CI) 를 다뤘다. 실무에서는 더 복잡한 자료와 통계량을 다룬다.

이 글은 (a) 회귀 부트스트랩 자세히, (b) 분류 모형 부트스트랩, (c) 시계열 + 클러스터 자료의 부트스트랩 변형을 다룬다.

2 회귀 부트스트랩 — 두 형태 비교

A-BUI7-3 에서 cases vs residual 부트스트랩을 소개. 이 글은 역학 사례디테일 추가.

2.1 Cases Bootstrap

자료점 \((X_i, Y_i)\)쌍으로 추출.

def cases_boot(X, Y, n_boot=5000):
    n = len(Y)
    coefs = []
    for _ in range(n_boot):
        idx = np.random.choice(n, size=n, replace=True)
        X_b, Y_b = X[idx], Y[idx]
        beta = np.linalg.lstsq(np.column_stack([np.ones(n), X_b]), Y_b, rcond=None)[0]
        coefs.append(beta)
    return np.array(coefs)

2.2 Residual Bootstrap

원 모형 적합 → 잔차 부트스트랩.

def residual_boot(X, Y, n_boot=5000):
    n = len(Y)
    X_design = np.column_stack([np.ones(n), X])
    beta_orig = np.linalg.lstsq(X_design, Y, rcond=None)[0]
    Y_hat = X_design @ beta_orig
    residuals = Y - Y_hat

    coefs = []
    for _ in range(n_boot):
        eps_b = np.random.choice(residuals, n, replace=True)
        Y_b = Y_hat + eps_b
        beta = np.linalg.lstsq(X_design, Y_b, rcond=None)[0]
        coefs.append(beta)
    return np.array(coefs)

2.3 Wild Bootstrap

이분산성 (heteroscedasticity) 자료에서 residual bootstrap 부정확. Wild bootstrap 변형:

def wild_boot(X, Y, n_boot=5000):
    n = len(Y)
    X_design = np.column_stack([np.ones(n), X])
    beta_orig = np.linalg.lstsq(X_design, Y, rcond=None)[0]
    residuals = Y - X_design @ beta_orig

    coefs = []
    for _ in range(n_boot):
        # 잔차에 ±1 랜덤 곱 (Rademacher distribution)
        signs = np.random.choice([-1, 1], n)
        Y_b = X_design @ beta_orig + residuals * signs
        beta = np.linalg.lstsq(X_design, Y_b, rcond=None)[0]
        coefs.append(beta)
    return np.array(coefs)

Wild bootstrap 은 각 잔차의 크기 보존 + 부호 무작위화. 이분산성에 robust.

직관 — Wild Bootstrap 의 원리

이분산성 자료: \(X\) 값에 따라 잔차 분산이 변함. 예: 큰 \(X\) 에서 큰 분산.

Residual bootstrap: 잔차를 섞음 → 큰 \(X\) 의 잔차가 작은 \(X\) 자리로 → 분산 구조 깨짐.

Wild bootstrap: 잔차를 그 위치에 유지, 부호만 무작위 → 분산 구조 보존.

따라서 wild bootstrap 이 이분산성 자료에 더 적합.

A/B 테스트 자료에서 처치 그룹의 분산이 대조군과 다른 경우 wild bootstrap 권장.

2.4 회귀 계수 vs 예측의 CI

A-BUI7-3 에서 다뤘듯이 계수 CI예측 CI/PI 가 다르다.

CI 유형 의미 부트스트랩
계수 CI \(\beta_j\) 의 불확실성 \(\hat{\beta}^*_b\) 분포
예측 CI (\(x_0\)) \(E(Y \mid x_0)\) 의 불확실성 \(\hat{y}^*_b(x_0)\) 분포
예측 PI (\(x_0\)) \(Y(x_0)\) 의 분포 \(\hat{y}^*_b + \varepsilon^*_b\) 분포

PI 가 항상 더 넓다 (잔차 변동 추가).

3 분류 모형의 부트스트랩

3.1 ROC AUC CI

분류 모형 평가의 표준 지표. 부트스트랩으로 CI 계산:

import numpy as np
from sklearn.metrics import roc_auc_score

def auc_boot(y_true, y_score, n_boot=5000):
    n = len(y_true)
    aucs = []
    for _ in range(n_boot):
        idx = np.random.choice(n, n, replace=True)
        # 양성/음성이 모두 있어야 AUC 정의 가능
        if len(np.unique(y_true[idx])) > 1:
            aucs.append(roc_auc_score(y_true[idx], y_score[idx]))
    return np.array(aucs)

3.2 Sensitivity, Specificity, PPV, NPV

모든 진단 지표의 CI 를 부트스트랩으로 계산 가능.

def diagnostic_boot(y_true, y_pred, n_boot=5000):
    """진단 지표 4 가지의 부트스트랩"""
    n = len(y_true)
    sens, spec, ppv, npv = [], [], [], []
    for _ in range(n_boot):
        idx = np.random.choice(n, n, replace=True)
        yt, yp = y_true[idx], y_pred[idx]
        tp = np.sum((yt == 1) & (yp == 1))
        tn = np.sum((yt == 0) & (yp == 0))
        fp = np.sum((yt == 0) & (yp == 1))
        fn = np.sum((yt == 1) & (yp == 0))
        if tp + fn > 0:
            sens.append(tp / (tp + fn))
        if tn + fp > 0:
            spec.append(tn / (tn + fp))
        if tp + fp > 0:
            ppv.append(tp / (tp + fp))
        if tn + fn > 0:
            npv.append(tn / (tn + fn))
    return sens, spec, ppv, npv

3.3 Calibration

분류 모형의 예측 확률 정확성 (calibration) 도 부트스트랩으로 측정.

4 시계열 부트스트랩 — Block Bootstrap

4.1 동기

시계열 자료는 자기 상관 존재. 단순 부트스트랩은 자기 상관을 깨뜨림. 결과: 부정확한 SE.

4.2 Block Bootstrap

Block Bootstrap (Künsch 1989)

길이 \(\ell\)연속 블록 단위로 추출.

def block_bootstrap(data, block_size, n_boot=5000):
    n = len(data)
    n_blocks = (n + block_size - 1) // block_size
    boot_samples = []
    for _ in range(n_boot):
        sample = []
        for _ in range(n_blocks):
            start = np.random.randint(0, n - block_size + 1)
            sample.extend(data[start:start + block_size])
        boot_samples.append(np.array(sample[:n]))
    return boot_samples

블록 크기 권장: \(\ell \approx n^{1/3}\).

4.3 Stationary Bootstrap (Politis & Romano 1994)

블록 크기를 무작위 (기하 분포) 로. Block bootstrap 보다 약간 robust.

4.4 A/B 테스트의 시간 효과

A/B 테스트에 요일·시간대 패턴 이 있으면 자기 상관. Block bootstrap 권장.

# 일별 매출 시계열에서 요일 효과
daily_revenue = np.array([...])  # 30 일 자료
block_size = 7  # 1 주일 블록

boot_means = []
for _ in range(5000):
    boot_sample = []
    n_blocks = 30 // block_size + 1
    for _ in range(n_blocks):
        start = np.random.randint(0, 30 - block_size + 1)
        boot_sample.extend(daily_revenue[start:start + block_size])
    boot_means.append(np.mean(boot_sample[:30]))

ci = np.percentile(boot_means, [2.5, 97.5])

이 절차가 요일 효과 보존 + 부트스트랩 추론.

5 클러스터 부트스트랩

5.1 동기

클러스터 자료 (학교, 병원, 사용자 반복 측정) 에서 클러스터 내 상관 존재. 단순 부트스트랩 부정확.

5.2 절차

Cluster Bootstrap

클러스터 단위 로 복원 추출. 각 클러스터 내 자료는 그대로 유지.

def cluster_bootstrap(data, cluster_id, n_boot=5000):
    clusters = np.unique(cluster_id)
    n_clusters = len(clusters)
    boot_samples = []
    for _ in range(n_boot):
        boot_clusters = np.random.choice(clusters, n_clusters, replace=True)
        boot_data = []
        for c in boot_clusters:
            boot_data.extend(data[cluster_id == c])
        boot_samples.append(np.array(boot_data))
    return boot_samples

5.3 A/B 테스트 응용

사용자가 여러 번 노출 되는 자료: 사용자 = 클러스터, 노출 = 클러스터 내 관측.

# 사용자 클러스터 부트스트랩
df = pd.DataFrame({
    'user_id': [...],  # 사용자 ID (반복)
    'revenue': [...]   # 노출별 매출
})

users = df['user_id'].unique()
boot_means = []
for _ in range(5000):
    boot_users = np.random.choice(users, len(users), replace=True)
    boot_data = pd.concat([df[df['user_id'] == u] for u in boot_users])
    boot_means.append(boot_data['revenue'].mean())

이 절차가 사용자 단위 독립성 가정 하의 robust CI.

6 가중 표본의 부트스트랩

6.1 Survey Weights

조사 자료에서 각 응답자에 가중치 부여 (모집단 비례). 부트스트랩에서 가중치 처리:

def weighted_boot(data, weights, n_boot=5000):
    n = len(data)
    weights = weights / weights.sum()
    boot_means = []
    for _ in range(n_boot):
        idx = np.random.choice(n, n, replace=True, p=weights)
        boot_means.append(np.mean(data[idx]))
    return boot_means

또는 Wild Bootstrap with weights 변형 사용.

7 베이즈 Bootstrap

Bayesian Bootstrap (Rubin 1981)

각 자료에 Dirichlet 분포의 가중치 부여. 베이즈 추론과 유사.

def bayesian_boot(data, n_boot=5000):
    n = len(data)
    boot_means = []
    for _ in range(n_boot):
        weights = np.random.dirichlet(np.ones(n))
        boot_means.append(np.sum(weights * data))
    return boot_means

전통 부트스트랩의 베이즈 해석. 거의 같은 결과.

8 부트스트랩 검정 vs CI

부트스트랩 CI 는 추정 도구. 부트스트랩 검정 도 가능 (다음 글 A-WOO14-4).

측면 CI 검정
목적 효과 크기 추정 가설 검정
출력 (low, high) p 값
절차 직접 부트스트랩 \(H_0\) 강제 + 부트스트랩

9 Woodward 의 추가 사례

9.1 Risk Difference 의 CI

코호트 연구에서 RD = \(p_T - p_C\). 부트스트랩으로:

def rd_boot(group_T, group_C, n_boot=5000):
    rds = []
    for _ in range(n_boot):
        T_b = np.random.choice(group_T, len(group_T), replace=True)
        C_b = np.random.choice(group_C, len(group_C), replace=True)
        rds.append(T_b.mean() - C_b.mean())
    return rds

9.2 두 평균의 비율

전통 공식 Fieller 가 복잡. 부트스트랩 단순:

def ratio_boot(group_T, group_C, n_boot=5000):
    ratios = []
    for _ in range(n_boot):
        T_b = np.random.choice(group_T, len(group_T), replace=True)
        C_b = np.random.choice(group_C, len(group_C), replace=True)
        ratios.append(T_b.mean() / C_b.mean())
    return ratios

9.3 Trimmed Mean

20 % trimmed mean (양 극단 10 % 제외 평균) 의 CI:

def trimmed_mean(x, trim=0.1):
    n = len(x)
    sorted_x = np.sort(x)
    return np.mean(sorted_x[int(n*trim):int(n*(1-trim))])

def trimmed_mean_boot(data, n_boot=5000):
    return [trimmed_mean(np.random.choice(data, len(data), replace=True))
            for _ in range(n_boot)]

이상치에 robust 한 평균.

10 실무 권장

10.1 Robustness Check 절차

1. 전통 분석 + CI 계산
2. 부트스트랩 분석 + CI 계산 (여러 방법)
3. 결과 비교
   일관 → 결과 강건
   다름 → 자료 진단 + 부트스트랩 CI 우선

10.2 분야별 표준

분야 부트스트랩 표준
임상 RCT BCa, \(B = 5000\)
역학 BCa or Percentile, \(B = 1000\)
A/B 테스트 Percentile (빠름), \(B = 5000\)
머신러닝 Percentile (큰 자료), \(B = 1000\)
논문 BCa, \(B = 10000\)

11 후속 — Bootstrap 가설 검정과 한계

다음 글 A-WOO14-4 는 부트스트랩 가설 검정 과 부트스트랩의 한계 를 다룬다.

12 관련 주제

선행 지식

후속 주제 (Phase A)

  • A-WOO14-4 Bootstrap 가설 검정 + 한계
  • A-WOO14-5 Permutation Tests

다른 카테고리 연결

Subscribe

Enjoy this blog? Get notified of new posts by email: