검정력 분석 — 전통 공식과 Bootstrap 시뮬레이션 (Buisson Ch.8.3)

B.E.A.N. (β·Effect·α·N) 의 의미, Test of Proportions, Bootstrap Power Simulation

Buisson (2021) Ch.8 의 마지막 절을 자세히 정리한다. Power analysis 의 통계 이론 (true/false positive·negative), 전통 공식 (Test of Proportions, ES.h), Bootstrap 시뮬레이션의 5 단계 절차, Power curve 시각화, AirCnC 사례의 단계별 적용, 실험 결과 분석 (logistic regression + 평균 effect) 을 시연한다.

Experimentation
Causal Inference
저자

Kwangmin Kim

공개

2026년 05월 08일

1 정의

정의: Power Analysis

실험 시작 전 표본 크기를 결정하는 절차 (Buisson, 2021, Ch.8).

핵심 입력 (B.E.A.N.):

  • Beta (β): False negative 비율 (= 1 - statistical power)
  • Effect size: 검출하고 싶은 최소 효과 (MDE)
  • Alpha (α): False positive 비율 (statistical significance)
  • Number: Sample size (목표)

이 4 가지가 서로 trade-off. 3 개 결정하면 4 번째 자동.

분석가의 default:

  • α = 0.05 (또는 비즈니스 0.10)
  • 1-β = 0.80 (power)
  • MDE = 비즈니스 결정 (손익분기 또는 most likely)
  • N = 산출
직관 — 4 변수의 trade-off 의 비유

비유: 도서관에서 책 찾기.

  • α (false positive): 잘못된 책 가져갈 확률
  • β (false negative): 진짜 책 못 찾을 확률
  • Effect: 찾는 책의 distinctiveness (얼마나 특이한가)
  • N: 검색 시간 (자원)

Trade-off:

  • 시간 적으면 (작은 N) → 찾기 어려움 (β ↑) 또는 잘못 찾음 (α ↑)
  • 책이 매우 특이 (큰 Effect) → 적은 시간으로 가능
  • 책이 비슷 (작은 Effect) → 많은 시간 필요

분석가가 결정: “얼마의 자원으로 얼마의 정확도?”

→ 이 결정이 sample size.

2 True / False Positive · Negative — 결정의 기본

2.1 결정 매트릭스

4 가지 시나리오
                    1-click 구현?
                  YES        NO
효과 진짜 있음?
   YES       True Pos   False Neg
   NO        False Pos  True Neg
시나리오 의미 비용
True Positive 효과 있음 + 구현 → 매출 ↑ 0 (이익)
True Negative 효과 없음 + 구현 안 함 0 (정상)
False Positive 효과 없는데 구현 → 비용 낭비 구현 비용
False Negative 효과 있는데 구현 안 함 → 기회 손실 잠재 매출

분석가는 두 종류의 잘못된 결정을 모두 줄이려 함.

직관 — 두 잘못의 비대칭

전통 통계 (Neyman-Pearson):

  • α 우선 통제 (5%) — false positive 가 더 위험 가정
  • β 는 부차 (20%) — false negative 는 작게

비즈니스 분석가의 question:

  • “False positive 가 진짜 더 비싼가?”
  • 1-click 사례: 구현 비용 1 억, 잠재 매출 100 억 → false negative 가 더 비쌈

결론:

  • 비즈니스 맥락에 따라 α, β 비율 다름
  • 단순히 0.05, 0.20 사용 안 함
  • 비용 분석 후 적절한 균형

2.2 Sharp Null vs Alternative

두 가지 임계값

전통 통계의 두 hypothesis:

H_0 (Sharp Null): 1-click 효과 = 0 (모든 사람에게 정확히 0)
H_1 (Alternative): 1-click 효과 = 특정 양수 값 (예: 1%)

각 hypothesis 의 false rate:

  • H_0 가 진짜 + reject → False Positive (rate = α = 5%)
  • H_1 가 진짜 + accept H_0 → False Negative (rate = β = 20%)

Power = 1 - β = 80%.

→ “Power 80%” 는 항상 특정 effect size 에 대한 것. 다른 effect size 는 다른 power.

직관 — Power 곡선

같은 실험 (N 고정) 의 power:

효과 크기 Power
0% 5% (= α)
0.5% 50%
1% (target) 80%
2% 99%
5% ~100%

큰 효과는 검출 쉬움. 작은 효과는 어려움.

→ Power 의 1 차원적 보고 (“80%”) 는 misleading. 항상 effect size 명시.

3 Buisson 의 우려와 대안

3.1 80% Power 의 자의성

Buisson 의 비판

“관습적인 80% power 를 그대로 사용하지 마라.”

근거:

  • 80% 는 학계의 historical convention
  • 비즈니스의 비용·이익 구조 반영 안 함
  • 조직의 실험 capacity 무시

분석가의 결정:

  • 큰 비용 + 어려운 실험: 90% (보수적)
  • 작은 비용 + 쉬운 실험: 70% (효율)

Trade-off:

  • Power ↑ → N ↑ → 시간 ↑
  • 1 년에 1 실험 가능한 조직: 90% 정확 권장
  • 1 주에 1 실험 가능한 조직: 70% 빠르게 (12 번 시도)
직관 — Testing Velocity 가 power 결정

조직의 실험 빈도가 적정 power 를 결정:

조직 capacity → 1 년에 N 실험
   ↓
실험당 시간 = 1년 / N
   ↓
N 결정 (시간 제약)
   ↓
Power 결정 (N 의 역계산)

이 logic 이 “70% power” 를 정당화 가능. 학계 80% 가 모든 상황에 맞지 않음.

→ Buisson 의 통찰: 통계 convention 보다 비즈니스 reality.

3.2 α 의 비대칭

Control 의 default 부담

전통 α = 0.05 의 함의:

  • Control 이 default
  • Treatment 가 통계적 유의 (p < 0.05) 일 때만 implement
  • Control 에 “유리한” 비대칭

비대칭이 적절한 경우:

  • 검증된 control (years of data) vs 새 treatment (unverified)
  • Control 의 abandon 비용 큼
  • α = 0.01 로 더 엄격 가능

비대칭이 부적절한 경우:

  • 두 옵션 동등 비교 (마케팅 이메일 A vs B)
  • Control 이 default 일 이유 없음
  • α = 0.10 또는 0.20 으로 완화

→ α 도 비즈니스 맥락 의존. 단순 0.05 아닌.

4 Traditional Power Analysis — Test of Proportions

4.1 공식

Test of Proportions

이항 (binary) outcome 의 두 그룹 비교에 표준 공식.

R (pwr 패키지):

library(pwr)
effect_size <- ES.h(0.1925, 0.1825)  # 19.25% vs 18.25%
pwr.2p.test(
    h = effect_size,
    n = NULL,
    sig.level = 0.05,
    power = 0.8,
    alternative = "greater"
)

Python (statsmodels):

import statsmodels.stats.proportion as ssprop
import statsmodels.stats.power as ssp

effect_size = ssprop.proportion_effectsize(0.1925, 0.1825)
n = ssp.tt_ind_solve_power(
    effect_size=effect_size,
    alpha=0.05,
    nobs1=None,
    alternative="larger",
    power=0.8,
)

출력 (AirCnC 사례):

  • N (그룹별) ≈ 18,800
  • N (전체) ≈ 37,600
직관 — Cohen’s h 의 의미

ES.h(p1, p2) 의 정의:

\[ h = 2 \arcsin\sqrt{p_1} - 2 \arcsin\sqrt{p_2} \]

이 변환의 이유:

  • 이항 비율 차이 (p1 - p2) 는 절대 차이 (5% vs 50% 같은 자릿수에서 다른 의미)
  • \(\arcsin\sqrt{p}\) 변환으로 분산 안정 (모든 p 에서 일정)
  • 통계 검정의 normality 가정에 가까움

해석:

  • h = 0.2: 작은 효과
  • h = 0.5: 중간
  • h = 0.8: 큰 효과

AirCnC: h = 0.026 (매우 작은 효과) → 큰 표본 필요.

4.2 Test of Proportions 의 한계

회귀 vs Test of Proportions

전통 공식의 한계:

  • 단순 두 그룹 비교만 (회귀 covariate 처리 안 함)
  • 정규성 가정 (이항이지만 normal approximation)
  • 분포 가정 위반 시 부정확

회귀 적합 시 추가 정보:

  • Age, Gender 등 covariate
  • Heterogeneous effects
  • 비선형 관계

→ 회귀 기반 power analysis 가 더 정확. 그러나 closed-form 공식 없음. Bootstrap simulation 필요.

직관 — Test of Proportions 의 위치

Buisson 의 권장:

“Test of Proportions = quick first estimate. 시뮬레이션의 시작점.”

Logic:

  • 정확한 sample size 모를 때 (100? 100,000?)
  • Test of Proportions 가 같은 자릿수의 답
  • 시뮬레이션의 시작 N 으로 사용
  • 시뮬레이션에서 정밀화

→ 두 도구 결합 사용. 공식이 quick first guess, 시뮬레이션이 final.

5 Bootstrap Power Simulation — 5 단계

5.1 절차 개요

5 단계
1. Metric function: 분석에서 사용할 통계량 정의
   - 예: 1-click 의 회귀 계수

2. Bootstrap CI function: 한 데이터셋의 신뢰구간 계산
   - 예: 90% CI

3. Decision function: 의사결정 규칙
   - 예: CI 가 0 위면 implement

4. Single simulation: 한 실험 시뮬레이션
   - 가짜 효과 추가 + 분석 + 의사결정

5. Power simulation: N 번 반복 + true positive 비율
   - 예: 200 번 실행 → 90% 가 implement → power = 90%

이 절차가 modern power analysis 의 표준.

5.2 Step 1: Metric Function

import statsmodels.formula.api as smf

def log_reg_fun(dat_df):
    """1-click 의 회귀 계수 추출."""
    model = smf.logit("booked ~ oneclick + age + gender", data=dat_df)
    res = model.fit(disp=0)
    return res.params["oneclick"]
직관 — Wrapper 함수의 의도

이 함수의 역할:

  • 한 데이터셋 → 한 통계량 (1-click 효과)
  • 모듈화: 분석 식 변경 시 한 곳만 수정
  • 재사용: bootstrap 에서 반복 호출

분석가의 default:

  • Metric function 을 항상 분리
  • 본 분석 코드와 power simulation 코드 일치 보장

→ “Same code, different data” 원칙.

5.3 Step 2: Bootstrap CI Function

import numpy as np

def boot_CI_fun(dat_df, metric_fun, B=100, conf_level=0.9):
    """Bootstrap 신뢰구간 계산."""
    N = len(dat_df)
    coeffs = []
    for _ in range(B):
        sim_data = dat_df.sample(n=N, replace=True)
        coeffs.append(metric_fun(sim_data))
    coeffs.sort()
    start = int(B * (1 - conf_level) / 2)
    end = -int(B * (1 - conf_level) / 2)
    return [coeffs[start], coeffs[end]]
직관 — Bootstrap 의 의도

Bootstrap = “데이터를 자기 자신에서 sampling” — 분포 가정 없이 신뢰구간.

전통 신뢰구간:

  • Normal approximation 사용 (\(\hat{\theta} \pm 1.96 \cdot \text{SE}\))
  • 분포 가정 위반 시 부정확

Bootstrap CI:

  • 분포 가정 없음
  • 모든 데이터 형태에 적용
  • 비선형 통계량도 처리

비용:

  • B 번 resampling (보통 100~1000)
  • 시간 ↑

비즈니스 분석에서 default = Bootstrap CI. 안전 + 일반.

5.4 Step 3: Decision Function

def decision_fun(dat_df, metric_fun, B=100, conf_level=0.9):
    """의사결정: CI 가 0 위면 implement."""
    boot_CI = boot_CI_fun(dat_df, metric_fun, B, conf_level)
    return 1 if boot_CI[0] > 0 else 0
직관 — CI vs P-value

전통 의사결정:

  • p < 0.05 → significant → implement

Bootstrap 의사결정:

  • 90% CI 가 0 미포함 → implement

두 결정의 등가성:

  • 90% CI 외 zero = p < 0.10
  • 95% CI 외 zero = p < 0.05

CI 의 장점:

  • 효과 크기 직접 보임 (p-value 는 효과 크기 안 보임)
  • 비즈니스 의미 명확
  • Effect 의 실용성 평가 가능

→ Buisson 권장: p-value 보다 CI 사용.

5.5 Step 4: Single Simulation

def single_sim_fun(dat_df, metric_fun, n_exp, eff_size, B=100, conf_level=0.9):
    """한 실험 시뮬레이션."""
    # 1. Historical 데이터로 baseline 예측 모형
    hist_model = smf.logit("booked ~ age + gender + period", data=dat_df).fit(disp=0)
    sim_data = dat_df.copy()
    sim_data["pred_prob_bkg"] = hist_model.predict()

    # 2. 표본 추출 (실험 표본 크기)
    sim_data = sim_data.sample(n_exp)

    # 3. 무작위 배정
    sim_data["oneclick"] = np.where(
        np.random.uniform(size=n_exp) <= 0.5, 0, 1
    )

    # 4. Treatment 그룹에 효과 추가
    sim_data["pred_prob_bkg"] = np.where(
        sim_data["oneclick"] == 1,
        sim_data["pred_prob_bkg"] + eff_size,
        sim_data["pred_prob_bkg"],
    )

    # 5. Booked 시뮬레이션
    sim_data["booked"] = (
        sim_data["pred_prob_bkg"] >= np.random.uniform(size=n_exp)
    ).astype(int)

    # 6. 의사결정
    return decision_fun(sim_data, metric_fun, B, conf_level)
직관 — 5 단계 시뮬레이션의 의미

각 단계의 의도:

  1. Baseline 모형: 과거 데이터에서 예측 모형 학습 → 실제 patterns 보존
  2. 표본 추출: 실험 표본 크기에 맞게 → 실제 실험 mirror
  3. 배정: 50:50 무작위 → 실제 배정 mirror
  4. 효과 추가: Treatment 에 가짜 효과 추가 → “효과가 진짜라면” 시나리오
  5. Outcome: 확률에 따라 실제 booking 시뮬레이션 → 잡음 포함

이 5 단계가 “효과 X 가 진짜라면 우리가 진짜 잡을까?” 의 답.

5.6 Step 5: Power Simulation

def power_sim_fun(dat_df, metric_fun, n_exp, eff_size, n_sim,
                   B=100, conf_level=0.9):
    """N_sim 번 실험 → power 추정."""
    decisions = []
    for i in range(n_sim):
        decisions.append(
            single_sim_fun(dat_df, metric_fun, n_exp, eff_size, B, conf_level)
        )
    return np.mean(decisions)
직관 — Power Estimation 의 직접성

이 함수가 보고하는 것:

“효과 X = eff_size 가 진짜이고, 표본 N = n_exp 일 때, 우리는 90% 의 경우 implement 결정 내림.”

이게 power 의 정확한 의미. 전통 공식의 추상적 80% 보다 직접적.

또한 이 시뮬레이션이 다음을 검증:

  • 분석 코드의 정확성 (실제 분석 코드 사용)
  • Bootstrap 의 coverage
  • 의사결정 규칙의 작동

Buisson 의 강조:

“Power simulation 은 분석 전체를 사전 검증.”

본 실험 후 분석 시 새로운 코드가 아닌 simulated 코드의 재실행. 일관성 보장.

5.7 Sample Size 결정 절차

Iterative refinement
초기 시도:
   N = 40,000 (test of proportions 출력 기반)
   N_sim = 20 → power 0.9 (대략)
   ↓
정밀화 1:
   N = 30,000, 35,000, 40,000, 45,000, 50,000 시도
   N_sim = 100 → 더 정확
   ↓
정밀화 2:
   N = 35,000, 40,000, 45,000 (좁은 범위)
   N_sim = 200 → 매우 정확
   ↓
결정: N = 40,000 (목표 power 90% 도달)

각 단계의 시간 비용 vs 정확도 trade-off.

분석가의 default: N_sim = 20 으로 시작, 200 까지 확장.

5.8 Power Curve 시각화

효과 크기별 power
import matplotlib.pyplot as plt

effect_sizes = [0.005, 0.0075, 0.01, 0.0125, 0.015, 0.02]
n_exp = 40000
n_sim = 200

powers = []
for eff in effect_sizes:
    p = power_sim_fun(dat_df, log_reg_fun, n_exp, eff, n_sim)
    powers.append(p)

plt.figure(figsize=(8, 5))
plt.plot(effect_sizes, powers, "o-")
plt.axhline(y=0.9, linestyle="--", color="red", label="Target 0.9")
plt.xlabel("Effect Size")
plt.ylabel("Power")
plt.title("Power Curve at N = 40,000")
plt.legend()
plt.savefig("power_curve.png", dpi=80)
plt.show()

해석:

  • effect 0.5%: power 50%
  • effect 1%: power 90% (target)
  • effect 2%: power ~100%
직관 — Power Curve 의 비즈니스 가치

비즈니스 파트너에게:

“Most likely 효과 1% 이면 90% 확률로 검출. 만약 효과가 작아 0.5% 이면 50% 확률로 놓침.”

이 정보가 의사결정 도구:

  • “1% 효과를 정말 기대하는가?”
  • “0.5% 미만 효과는 비즈니스 의미 있는가?”

만약 “Yes, 0.5% 도 의미” → 더 큰 N 필요 (다시 시뮬레이션). 만약 “No, 1% 미만 무시” → 현재 N OK.

→ Power curve 가 정확한 의사결정을 가능하게 함.

5.9 False Positive Rate 검증

직관 — α 검증

Effect size 를 0 으로 설정하고 시뮬레이션:

# False positive rate 측정
alpha_check = power_sim_fun(dat_df, log_reg_fun, 40000, 0.0, n_sim=200)
print(f"False positive rate: {alpha_check:.3f} (target = 0.05)")

진짜 effect 0 일 때:

  • Bootstrap 90% CI 의 5% 가 (false) positive
  • 200 simulation 의 5% (= 10 회) 가 implement 결정

검증:

  • 0.04 ~ 0.06 가까이 → 시스템 OK
  • 0.10 이상 → 시스템 점검 필요 (Bootstrap coverage 문제)

→ 이 self-check 가 power simulation 의 self-validation.

6 실험 결과 분석

6.1 회귀 + Bootstrap CI

분석 코드

본 실험 종료 후:

import statsmodels.formula.api as smf

# 회귀
model = smf.logit("booked ~ age + gender + oneclick", data=exp_data).fit()
print(model.summary())

# Bootstrap CI
ci = boot_CI_fun(exp_data, log_reg_fun, B=1000, conf_level=0.9)
print(f"\n90% Bootstrap CI: [{ci[0]:.4f}, {ci[1]:.4f}]")

# 의사결정
if ci[0] > 0:
    print("\n→ CI 가 0 위. 1-click 구현 권장.")
else:
    print("\n→ CI 가 0 포함. 1-click 구현 보류.")

AirCnC 사례 (가상 결과):

  • 1-click 회귀 계수 = 0.158
  • 90% CI = [0.073, 0.250]
  • → CI > 0 → Implement

이게 분석의 통계적 결과.

6.2 Logistic 계수 → 평균 효과

계수 해석의 함정

Logistic 회귀 계수 0.158 의 의미:

  • 직접 해석 어려움 (log-odds)
  • Odds ratio: \(e^{0.158} = 1.17\) → “1-click 이 booking odds 를 17% 증가”
  • 그러나 booking probability 의 변화는 baseline 에 따라 다름

Buisson 의 권장: 평균 effect 계산.

def diff_prob_fun(dat_df, model):
    """1-click on/off 두 시나리오의 평균 booking 확률 차이."""
    # No-button 시나리오
    no_button = dat_df.copy()
    no_button["oneclick"] = 0
    no_button_pred = model.predict(no_button)

    # Button 시나리오
    button = dat_df.copy()
    button["oneclick"] = 1
    button_pred = model.predict(button)

    return (button_pred - no_button_pred).mean()


avg_effect = diff_prob_fun(exp_data, model)
print(f"평균 효과: {avg_effect:.4f}")

출력 (예상): 0.0071 (= 0.71%p).

해석: “1-click 적용 시 평균 booking 확률 0.71%p 증가.”

직관 — 평균 효과의 비즈니스 가치

“회귀 계수 0.158” vs “평균 효과 +0.71%p”:

  • 비즈니스 파트너는 후자만 이해
  • 의사결정 자료는 후자
  • 회귀 계수는 분석가의 도구

분석가의 보고서:

  • 회귀 계수 + Odds ratio (분석가용)
  • 평균 효과 (비즈니스용)
  • Bootstrap CI 둘 다

→ 청자에 따라 정보 layered.

6.3 실험 결과의 ToC 통합 해석

직관 — 결과 보고 형식

좋은 보고:

“ToC 의 가설 (1-click → 시간 단축 → 예약 완료 ↑) 이 검증됨.

Booking probability 평균 효과: +0.71%p (90% CI [0.45, 0.95]). Logistic 계수: 0.158 (90% CI [0.073, 0.250]). Sample size: 40,000 명 (40 일 실험). Power: 90% (1% 효과 가정).

결과: - MDE (0.5%p) 보다 큰 효과 → 비즈니스 의미 ↑. - 95% CI 가 0 미포함 → 통계적 유의 99% 이상. - Mediator (시간) 도 -2.5 분 단축 확인 → 메커니즘 일치.

권장: Production 적용. Year 1 매출 영향 약 +1%.”

ToC + 통계 + 메커니즘 + 비즈니스 의미 모두.

7 코드 예시 — 종합 power simulation

7.1 통합 함수

import numpy as np
import pandas as pd
import statsmodels.formula.api as smf
import matplotlib.pyplot as plt


class PowerSimulator:
    """Bootstrap 기반 power simulation 통합."""

    def __init__(self, hist_data, formula, treatment_var):
        self.hist_data = hist_data
        self.formula = formula
        self.treatment_var = treatment_var

    def metric(self, dat):
        """Treatment 변수의 회귀 계수."""
        m = smf.logit(self.formula, data=dat).fit(disp=0)
        return m.params[self.treatment_var]

    def boot_ci(self, dat, B=100, conf=0.9):
        """Bootstrap CI."""
        N = len(dat)
        coeffs = []
        for _ in range(B):
            sample = dat.sample(N, replace=True)
            coeffs.append(self.metric(sample))
        coeffs.sort()
        s = int(B * (1 - conf) / 2)
        e = -int(B * (1 - conf) / 2)
        return (coeffs[s], coeffs[e])

    def decision(self, dat, B=100, conf=0.9):
        """CI > 0 → implement."""
        ci = self.boot_ci(dat, B, conf)
        return int(ci[0] > 0)

    def single_sim(self, n_exp, eff_size, baseline_formula):
        """한 실험 시뮬레이션."""
        # Baseline 모형
        m = smf.logit(baseline_formula, data=self.hist_data).fit(disp=0)
        dat = self.hist_data.copy()
        dat["pred_prob"] = m.predict()

        # 표본
        dat = dat.sample(n_exp).reset_index(drop=True)

        # 배정
        dat[self.treatment_var] = (np.random.uniform(0, 1, n_exp) > 0.5).astype(int)

        # Effect 추가
        dat["pred_prob"] = np.where(
            dat[self.treatment_var] == 1,
            dat["pred_prob"] + eff_size,
            dat["pred_prob"],
        )

        # Outcome
        dat["booked"] = (dat["pred_prob"] >= np.random.uniform(0, 1, n_exp)).astype(int)

        return self.decision(dat)

    def power_at(self, n_exp, eff_size, baseline_formula, n_sim=20):
        """Power simulation."""
        decisions = [
            self.single_sim(n_exp, eff_size, baseline_formula)
            for _ in range(n_sim)
        ]
        return np.mean(decisions)


# 가상 historical 데이터
np.random.seed(42)
n_hist = 50000
hist = pd.DataFrame({
    "age": np.random.normal(40, 12, n_hist).clip(18, 80),
    "gender": np.random.choice(["F", "M"], n_hist),
    "period": np.random.choice(range(1, 33), n_hist),
})
# Booked 가 age, gender 의 함수
logit = -2 + (-0.05) * (hist["age"] - 40) + 0.3 * (hist["gender"] == "F")
hist["booked"] = np.random.binomial(1, 1 / (1 + np.exp(-logit)))

# Power simulator
sim = PowerSimulator(hist, "booked ~ oneclick + age + gender", "oneclick")

# Power at N = 40000, effect = 0.01
power = sim.power_at(40000, 0.01, "booked ~ age + gender + period", n_sim=20)
print(f"Power at N=40000, effect=0.01: {power:.2f}")
직관 — 클래스 캡슐화

이 클래스가 분석가에게 주는 것:

  • 데이터 + 모형 입력
  • Power simulation 자동
  • 결과 재현성 (seed 관리)

본 실험 후 분석 시:

  • 같은 클래스의 다른 메서드 사용
  • 코드 일관성 보장

→ Power simulation 의 production-quality 도구.

7.2 Power Curve

# Power curve at N = 40000
effect_sizes = [0.005, 0.0075, 0.01, 0.015, 0.02]
powers = []
for eff in effect_sizes:
    p = sim.power_at(40000, eff, "booked ~ age + gender + period", n_sim=50)
    powers.append(p)

plt.figure(figsize=(8, 5))
plt.plot(effect_sizes, powers, "o-")
plt.axhline(y=0.9, linestyle="--", color="red", label="Target 0.9")
plt.xlabel("Effect Size (absolute probability)")
plt.ylabel("Power")
plt.title("Power Curve at N = 40,000")
plt.grid()
plt.legend()
plt.tight_layout()
plt.savefig("power_curve.png", dpi=80)
plt.show()

print("\n=== Power Curve ===")
for e, p in zip(effect_sizes, powers):
    print(f"  Effect = {e:.4f} → Power = {p:.2f}")
직관 — Curve 의 형태

기대 형태:

1.0 |                            *
0.9 |                       *  *
0.8 |                   *
0.7 |              *
0.5 |          *
0.3 |      *
0.1 |  *
0.05|*___________________________________
    0   0.005  0.01  0.015  0.02

S 형 곡선.

해석:

  • 작은 effect: power 작음 (검출 어려움)
  • 중간 effect: power 빠르게 증가
  • 큰 effect: power saturate (1 가까이)

→ 분석가가 적정 N 결정에 사용.

7.3 False Positive 검증

# Effect = 0 일 때 false positive rate
fpr = sim.power_at(40000, 0.0, "booked ~ age + gender + period", n_sim=100)
print(f"\nFalse positive rate: {fpr:.3f} (target = 0.05)")

if 0.03 < fpr < 0.07:
    print("→ FPR 정상. 시스템 검증 통과.")
else:
    print("→ FPR 비정상. Bootstrap coverage 점검 필요.")
직관 — Self-Check 의 가치

이 검증이 power simulation 의 자기 검증:

  • Bootstrap 의 coverage 가 의도한 90% 와 일치?
  • Effect = 0 일 때 정말 5% false positive?

만약 false positive > 10%:

  • Bootstrap iteration 수 증가 (B = 1000)
  • 또는 분석 모형 점검 (covariate 누락?)

이 self-check 통과 후 본 실험 진행 → 신뢰성 보장.

8 종합 — Sample Size 결정 절차

분석가의 default workflow
1. ToC 명확화 (Ch.8.1)
2. Random assignment 설계 (Ch.8.2)
3. Test of Proportions 으로 quick estimate
   → 자릿수 확보 (예: 10,000 또는 100,000)
4. Bootstrap simulation:
   a. N = quick estimate, n_sim = 20
   b. Power 점검
   c. N 조정 후 반복
   d. n_sim = 200 으로 정밀화
5. Power Curve 그리기 (effect size 별)
6. False Positive 검증 (effect = 0)
7. 비즈니스 파트너 보고 + 의사결정
8. A/A Test 1~2 주
9. 본 실험
10. 분석 (같은 코드, 실제 데이터)

이 10 단계가 power analysis 의 표준.

9 관련 주제

9.1 Ch.8 의 형제 글 (Ch.8 완결)

9.2 후속 챕터

9.5 카테고리 진입점

Subscribe

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