1 개요
p-값의 이론과 실무에서 p-값은 “\(H_0\) 하에서 관측값보다 더 극단적인 검정 통계량이 나올 확률”로 정의된다. 이를 계산하려면 귀무 분포(null distribution) 를 알아야 한다.
전통적 방법은 t-분포, \(\chi^2\)-분포, F-분포 등 이론적 귀무 분포를 사용한다. 하지만 다음 상황에서 이론적 분포는 신뢰할 수 없다:
- 표본이 너무 작아 점근 이론이 성립하지 않는다
- 데이터가 정규성을 심하게 위반하거나 극단적 이상치가 있다
- 검정 통계량이 특이하여 이론적 분포 자체가 없다
순열 p-값(permuted p-value) 은 이론적 분포 없이 데이터 자체로 귀무 분포를 근사한다 (James et al., 2021, ISLR 2nd ed., Ch.13.5).
2 핵심 직관: 왜 순열이 귀무 분포를 만드는가
2.1 교환 가능성 (Exchangeability)
두 집단 \(X\), \(Y\) 의 평균이 같은지 검정한다고 하자:
\[H_0: E(X) = E(Y) \quad \text{vs} \quad H_a: E(X) \neq E(Y)\]
\(H_0\) 가 참이고 두 집단의 분포가 동일하다면, “어떤 관측값이 \(X\) 집단이고 어떤 관측값이 \(Y\) 집단인지”라는 레이블은 임의적이다. 레이블을 무작위로 뒤섞어도 검정 통계량의 분포가 바뀌지 않는다.
직관: 두 봉지에서 구슬을 꺼냈을 때 두 봉지의 구슬이 같은 종류라면, 어느 봉지에서 꺼낸 구슬인지를 랜덤하게 재배정해도 무방하다. 레이블이 의미를 잃는다 — 이것이 \(H_0\) 가 참일 때의 상황이다.
반대로 \(H_a\) 가 참이라면(두 집단의 분포가 다르다), 레이블을 뒤섞으면 집단 간 차이가 희석된다. 실제 데이터에서 계산한 검정 통계량은 순열 데이터보다 더 극단적이다 — 이것이 작은 p-값으로 나타난다.
\(H_0\): 두 집단 \(X\), \(Y\) 의 분포가 동일하다면,
\[ (X_1, \ldots, X_{n_X}, Y_1, \ldots, Y_{n_Y}) \overset{d}{=} (\text{어떤 순열도}) \]
즉, \(n_X + n_Y\) 개 관측값의 어떤 순열도 원래 데이터와 동일한 결합 분포를 가진다.
3 순열 p-값의 수식
3.1 검정 통계량
두 집단 검정에서 흔히 사용하는 이표본 t-통계량:
\[ T = \frac{\hat{\mu}_X - \hat{\mu}_Y}{s\sqrt{\frac{1}{n_X} + \frac{1}{n_Y}}} \tag{13.11} \]
여기서 \(s = \sqrt{\dfrac{(n_X-1)s_X^2 + (n_Y-1)s_Y^2}{n_X + n_Y - 2}}\) 는 풀링된 표준편차 추정량이다. 큰 \(|T|\) 가 \(H_a\) 에 대한 증거를 제공한다.
3.2 순열 p-값 공식
원래 데이터에서 계산한 검정 통계량을 \(T\), \(B\) 번 순열에서 계산한 검정 통계량을 \(T^{*1}, \ldots, T^{*B}\) 라 하면:
\[ p\text{-value} = \frac{\sum_{b=1}^{B} \mathbf{1}_{(|T^{*b}| \geq |T|)}}{B} \tag{13.12} \]
즉, 순열 검정 통계량 중 실제 관측값만큼 또는 더 극단적인 것의 비율이다.
수식 해석:
- \(\mathbf{1}_{(|T^{*b}| \geq |T|)}\): \(b\) 번째 순열의 검정 통계량이 원래 통계량보다 극단적이면 1, 아니면 0
- 분자: 극단적인 순열의 수
- 분모 \(B\): 전체 순열 수 (통상 \(B = 10{,}000\))
- 결과: \([0, 1]\) 사이의 값 → 이것이 순열 p-값
4 알고리즘 — 단계별 절차
입력: \(X\) 집단의 \(n_X\) 개 관측값, \(Y\) 집단의 \(n_Y\) 개 관측값, 반복 횟수 \(B\)
원래 데이터에서 검정 통계량 \(T\) 를 계산한다.
\(b = 1, \ldots, B\) 에 대해:
- \(n_X + n_Y\) 개 관측값을 무작위로 섞는다.
처음 \(n_X\) 개를 \(x_1^*, \ldots, x_{n_X}^*\), 나머지 \(n_Y\) 개를 \(y_1^*, \ldots, y_{n_Y}^*\) 로 지정한다.
- \(n_X + n_Y\) 개 관측값을 무작위로 섞는다.
- 섞인 데이터에서 검정 통계량 \(T^{*b}\) 를 계산한다.
순열 p-값 = \(\dfrac{\sum_{b=1}^{B} \mathbf{1}_{(|T^{*b}| \geq |T|)}}{B}\)
왜 \(n_X + n_Y\) 개를 한꺼번에 섞는가?
레이블(“\(X\) 집단인가 \(Y\) 집단인가”)을 무작위로 재배정하는 것이기 때문이다. 순열 후 처음 \(n_X\) 개는 새로운 “\(X\) 집단”, 나머지 \(n_Y\) 개는 새로운 “\(Y\) 집단”이 된다. 이 과정이 \(H_0\) 하에서 데이터가 어떻게 보일지를 시뮬레이션한다.
5 이론적 p-값과 순열 p-값 비교
5.1 언제 동일한가
표본이 크고 데이터가 이론적 가정(정규성, 등분산 등)을 잘 만족하면 두 p-값은 거의 같다.
예: ISLR Khan 데이터셋 11번째 유전자 (James et al., 2021, Ch.13.5) - 검정 통계량: \(T = -2.09\) - 이론적 p-값: \(0.041\) (\(t_{52}\) 분포 사용) - 순열 p-값 (\(B = 10{,}000\)): \(0.042\) - 결론: 두 방법이 거의 동일 — 이론적 분포가 타당한 경우
5.2 언제 다른가
데이터에 극단적 이상치가 있거나 표본이 작아 정규성 가정이 위반되면 크게 달라진다.
예: Khan 데이터셋 877번째 유전자 - 검정 통계량: \(T = -0.57\) - 이론적 p-값: \(0.571\) - 순열 p-값: \(0.673\) - 원인: 877번째 유전자에 극단적 이상치가 하나 있어 분포가 심하게 치우침 - 결론: 이 경우 순열 p-값이 더 신뢰할 수 있다
- 이론적 귀무 분포가 없는 경우: 비표준 검정 통계량 (예: 두 집단의 분산비 이외의 함수)
- 이론적 분포의 가정이 위반된 경우:
- 표본이 너무 작아 CLT가 적용되지 않는다 (\(n < 30\))
- 데이터가 정규성을 심하게 위반한다 (이상치, 치우침)
- 등분산 가정이 의심된다
- 두 방법 결과가 크게 다를 경우: 순열 p-값을 우선 신뢰한다
6 다중 검정으로의 확장 — 순열 FDR
6.1 문제 설정
\(m\) 개 귀무 가설 \(H_{01}, \ldots, H_{0m}\) 을 동시에 검정할 때 (예: 유전체 연구에서 수천 개 유전자), 각 가설의 순열 p-값을 계산하면 거짓 발견율(FDR) 을 추정할 수 있다.
6.2 핵심 아이디어
임계값 \(c\) 를 설정하고 \(|T_j| \geq c\) 인 귀무 가설을 기각한다고 하면:
\[ \widehat{FDR} = \frac{\hat{V}}{R}, \quad \text{where} \quad R = \sum_{j=1}^m \mathbf{1}_{(|T_j| \geq c)} \]
- \(R\): 실제 데이터에서 기각된 귀무 가설의 수 (쉽게 계산)
- \(\hat{V}\): 거짓 양성(false positive)의 기대값 → 순열로 추정
순열 데이터에서는 모든 \(H_{0j}\) 가 참이므로, 순열 데이터에서 임계값을 넘는 통계량의 수가 \(\hat{V}\) 의 추정량이 된다:
\[ \hat{V} = \frac{1}{B}\sum_{b=1}^B \sum_{j=1}^m \mathbf{1}_{(|T_j^{*b}| \geq c)} \]
6.3 풀링 순열 p-값 (ISLR 식 13.14)
\(m\) 개 가설을 동시에 고려하는 풀링 순열 p-값:
\[ p_j = \frac{\sum_{j'=1}^{m}\sum_{b=1}^{B} \mathbf{1}_{(|T_{j'}^{*b}| \geq |T_j|)}}{Bm} \tag{13.14} \]
이것은 단순 순열 p-값(식 13.12)과 달리 모든 \(m\) 개 가설의 순열 통계량을 함께 사용하여 귀무 분포를 추정한다. 추정이 더 안정적이고, 이 p-값에 BH 절차를 적용하면 알고리즘 13.4와 동치가 된다.
왜 풀링이 유리한가? 가설별로 \(B\) 개 순열만 사용하면 귀무 분포 추정이 불안정하다. \(m\) 개 가설 \(\times B\) 번 순열 = \(Bm\) 개 통계량을 한꺼번에 사용하면 분포 추정이 훨씬 안정적이다.
7 적용 조건과 한계
7.1 핵심 가정: 교환 가능성
순열 p-값이 유효하려면 \(H_0\) 하에서 관측값이 교환 가능(exchangeable) 해야 한다. 즉, 두 집단의 분포가 완전히 동일해야 한다.
만약 분산은 다른데 평균만 같다면? ( \(H_0: \mu_X = \mu_Y\) 이지만 \(\sigma_X \neq \sigma_Y\) )
이 경우 레이블을 섞으면 실제 데이터의 이분산성이 희석된다 — 순열 p-값의 귀무 분포가 올바르지 않을 수 있다. Welch t-검정처럼 이분산을 허용하는 검정 통계량을 사용하거나, 순열 방식을 조정해야 한다.
7.2 계산 비용
\(B = 10{,}000\) 번 순열은 간단한 경우 빠르지만, \(m = 10{,}000\) 개 유전자에 대해 각각 \(B\) 번 반복하면 \(10^8\) 번 통계량 계산이 필요하다. 같은 순열 집합을 모든 \(m\) 개 가설에 재사용하는 것이 효율적이다.
7.3 단측 vs 양측
위 공식은 양측 검정 ( \(|T^{*b}| \geq |T|\) ) 기준이다. 단측 검정에서는:
\[ p_{\text{단측}} = \frac{\sum_{b=1}^{B} \mathbf{1}_{(T^{*b} \geq T)}}{B} \]
8 이론적 p-값과의 관계 정리
| 항목 | 이론적 p-값 | 순열 p-값 |
|---|---|---|
| 귀무 분포 | 이론적 (t, \(\chi^2\), F 등) | 데이터에서 직접 근사 |
| 가정 | 정규성, 등분산, 대표본 | 교환 가능성 (\(H_0\) 하) |
| 표본 크기 | 클수록 정확 | 소표본에서도 유효 |
| 계산 비용 | 낮음 (해석적) | 높음 (\(B\) 번 반복) |
| 이상치 | 취약 | 강건 |
| 표준 소프트웨어 | 쉽게 사용 | 직접 구현 필요 |
| 결과 일치 | 가정 충족 시 동일 | 가정 위반 시 더 신뢰 |
9 코드 구현
9.1 Step 1: 순수 Python 구현 (알고리즘 13.3)
import math
import random
def two_sample_t(x: list, y: list) -> float:
"""이표본 t-통계량 (풀링 분산)"""
nx, ny = len(x), len(y)
mu_x = sum(x) / nx
mu_y = sum(y) / ny
s2_x = sum((xi - mu_x)**2 for xi in x) / (nx - 1)
s2_y = sum((yi - mu_y)**2 for yi in y) / (ny - 1)
s_pool = math.sqrt(((nx-1)*s2_x + (ny-1)*s2_y) / (nx + ny - 2))
return (mu_x - mu_y) / (s_pool * math.sqrt(1/nx + 1/ny))
def permuted_pvalue(x: list, y: list, B: int = 10000, seed: int = 42) -> dict:
"""
순열 p-값 계산 (알고리즘 13.3)
H0: E(X) = E(Y) vs Ha: E(X) != E(Y)
"""
random.seed(seed)
T_obs = two_sample_t(x, y)
all_data = x + y
nx = len(x)
count_extreme = 0
for _ in range(B):
shuffled = all_data[:]
random.shuffle(shuffled)
x_perm = shuffled[:nx]
y_perm = shuffled[nx:]
T_perm = two_sample_t(x_perm, y_perm)
if abs(T_perm) >= abs(T_obs):
count_extreme += 1
p_value = count_extreme / B
return {
"T 관측값": T_obs,
"순열 p-값": p_value,
"극단적 순열 수": count_extreme,
"총 순열 수": B,
}
# 예시 1: H0 참에 가까운 경우 (두 집단 평균 비슷)
x_null = [5.1, 4.9, 5.3, 4.8, 5.2, 5.0, 4.7, 5.4]
y_null = [5.0, 5.1, 4.9, 5.2, 5.3, 4.8, 5.1, 4.9]
result_null = permuted_pvalue(x_null, y_null, B=5000)
print("=== H0에 가까운 경우 ===")
for k, v in result_null.items():
print(f" {k}: {v:.4f}" if isinstance(v, float) else f" {k}: {v}")
# 예시 2: H1에 가까운 경우 (두 집단 평균 차이 큼)
x_alt = [5.1, 4.9, 5.3, 4.8, 5.2, 5.0, 4.7, 5.4]
y_alt = [7.0, 7.1, 6.9, 7.2, 7.3, 6.8, 7.1, 6.9]
result_alt = permuted_pvalue(x_alt, y_alt, B=5000)
print("\n=== H1에 가까운 경우 (평균 차이 ~2) ===")
for k, v in result_alt.items():
print(f" {k}: {v:.4f}" if isinstance(v, float) else f" {k}: {v}")9.2 Step 2: numpy/scipy 구현 및 이론적 p-값과 비교
import numpy as np
from scipy import stats
def permuted_vs_theoretical(x: np.ndarray, y: np.ndarray,
B: int = 10000, seed: int = 42) -> None:
"""순열 p-값과 이론적 p-값 비교"""
rng = np.random.default_rng(seed)
# 이론적 p-값 (t-분포 기반)
t_obs, p_theoretical = stats.ttest_ind(x, y, equal_var=True)
# 순열 p-값
all_data = np.concatenate([x, y])
nx = len(x)
count_extreme = 0
for _ in range(B):
shuffled = rng.permutation(all_data)
x_p, y_p = shuffled[:nx], shuffled[nx:]
t_p, _ = stats.ttest_ind(x_p, y_p, equal_var=True)
if abs(t_p) >= abs(t_obs):
count_extreme += 1
p_permuted = count_extreme / B
print(f"n_X={len(x)}, n_Y={len(y)}")
print(f" T 관측값: {t_obs:.4f}")
print(f" 이론적 p-값: {p_theoretical:.4f}")
print(f" 순열 p-값: {p_permuted:.4f}")
print(f" 차이: {abs(p_theoretical - p_permuted):.4f}")
np.random.seed(42)
print("=== 케이스 1: 정규 데이터, 대표본 (이론/순열 거의 같음) ===")
x1 = np.random.normal(5.0, 1.0, 50)
y1 = np.random.normal(5.3, 1.0, 50)
permuted_vs_theoretical(x1, y1)
print("\n=== 케이스 2: 정규 데이터, 소표본 (약간 차이) ===")
x2 = np.random.normal(5.0, 1.0, 8)
y2 = np.random.normal(5.3, 1.0, 8)
permuted_vs_theoretical(x2, y2)
print("\n=== 케이스 3: 이상치 포함, 소표본 (크게 차이) ===")
x3 = np.array([5.1, 4.9, 5.3, 4.8, 5.2, 5.0, 4.7, 50.0]) # 이상치 포함
y3 = np.random.normal(5.0, 1.0, 8)
permuted_vs_theoretical(x3, y3)9.3 Step 3: 다중 검정에서 순열 FDR 추정
import numpy as np
from scipy import stats
def permuted_fdr_estimate(X: np.ndarray, Y: np.ndarray,
threshold_c: float,
B: int = 1000, seed: int = 42) -> dict:
"""
순열 FDR 추정 (알고리즘 13.4)
X: (m, n_X) 행렬 — m개 가설, 각각 n_X개 관측값
Y: (m, n_Y) 행렬 — m개 가설, 각각 n_Y개 관측값
threshold_c: 기각 임계값
"""
rng = np.random.default_rng(seed)
m, nx = X.shape
ny = Y.shape[1]
# 원래 통계량
T_obs = np.array([stats.ttest_ind(X[j], Y[j], equal_var=True)[0] for j in range(m)])
R = np.sum(np.abs(T_obs) >= threshold_c) # 기각된 귀무 가설 수
# 순열로 V 추정
V_estimates = []
for _ in range(B):
V_b = 0
for j in range(m):
all_data = np.concatenate([X[j], Y[j]])
shuffled = rng.permutation(all_data)
x_p, y_p = shuffled[:nx], shuffled[nx:]
t_p, _ = stats.ttest_ind(x_p, y_p, equal_var=True)
if abs(t_p) >= threshold_c:
V_b += 1
V_estimates.append(V_b)
V_hat = np.mean(V_estimates)
fdr_hat = V_hat / R if R > 0 else 0.0
return {
"m (가설 수)": m,
"R (기각된 수)": int(R),
"V_hat (추정 거짓 기각)": round(V_hat, 2),
"FDR 추정": round(fdr_hat, 4),
"임계값 c": threshold_c,
}
# 시뮬레이션: m=100개 가설, 그 중 20%가 실제로 차이 있음
np.random.seed(42)
m, nx, ny = 100, 15, 15
n_true_alt = 20 # 실제 효과 있는 가설 수
X = np.random.normal(0, 1, (m, nx))
Y = np.random.normal(0, 1, (m, ny))
# 처음 n_true_alt 개 가설에 실제 차이 부여
Y[:n_true_alt] += 1.5
print("=== 다중 검정 순열 FDR 추정 ===")
print(f"m={m}개 가설, 실제 효과 있는 것: {n_true_alt}개")
print()
for c in [1.5, 2.0, 2.5]:
result = permuted_fdr_estimate(X, Y, threshold_c=c, B=200)
print(f"임계값 c={c}:")
for k, v in result.items():
if k != "임계값 c":
print(f" {k}: {v}")
print()10 핵심 정리
순열 p-값의 수학적 구조:
\[ p_{\text{perm}} = \frac{\#\{b : |T^{*b}| \geq |T|\}}{B} \approx P_{H_0}(|T| \geq |T_{\text{obs}}|) \]
유효성의 근거: \(H_0\) 하에서 교환 가능성이 성립하면, 순열 통계량 \(T^{*1}, \ldots, T^{*B}\) 는 귀무 분포의 iid 표본으로 볼 수 있다.
| 조건 | 권장 방법 |
|---|---|
| 대표본 + 정규성 + 등분산 | 이론적 t 검정 (빠르고 동등하게 정확) |
| 소표본 또는 이상치 존재 | 순열 p-값 (가정 불필요) |
| 비표준 검정 통계량 | 순열 p-값 (이론적 분포 없음) |
| 다중 검정 + 분포 불확실 | 순열 FDR (알고리즘 13.4) |
11 관련 주제
선행 지식
- p-값의 이론과 실무 — 이론적 p-값의 정의와 Theorem 8.3.27
- 가설검정 개요
- 최강력 검정 — NP 보조정리
관련 개념
- 베이즈 검정 — p-값 대신 사후 확률 사용
후속 주제
- 다중 검정 보정 — Bonferroni, Benjamini-Hochberg, FDR (교재 미확인 — agent 사전학습 기반)
- 부트스트랩 신뢰구간 — 순열과 유사한 재표본 기법
12 참고 문헌
- James, G., Witten, D., Hastie, T., & Tibshirani, R. (2021). An Introduction to Statistical Learning (2nd ed.). Springer. Chapter 13, Sections 13.5.1–13.5.3.
- Good, P. I. (2005). Permutation, Parametric, and Bootstrap Tests of Hypotheses (3rd ed.). Springer. (교재 미확인 — agent 사전학습 기반)
- Efron, B. & Hastie, T. (2016). Computer Age Statistical Inference. Cambridge University Press. (교재 미확인 — agent 사전학습 기반)