1 도입 — Fisher 의 1935 발견의 일반화
A-MAX2-1 에서 Fisher 의 Lady Tasting Tea 와 randomization test 를 다뤘다. 이 글은 그 절차를 일반화 한 permutation test 를 다룬다.
Permutation test 의 핵심 통찰: 귀무가설이 참이라면 그룹 라벨이 무작위로 배정된 것 과 동등. 따라서 라벨을 무작위로 섞은 자료의 검정 통계량 분포가 영가설 분포.
2 Permutation Test 의 정의
귀무가설 \(H_0\) 하에서 그룹 라벨 (또는 그에 해당하는 변수) 이 교환 가능 (exchangeable) 함을 가정. 라벨을 모든 가능한 방식으로 재배치하여 영가설 분포 직접 구성.
절차:
- 관측 검정 통계량 \(T_{\text{obs}}\) 계산
- 자료의 그룹 라벨을 무작위 셔플
- 셔플된 자료에서 검정 통계량 \(T^*\) 계산
- 2, 3 을 모든 가능한 셔플 또는 \(B\) 회 반복
- \(T^*\) 분포에서 \(T_{\text{obs}}\) 의 극단성 = p 값
2.1 정확 vs Monte Carlo
- 정확 Permutation: 모든 가능한 셔플 (\(\binom{n_1+n_2}{n_1}\) 가지) 열거. 작은 표본에서만 가능.
- Monte Carlo Permutation: \(B\) 개 무작위 셔플만 사용. 큰 표본에 표준.
2.2 사례 — 두 그룹 평균 비교
import numpy as np
def perm_test_two_means(group_A, group_B, n_perm=5000):
obs_diff = group_A.mean() - group_B.mean()
combined = np.concatenate([group_A, group_B])
n_A = len(group_A)
perm_diffs = []
for _ in range(n_perm):
np.random.shuffle(combined)
perm_diffs.append(combined[:n_A].mean() - combined[n_A:].mean())
p_value = np.mean(np.abs(perm_diffs) >= np.abs(obs_diff))
return p_value, perm_diffs이 절차는 분포 가정 X. 교환 가능성 만 가정.
3 교환 가능성 (Exchangeability)
자료 \((X_1, X_2, \ldots, X_n)\) 의 결합 분포가 어떤 순열 에도 불변 하다는 성질.
\[ P(X_1, X_2, \ldots, X_n) = P(X_{\pi(1)}, X_{\pi(2)}, \ldots, X_{\pi(n)}) \]
모든 순열 \(\pi\) 에 대해.
3.1 의미
- 모든 자료점이 같은 분포에서 추출 인 경우
- \(H_0\) 하의 두 그룹 비교: 그룹 A 와 B 가 같은 모집단에서 추출 → 라벨 교환 가능
- 의존성 자료에서 일반적으로 깨짐
3.2 Permutation Test 의 적용 조건
- 관측이 교환 가능
- \(H_0\) 하에서 그룹 라벨이 무관
A/B 테스트의 무작위 배정 자료에서 자연스럽게 충족.
i.i.d. (독립 동일 분포) → 교환 가능. 그러나 역은 아님.
- i.i.d. + 베이즈 모형 → 교환 가능 (de Finetti 정리)
- 시계열 자기 상관 → 교환 가능 X
- 클러스터 자료 → 교환 가능 X (클러스터 단위에서는 가능)
A/B 테스트의 일반 자료는 사용자 단위에서 i.i.d. 가까움 → 교환 가능. Permutation test 적용 OK.
반복 측정 자료에서 사용자 = 클러스터로 봐야 함. 사용자 단위 permutation 또는 cluster permutation 적용.
4 Permutation 의 수학적 정당성
4.1 정확 Permutation
모든 셔플의 비율로 p 값. 정확한 α 보장 (분포 가정 없이).
4.2 Monte Carlo Permutation
\(B\) 회 무작위 셔플. p 값 = 극단 비율 + \(1/(B+1)\) (수정).
이 Plus 1 보정이 작은 p 값의 정확성 향상.
5 두 그룹 비교 — 자세히
5.1 평균 차이 검정
위에서 다룸. Welch t 와 거의 동등.
5.2 중앙값 차이 검정
def perm_median_diff(group_A, group_B, n_perm=5000):
obs_diff = np.median(group_A) - np.median(group_B)
combined = np.concatenate([group_A, group_B])
n_A = len(group_A)
perm_diffs = []
for _ in range(n_perm):
np.random.shuffle(combined)
perm_diffs.append(np.median(combined[:n_A]) - np.median(combined[n_A:]))
return np.mean(np.abs(perm_diffs) >= np.abs(obs_diff))전통 중앙값 차이 검정 공식 X. Permutation 이 표준.
5.3 Mann-Whitney U 검정
순위 기반 비모수 검정. Permutation 의 특수 사례.
def perm_mann_whitney(group_A, group_B, n_perm=5000):
combined = np.concatenate([group_A, group_B])
ranks = np.argsort(np.argsort(combined)) + 1
n_A = len(group_A)
obs_U = np.sum(ranks[:n_A]) - n_A * (n_A + 1) / 2
perm_Us = []
for _ in range(n_perm):
np.random.shuffle(ranks)
perm_Us.append(np.sum(ranks[:n_A]) - n_A * (n_A + 1) / 2)
return np.mean(np.abs(perm_Us - n_A*(n_A+len(group_B)+1)/2) >=
np.abs(obs_U - n_A*(n_A+len(group_B)+1)/2))6 회귀 계수의 Permutation Test
회귀 계수 \(\beta_j\) 의 검정.
6.1 절차
def perm_regression(X, Y, var_idx, n_perm=5000):
n = len(Y)
X_design = np.column_stack([np.ones(n), X])
obs_beta = np.linalg.lstsq(X_design, Y, rcond=None)[0][var_idx + 1]
perm_betas = []
for _ in range(n_perm):
# X 의 var_idx 번 변수를 셔플
X_perm = X.copy()
X_perm[:, var_idx] = np.random.permutation(X[:, var_idx])
X_perm_design = np.column_stack([np.ones(n), X_perm])
beta = np.linalg.lstsq(X_perm_design, Y, rcond=None)[0][var_idx + 1]
perm_betas.append(beta)
return np.mean(np.abs(perm_betas) >= np.abs(obs_beta))이 절차로 분포 가정 없이 회귀 계수 p 값.
6.2 다중 변수 회귀의 한계
다중 변수 회귀에서 한 변수의 마진 효과 를 permutation 으로 검정 시, 다른 변수와의 상관 이 영향. 부분 잔차 (partial residual) 사용 권장.
7 상관 계수 검정
8 Permutation vs Bootstrap
| 측면 | Permutation | Bootstrap |
|---|---|---|
| 추출 | 비복원 (셔플) | 복원 |
| 목적 | 가설 검정 | 추정 |
| 가정 | 교환 가능성 | i.i.d. |
| 정확성 | \(H_0\) 분포 정확 | 점근 |
| 출력 | p 값 | CI, SE |
8.1 사용 시점
- p 값 만 필요 → Permutation
- 효과 크기 + CI 필요 → Bootstrap
- 둘 다 필요 → 둘 다 사용
9 사례 — Permutation 의 정확성
작은 표본에서 전통 t 검정 vs permutation 검정 비교.
import numpy as np
from scipy.stats import ttest_ind
# 매우 작은 표본 + 비대칭 자료
group_A = np.array([1, 2, 3, 4, 5])
group_B = np.array([2, 3, 4, 5, 100]) # 이상치
# t 검정
t_stat, p_t = ttest_ind(group_A, group_B, equal_var=False)
print(f"Welch t: t = {t_stat:.3f}, p = {p_t:.4f}")
# Permutation
n_A = len(group_A)
combined = np.concatenate([group_A, group_B])
obs_diff = group_A.mean() - group_B.mean()
# 정확 permutation (모든 가능한 셔플)
from itertools import combinations
all_perms = []
for idx_A in combinations(range(10), n_A):
A_perm = combined[list(idx_A)]
B_perm = combined[[i for i in range(10) if i not in idx_A]]
all_perms.append(A_perm.mean() - B_perm.mean())
p_exact = np.mean(np.abs(all_perms) >= np.abs(obs_diff))
print(f"정확 Permutation p = {p_exact:.4f}")작은 표본에서 t 검정과 permutation 의 결과가 다를 수 있음. Permutation 이 분포 가정 없이 정확.
10 Genome-Wide Permutation
수백만 SNP 의 동시 검정에서 영가설 분포 직접 추정.
def gwas_permutation(genotypes, phenotype, n_perm=1000):
"""수많은 SNP 의 영가설 분포 시뮬레이션"""
n_snps = genotypes.shape[1]
# 관측 통계량
obs_stats = []
for j in range(n_snps):
corr = np.corrcoef(genotypes[:, j], phenotype)[0, 1]
obs_stats.append(corr**2)
# Permutation 분포 — 표현형 셔플
perm_max_stats = []
for _ in range(n_perm):
phen_perm = np.random.permutation(phenotype)
perm_corrs = [np.corrcoef(genotypes[:, j], phen_perm)[0, 1]**2
for j in range(n_snps)]
perm_max_stats.append(np.max(perm_corrs)) # 최대 통계량
# 각 SNP 의 family-wise p 값
fwer_p = []
for stat in obs_stats:
fwer_p.append(np.mean(np.array(perm_max_stats) >= stat))
return obs_stats, fwer_p이 절차가 family-wise error rate (FWER) 자동 통제. Bonferroni 보다 정확 (변수 간 상관 활용).
11 A/B 테스트의 응용
11.1 시나리오 1 — 두 변형의 매출 차이
import numpy as np
n_per = 1000
control = np.random.lognormal(4, 1.0, n_per)
treatment = np.random.lognormal(4.05, 1.0, n_per)
# Permutation
obs_diff = treatment.mean() - control.mean()
combined = np.concatenate([control, treatment])
n_perm = 5000
perm_diffs = []
for _ in range(n_perm):
np.random.shuffle(combined)
perm_diffs.append(combined[:n_per].mean() - combined[n_per:].mean())
p = np.mean(np.abs(perm_diffs) >= np.abs(obs_diff))
print(f"Permutation p = {p:.4f}")11.2 시나리오 2 — 다중 메트릭 동시
5 개 메트릭의 동시 permutation. 최대 통계량 사용으로 family-wise α 통제.
11.3 시나리오 3 — Subgroup 분석
특정 segment 의 효과를 permutation 으로 검정. 분포 가정 없이.
12 Permutation 의 한계
12.1 한계 1 — 큰 표본 + 정확 permutation 불가
\(n = 100\) 양 그룹: \(\binom{200}{100} \approx 9 \times 10^{58}\) 가지. 정확 permutation 불가능.
해법: Monte Carlo permutation (\(B = 10000\) 충분).
12.2 한계 2 — 의존성 자료
시계열, 클러스터에서 교환 가능성 위반. 단순 permutation 부적절.
해법: Cluster permutation (클러스터 단위 셔플), Block permutation.
12.3 한계 3 — 효과 크기 추정 X
Permutation 은 p 값 만 줌. 효과 크기 + CI 는 부트스트랩 사용.
12.4 한계 4 — 다중 변수 모형의 미묘함
다중 회귀에서 한 변수의 marginal 효과 검정 시, 다른 변수의 영향 처리 어려움.
13 Permutation 검정의 정당성 시뮬레이션
import numpy as np
from scipy.stats import ttest_ind
# H_0 하의 시뮬레이션
np.random.seed(42)
n_simulations = 5000
n_per = 30
p_values_t = []
p_values_perm = []
for _ in range(n_simulations):
# 두 그룹 모두 같은 분포 (H_0 참)
A = np.random.normal(0, 1, n_per)
B = np.random.normal(0, 1, n_per)
# t 검정
_, p_t = ttest_ind(A, B)
p_values_t.append(p_t)
# Permutation
obs_diff = A.mean() - B.mean()
combined = np.concatenate([A, B])
perm_diffs = []
for _ in range(500):
np.random.shuffle(combined)
perm_diffs.append(combined[:n_per].mean() - combined[n_per:].mean())
p_perm = np.mean(np.abs(perm_diffs) >= np.abs(obs_diff))
p_values_perm.append(p_perm)
# Type I error rate (α = 0.05)
print(f"t 검정 Type I error: {np.mean(np.array(p_values_t) < 0.05):.3f}")
print(f"Permutation Type I error: {np.mean(np.array(p_values_perm) < 0.05):.3f}")
# 이상적: 0.05 근처이 시뮬레이션이 Permutation 의 정확한 α 통제 를 직접 검증.
14 후속 — Missing Values
다음 글 A-WOO14-6 는 결측 자료의 처리 (단순 대체 방법) 를 다룬다.
15 관련 주제
선행 지식
후속 주제 (Phase A)
- A-WOO14-6 Missing Values
- A-WOO14-7 Multiple Imputation
다른 카테고리 연결