콘텐츠로 이동

대응자료에 대한 Wilcoxon 부호순위검정

대응 부호검정은 대응차이의 중앙값이 0인지 검정하지만 각 차이가 얼마나 큰지는 무시한다. 차이가 의미 있는 수치 척도로 측정되고 그 분포가 근사적으로 대칭이라면, 대응자료에 대한 Wilcoxon 부호순위검정이 각 절대차이의 부호와 순위를 모두 반영하여 더 강력한 대안을 제공한다. 이는 Wilcoxon 부호순위 절차를 차이 \(D_i = X_i - Y_i\)에 그대로 적용한 것이다.

가정

  1. 쌍 \((X_i, Y_i)\)들이 독립이다.
  2. 각 차이 \(D_i = X_i - Y_i\)가 연속분포에서 나온다.
  3. \(H_0\) 아래에서 \(D_i\)의 분포가 중앙값을 중심으로 대칭이다.

대칭성 요구가 대응 부호검정에 비해 추가되는 핵심 가정이다. 차이가 눈에 띄게 치우쳐 있다면 대응 부호검정이나 대응 순열검정이 더 적절할 수 있다.

가설

\[ H_0 \colon D_i = X_i - Y_i \text{의 중앙값이 0이다} \]
\[ H_a \colon D_i \text{의 중앙값이 0이 아니다} \quad \text{(양측)} \]

단측 대립가설(\(H_a \colon \text{중앙값} > 0\) 또는 \(< 0\))도 마찬가지이다.

절차

1단계. 대응차이 \(D_i = X_i - Y_i\)를 계산한다.

2단계. \(D_i = 0\)인 쌍을 제외한다. 남은 쌍의 개수를 \(n\)이라 하자.

3단계. 절대차이 \(|D_1|, |D_2|, \ldots, |D_n|\)을 작은 값부터 순위를 매기고 동점에는 중간순위를 배정한다.

4단계. 부호순위합을 계산한다.

\[ W^+ = \sum_{\{i : D_i > 0\}} R_i, \qquad W^- = \sum_{\{i : D_i < 0\}} R_i \]

\(W^+ + W^- = n(n+1)/2\)임에 유의하라.

5단계. 양측검정의 검정통계량은 \(T = \min(W^+, W^-)\)이다. 동치로 \(W^+\)를 그 귀무분포와 비교해도 된다.

귀무분포

차이가 대칭인 \(H_0\) 아래에서 모든 부호 배정이 동등하게 가능하다. \(W^+\)의 귀무분포는

\[ \mu_{W^+} = \frac{n(n+1)}{4}, \qquad \sigma_{W^+}^2 = \frac{n(n+1)(2n+1)}{24} \]

을 갖는다. \(n\)이 클 때의 정규근사는

\[ Z = \frac{W^+ - n(n+1)/4}{\sqrt{n(n+1)(2n+1)/24}} \]

이다. 절대차이의 동점 집단이 \(g\)개이고 크기가 \(t_1, \ldots, t_g\)일 때 보정된 분산은

\[ \sigma_{W^+}^2 = \frac{n(n+1)(2n+1)}{24} - \frac{1}{48}\sum_{j=1}^{g}(t_j^3 - t_j) \]

이다.

보기 1. 교육 프로그램과 생산성 점수. 한 회사가 교육 프로그램이 직원의 생산성 점수를 높이는지 검정한다. 직원 12명을 프로그램 전후에 측정했다.

직원 전 (\(Y_i\)) 후 (\(X_i\)) \(D_i = X_i - Y_i\) \(\lvert D_i \rvert\) 순위 부호순위
1 45 52 7 7 5.5 \(+5.5\)
2 38 41 3 3 2 \(+2\)
3 50 48 \(-2\) 2 1 \(-1\)
4 42 49 7 7 5.5 \(+5.5\)
5 55 60 5 5 3.5 \(+3.5\)
6 48 53 5 5 3.5 \(+3.5\)
7 41 50 9 9 8 \(+8\)
8 53 45 \(-8\) 8 7 \(-7\)
9 47 58 11 11 9.5 \(+9.5\)
10 44 55 11 11 9.5 \(+9.5\)
11 50 62 12 12 11 \(+11\)
12 46 59 13 13 12 \(+12\)

순위합 계산:

풀이
\[ W^+ = 5.5 + 2 + 5.5 + 3.5 + 3.5 + 8 + 9.5 + 9.5 + 11 + 12 = 70 \]
\[ W^- = 1 + 7 = 8 \]

검산: \(W^+ + W^- = 70 + 8 = 78 = 12(13)/2\). \(\checkmark\)

검정통계량: \(T = \min(70, 8) = 8\).

정규근사 (\(n = 12\)):

\[ \mu_{W^+} = \frac{12 \times 13}{4} = 39 \]
\[ \sigma_{W^+}^2 = \frac{12 \times 13 \times 25}{24} = 162.5 \]

절대차이에 크기 2인 동점 집단이 세 개 있다(\(|D| = 5\)가 둘, \(|D| = 7\)이 둘, \(|D| = 11\)이 둘). 따라서 \(\sum_j (t_j^3 - t_j) = 3(8 - 2) = 18\)이고

\[ \sigma_{W^+} = \sqrt{162.5 - \frac{18}{48}} = \sqrt{162.125} \approx 12.733 \]
\[ Z = \frac{70 - 39}{12.733} \approx 2.434 \]
\[ p = 2\,\Phi(-2.434) \approx 0.015 \]

\(\alpha = 0.05\)에서 \(H_0\)을 기각한다. 교육 프로그램이 생산성 점수를 개선했다는 유의한 증거가 있다.

정확 \(p\)값

\(n = 12\)에서는 SciPy가 정확검정을 쓸 수 있으며 결과는 \(p = 0.0122\)이다. 정규근사값 \(0.0149\)보다 작다.

import numpy as np
from scipy import stats
D = np.array([7, 3, -2, 7, 5, 5, 9, -8, 11, 11, 12, 13])
print(stats.wilcoxon(D, method='exact').pvalue)   # 0.012207
print(stats.wilcoxon(D, method='approx', correction=False).pvalue)  # 0.014906

출력:

0.01220703125
0.014906162657892297

동점이 있으면 정확 귀무분포가 엄밀하게는 성립하지 않으므로, SciPy의 정확값도 "동점이 없었다면"의 값임에 유의하라.

절대차이를 정렬해 자리와 중간순위를 배정하는 과정

절차의 3단계와 4단계가 실제로 무슨 일을 하는지 한 줄로 펼쳐 보자. 맨 윗줄이 절대차이 \(|D|\)를 작은 것부터 세운 것이고, 파란 칸은 원래 차이가 양수였던 것, 빨간 칸은 음수였던 것이다.

두 번째 줄의 "자리"와 세 번째 줄의 "중간순위"가 다른 지점을 보라. \(|D| = 5\)가 두 개 있어 자리 \(3\)과 \(4\)를 차지하는데, 어느 쪽에 \(3\)을 주고 어느 쪽에 \(4\)를 줄 이유가 없으므로 둘 다 \((3+4)/2 = 3.5\)를 받는다. \(|D| = 7\)인 두 개는 \(5.5\), \(|D| = 11\)인 두 개는 \(9.5\)를 받는다. 이렇게 배정해도 순위의 총합은 \(78 = 12 \cdot 13 / 2\)로 변하지 않는다. \(W^+ = 70\), \(W^- = 8\)이고 합이 정확히 \(78\)인 것이 검산이다.

이 그림에서 부호순위검정이 크기를 쓰는 방식이 얼마나 완곡한지도 드러난다. 가장 큰 차이 \(13\)이 받는 것은 \(13\)이 아니라 순위 \(12\)이고, 음의 차이 \(-8\)이 깎아내리는 것은 \(8\)이 아니라 순위 \(7\)이다. 그래서 \(W^-\)가 \(1 + 7 = 8\)에 그친다. 이 완곡함 덕에 이상치가 통계량을 끌고 가지 못하는 동시에, 순위 \(1\)과 순위 \(12\)를 구별하므로 부호만 세는 것보다는 훨씬 많은 정보를 쓴다. 단측 \(p\)값이 부호검정 \(0.0193\), 부호순위검정 \(0.0061\), 대응 \(t\) 검정 \(0.0028\)로 늘어서는 것이 그 순서이다.

동점 보정은 이 자료에서 거의 하는 일이 없다. 크기 \(2\)인 동점 집단이 셋뿐이라 보정항이 \(\frac{1}{48}\sum (t_j^3 - t_j) = \frac{3 \times 6}{48} = 0.375\)에 불과하여, 분산이 \(162.500\)에서 \(162.125\)로 \(0.2\%\) 줄고 단측 \(p\)값은 \(0.00751\)에서 \(0.00745\)로 바뀔 뿐이다. 보정이 문제가 되는 것은 동점 집단이 크거나 많을 때이며, Likert 척도처럼 값이 몇 개뿐인 자료가 전형적인 경우이다.

왜 대응 부호검정보다 강력한가

같은 자료에서 대응 부호검정은 \(n_+ = 10\), \(n_- = 2\)를 셀 뿐이다. 음의 차이(\(-2\)와 \(-8\))가 양의 차이보다 대체로 작다는 사실을 무시한다. Wilcoxon 부호순위검정은 음의 차이에 낮은 순위(1과 7)를 배정하고 양의 차이가 높은 순위를 쌓아 가게 함으로써 이 사실을 포착한다. 크기를 반영한 이 가중치가 증거를 훨씬 효과적으로 모은다.

ARE 비교

차이가 정규일 때 대응 Wilcoxon 검정의 대응 \(t\) 검정 대비 ARE는 \(3/\pi \approx 0.955\)이다. 대응 부호검정의 ARE는 \(2/\pi \approx 0.637\)에 그친다. 즉 Wilcoxon 검정은 \(t\) 검정의 검정력에 맞추려면 관측값이 약 5% 더 필요한 반면, 부호검정은 약 57% 더 필요하다.

연습문제

연습문제 1. 위 생산성 자료에 네 가지 대응 검정(부호검정, Wilcoxon, 순열검정, \(t\) 검정)을 모두 적용하여 양측 \(p\)값을 비교하고, 순서를 설명하라.

풀이
import numpy as np, itertools
from scipy import stats
D = np.array([7, 3, -2, 7, 5, 5, 9, -8, 11, 11, 12, 13])

print(stats.binomtest(10, 12).pvalue)                      # 0.03857
print(stats.wilcoxon(D, method='exact').pvalue)            # 0.01221
print(stats.ttest_1samp(D, 0).pvalue)                      # 0.00569

a = np.abs(D); T = D.sum()
allT = np.array([np.dot(s, a) for s in itertools.product([-1, 1], repeat=12)])
print((np.abs(allT) >= abs(T)).mean())                     # 0.01074

출력:

0.03857421875
0.01220703125
0.00569322392504434
0.0107421875
검정 양측 \(p\)값
대응 부호검정 \(0.0386\)
대응 Wilcoxon (정확) \(0.0122\)
대응 순열검정 \(0.0107\)
대응 \(t\) \(0.0057\)

사용하는 정보량 순서와 정확히 일치한다. 부호만 → 부호+순위 → 부호+원값 → 정규모형까지 가정. 각 단계마다 \(p\)값이 작아진다.

이 자료는 차이가 대칭에 가깝고 이상치가 없어 \(t\) 검정에 유리한 조건이다. \(-8\)이라는 비교적 큰 음의 차이가 하나 있지만 \(+13\), \(+12\), \(+11\), \(+11\)이 이를 압도한다.

순열검정(\(0.0107\))이 Wilcoxon(\(0.0122\))보다 조금 작다. 순열검정이 원값을 그대로 쓰므로 \(+13\)과 \(+11\)의 차이를 순위 \(12\)와 \(9.5\)의 차이보다 정확하게 반영하기 때문이다.

연습문제 2. \(n = 12\)에서 Wilcoxon 부호순위검정의 정확 귀무분포를 직접 열거하여 구하고, 관측된 \(W^+ = 70\)의 정확 \(p\)값을 계산하라. 정규근사와 비교하라.

풀이
import numpy as np, itertools
n = 12
ranks = np.arange(1, n + 1)
Wplus = np.array([np.dot(s, ranks)
                  for s in itertools.product([0, 1], repeat=n)])
print(len(Wplus))                       # 4096 = 2^12
print(Wplus.mean(), Wplus.var())        # 39.0, 162.5
p = 2 * min((Wplus >= 70).mean(), (Wplus <= 70).mean())
print(p)                                # 0.012207

출력:

4096
39.0 162.5
0.01220703125

열거로 얻은 평균 \(39.0\)과 분산 \(162.5\)가 공식 \(n(n+1)/4\)와 \(n(n+1)(2n+1)/24\)의 값과 정확히 일치한다.

방법 \(p\)값
정확 (열거) \(0.01221\)
정규근사, 동점 보정 없음 \(0.01502\)
정규근사, 동점 보정 \(0.01491\)

동점 보정의 효과가 미미하다(\(0.01502 \to 0.01491\), 0.8% 감소). 보정항 \(18/48 = 0.375\)가 분산 \(162.5\)의 0.23%에 불과하기 때문이다. 동점이 크기 2짜리 세 개뿐이라 그렇다.

반면 정규근사 자체의 오차는 훨씬 크다. \(0.0149\) 대 \(0.0122\)로 22% 과대평가한다. \(n = 12\)에서 \(W^+\)의 분포가 아직 계단이 굵어 정규곡선과 잘 맞지 않기 때문이다.

교훈: 동점 보정보다 정확검정을 쓰는 편이 훨씬 중요하다. \(n \le 25\) 정도면 정확 계산이 즉시 끝난다.

연습문제 3. 차이 분포가 대칭이 아니면 대응 Wilcoxon 검정이 위험하다. 처치 효과가 일부 피험자에게만 나타나는 상황을 모형화하여 이 문제를 보여라.

풀이

처치가 피험자의 30%에게만 크게 작용하고 나머지 70%에게는 아무 효과가 없다고 하자. 이때 차이의 분포는 0에 몰린 덩어리와 오른쪽 꼬리로 이루어진 심한 비대칭 혼합이다.

import numpy as np
from scipy import stats
rng = np.random.default_rng(4)

def sim(n, B=2000):
    pw = np.empty(B); ps = np.empty(B)
    for b in range(B):
        responder = rng.random(n) < 0.3
        d = rng.normal(0, 1, n) + responder * 3.0
        pw[b] = stats.wilcoxon(d).pvalue
        nz = d[d != 0]
        ps[b] = stats.binomtest(int((nz > 0).sum()), len(nz)).pvalue
    return (pw < .05).mean(), (ps < .05).mean()

for n in (10, 20, 40):
    print(n, sim(n))

출력:

10 (0.2445, 0.0875)
20 (0.486, 0.246)
40 (0.811, 0.432)

여기서 참인 중앙값 차이는 0이 아니다(반응자가 30%이므로 중앙값은 여전히 0 근처이지만 평균은 \(+0.9\)이다). 따라서 이것은 크기 문제가 아니라 두 검정이 서로 다른 것을 재고 있음을 보여 주는 예이다.

\(n\) Wilcoxon 기각률 부호검정 기각률
10 0.244 0.088
20 0.486 0.246
40 0.811 0.432

Wilcoxon이 훨씬 자주 기각한다. 그런데 이것을 "검정력이 높다"고 말할 수 있을까?

문제는 무엇을 결론으로 삼느냐이다. Wilcoxon이 기각할 때 우리가 아는 것은 "Walsh 평균의 중앙값이 0이 아니다"이고, 이는 여기서 참이다. 그러나 실무자는 이를 "전형적인 환자가 개선된다"로 읽기 쉽다. 그런데 참가자의 70%는 아무 효과도 없다.

부호검정은 정직하게 "개선된 사람이 절반을 넘는가?"를 묻고, 답은 "그렇다, 하지만 약하게"이다(\(n = 40\)에서 기각률 0.43으로 Wilcoxon의 0.81의 절반이다).

실무 권고: 반응자/비반응자 혼합이 의심되면 검정 하나로 요약하지 말고 차이의 히스토그램을 반드시 그려야 한다. 이봉 분포가 보이면 "평균 효과"라는 개념 자체가 오도적이며, 반응자 비율을 추정하는 것이 옳은 분석이다.

연습문제 4. 대응 Wilcoxon 검정으로 중앙값 차이의 신뢰구간을 만들고, 대응 부호검정 기반 구간 및 \(t\) 구간과 폭을 비교하라.

풀이

Wilcoxon 구간은 차이의 Walsh 평균 \((D_i + D_j)/2\) (\(i \le j\))에 기반한다.

import numpy as np, itertools
from scipy import stats
D = np.array([7, 3, -2, 7, 5, 5, 9, -8, 11, 11, 12, 13])
n = len(D)

walsh = np.sort([(D[i] + D[j]) / 2 for i in range(n) for j in range(i, n)])
ranks = np.arange(1, n + 1)
Wplus = np.array([np.dot(s, ranks) for s in itertools.product([0, 1], repeat=n)])
k = max(c for c in range(30) if 2 * (Wplus <= c).mean() <= 0.05)
print(k, len(walsh), walsh[k], walsh[-(k + 1)])   # 13 78 1.5 10.0

Ds = np.sort(D)
m = max(c for c in range(n) if 2 * stats.binom.cdf(c, n, 0.5) <= 0.05)
print(m, Ds[m + 1], Ds[n - m - 1])                # 2 5 11
print(stats.ttest_1samp(D, 0).confidence_interval())

출력:

13 78 1.5 10.0
2 5 11
ConfidenceInterval(low=2.1717302734032677, high=9.994936393263398)
방법 95% 신뢰구간 폭 실제 포함확률
대응 부호검정 \((5, \; 11)\) \(6.0\) \(0.961\)
대응 Wilcoxon \((1.5, \; 10.0)\) \(8.5\) \(0.958\)
대응 \(t\) \((2.17, \; 9.99)\) \(7.8\) \(0.950\)

세 구간 모두 0을 포함하지 않으므로 세 검정이 모두 기각한 것과 일관된다.

부호검정 구간이 가장 좁다는 것이 뜻밖으로 보인다. 검정력이 가장 낮은 검정이 왜 가장 좁은 구간을 주는가? 이는 두 구간이 다른 방식으로 만들어지기 때문이다. 부호검정 구간은 순서통계량 \(D_{(3)} = 5\)와 \(D_{(10)} = 11\) 사이이고, 이 자료의 차이가 대부분 \(5\) 이상에 몰려 있어 우연히 좁게 나왔다. Wilcoxon 구간은 Walsh 평균에 기반하므로 \(-8\)과 \(-2\)가 만드는 낮은 쌍평균들까지 반영되어 아래쪽으로 늘어난다.

구간의 폭은 검정력의 좋은 지표가 아니다. 표본이 하나뿐일 때 어느 구간이 좁은지는 그 표본의 우연에 크게 좌우된다. 검정력을 비교하려면 여러 표본에 걸친 평균 폭을 보아야 한다.

점추정값을 비교하면 부호검정의 점추정은 표본중앙값 \(7.0\), Wilcoxon의 Hodges-Lehmann 추정은 Walsh 평균의 중앙값 \(7.0\), \(t\)의 점추정은 표본평균 \(6.08\)이다.


정리하며

대응자료에 대한 Wilcoxon 부호순위검정은 절대 대응차이에 순위를 매기고 각 부호에 그 순위만큼 가중치를 준다. 대칭성 가정이 성립하면 대응 부호검정보다 강력한 검정이 된다. 절차는 차이 \(D_i = X_i - Y_i\)에 일표본 Wilcoxon 부호순위검정을 적용한 것과 동일하다. 소표본에서는 정확 임계값을 쓸 수 있고, 표본이 크면 동점 보정을 적용한 정규근사가 신뢰할 만한 \(p\)값을 준다.