잔차의 정규성 확인¶
정규성이 중요한 이유¶
잔차(관측값과 예측값의 차이)는 각 집단에서 정규분포를 따라야 한다. 이 가정이 있어야 F-통계량이 귀무가설 아래에서 올바른 \(F\)-분포를 따른다. 잔차가 정규성에서 크게 벗어나면 분산분석이 주는 p-값이 부정확해져 잘못된 결론으로 이어질 수 있다.
정규성 가정은 표본이 작을 때 특히 중요하다. 표본이 크면(집단당 \(n \geq 30\)) 중심극한정리가 완만한 이탈에 대해 로버스트성을 제공한다. 바탕 분포가 무엇이든 집단 평균의 표본분포가 근사적으로 정규가 되기 때문이다.
설정¶
보기 1. 진단에 쓸 모형 준비. 세 집단 각 \(n = 20\)에 모표준편차를 \(1.0,\ 1.3,\ 1.6\)으로 주고 적합한다.
(1) 정규표본에서 \(S^2\)은 \(\sigma^2\)의 불편추정량이지만 \(S\)는 \(\sigma\)를 아래로 치우쳐 추정한다. \(E[S] = c_4 \sigma\) 꼴로 적고 \(c_4\)를 감마함수로 표현하시오. 아울러 \(\operatorname{SD}(S) = \sigma\sqrt{1 - c_4^2}\)임을 보이고 \(n = 20\)에서 두 값을 계산하시오.
(2) 모형을 적합해 세 표본표준편차를 (1)의 \(E[S]\)와 \(\operatorname{SD}(S)\)에 비추어 읽으시오. 세 추정값이 모두 참값보다 작게 나온 것은 치우침 때문인가 표집 변동 때문인가.
풀이
(1) 해석적으로. 정규표본에서 \((n-1)S^2/\sigma^2 \sim \chi^2(n-1)\)이다. 그러므로
이고 \(E[S]\)를 구하려면 \(E[\sqrt{Q}]\)를 알아야 한다. \(\chi^2(d)\)의 밀도로 직접 적분하면
이다. 중간 단계는 \(\int_0^\infty x^{a-1}e^{-x/2}dx = 2^a\Gamma(a)\)를 \(a = (d+1)/2\)에 쓴 것뿐이다. \(d = n-1\)을 넣으면
을 얻는다. \(\sqrt{\cdot}\)가 오목함수이므로 옌센 부등식이 \(E[\sqrt Q] < \sqrt{E[Q]}\)를 보장하고 따라서 \(c_4 < 1\), 곧 \(S\)는 \(\sigma\)를 아래로 치우쳐 추정한다. \(S^2\)이 불편이라는 것과 모순이 아니다. 불편성은 비선형변환을 통과하지 못한다.
분산은 \(E[S^2] = \sigma^2\)을 그대로 쓰면 바로 나온다.
\(n = 20\)에서 \(c_4 = 0.98693\)이고 \(\sqrt{1-c_4^2} = 0.16112\)다. 치우침은 \(1.3\%\)뿐인데 표집 변동은 \(16.1\%\)다. 둘의 크기가 열 배 넘게 차이 난다는 것을 미리 적어 둔다.
(2) 수치적으로. 모형을 적합하고 세 표본표준편차를 (1)의 값과 나란히 둔다.
import numpy as np
import pandas as pd
from scipy import special
from statsmodels.formula.api import ols
# 이 페이지의 모든 진단은 아래 모형 하나를 놓고 수행한다.
# 집단마다 표준편차를 1.0, 1.3, 1.6으로 다르게 주어 진단이 무엇을 잡아내는지
# (그리고 무엇을 못 잡아내는지) 볼 수 있게 했다.
rng = np.random.default_rng(42)
n = 20
data = pd.DataFrame({
"group": np.repeat(["A", "B", "C"], n),
"response": np.concatenate([
rng.normal(10.0, 1.0, n),
rng.normal(10.8, 1.3, n),
rng.normal(12.0, 1.6, n),
]),
})
model = ols("response ~ C(group)", data=data).fit()
print(data.groupby("group").response.agg(["count", "mean", "std"]).round(3))
print(f"\nF = {model.fvalue:.4f}, p = {model.f_pvalue:.4f}")
# (1) 에서 유도한 c4 와 SD(S)/sigma 를 n = 20 에서 계산한다.
c4 = np.sqrt(2 / (n - 1)) * special.gamma(n / 2) / special.gamma((n - 1) / 2)
rel_sd = np.sqrt(1 - c4 ** 2)
print(f"\nc4 = {c4:.5f} (치우침 {100 * (1 - c4):.2f}%)")
print(f"SD(S)/sigma = {rel_sd:.5f} (표집 변동 {100 * rel_sd:.2f}%)")
print(f"\n{'sigma':>7}{'E[S]':>9}{'SD(S)':>9}{'관측 s':>9}{'z':>8}")
obs = data.groupby("group").response.std().values
for sigma, s in zip([1.0, 1.3, 1.6], obs):
print(f"{sigma:>7.1f}{c4 * sigma:>9.4f}{rel_sd * sigma:>9.4f}"
f"{s:>9.4f}{(s - c4 * sigma) / (rel_sd * sigma):>+8.3f}")
출력:
count mean std
group
A 20 9.967 0.870
B 20 10.942 1.034
C 20 12.191 1.145
F = 23.7708, p = 0.0000
c4 = 0.98693 (치우침 1.31%)
SD(S)/sigma = 0.16112 (표집 변동 16.11%)
sigma E[S] SD(S) 관측 s z
1.0 0.9869 0.1611 0.8702 -0.725
1.3 1.2830 0.2095 1.0335 -1.191
1.6 1.5791 0.2578 1.1450 -1.684
유도한 \(c_4 = 0.98693\)과 \(\operatorname{SD}(S)/\sigma = 0.16112\)가 코드와 맞는다.
물음에 답하면 압도적으로 표집 변동 때문이다. 치우침은 \(\sigma = 1.6\)에서도 \(1.6 - 1.5791 = 0.021\)밖에 설명하지 못하는데, 실제로 벌어진 간격은 \(1.6 - 1.145 = 0.455\)다. 표준화하면 세 집단의 \(z\)가 \(-0.73\), \(-1.19\), \(-1.68\)이고 모두 \(2\) 표준편차 안에 있으므로 어느 하나도 놀랄 값이 아니다.
셋이 모두 아래로 떨어진 것도 이상하지 않다. \(P(S < E[S]) = P\bigl(\chi^2_{19} < 19 c_4^2\bigr) = 0.511\)이므로 세 집단이 독립일 때 모두 아래일 확률은 \(0.511^3 = 0.134\)다. 일곱 번에 한 번쯤 일어나는 일이다.
실무적 교훈은 이것이다. 집단당 20개에서 표본표준편차는 참값의 \(\pm 32\%\)(\(2\operatorname{SD}\)) 안에서 흔들린다. 참 비가 \(1.6/1.0 = 1.6\)이었는데 관측된 비가 \(1.145/0.870 = 1.32\)로 눌려 나온 것이 그 결과이며, 아래에서 등분산 검정이 이 자료의 분산 차이를 잡아내지 못하는 것도 같은 까닭이다. 가정 검정의 결과를 "가정이 성립한다"로 읽으면 안 되는 이유를 이 표 하나가 보여 준다.
확인 방법¶
Q-Q 그림 (분위수-분위수 그림)¶
Q-Q 그림은 관측된 잔차의 분위수를 정규분포의 이론적 분위수와 비교한다. 잔차가 정규분포를 따르면 점들이 45도 기준선을 따라 대략 놓인다.
- 점들이 꼬리에서 선을 벗어나면 꼬리가 두껍거나 얇은 분포를 시사한다.
- 체계적인 S자 곡선은 치우침을 시사한다.
- 양 극단의 몇몇 점이 벗어나는 것은 자연스러운 표집 변동을 반영한 것일 수 있다.
보기 2. Q-Q 그림. 보기 1의 모형에서 나온 잔차 60개를 그린다.
(1) 잔차의 Q-Q 그림을 그리고 무엇을 읽을 수 있는지 말하시오. "점들이 기준선을 잘 따른다"는 판단을 수치로 뒷받침하시오.
(2) 이 그림이 가리는 것은 무엇인가. 이 자료에 실제로 들어 있는 가정 위반 가운데 Q-Q 그림으로는 볼 수 없는 것을 찾아 수치로 보이시오.
풀이
이 보기에는 유도할 식이 없다. 그림에서 무엇을 읽어야 하는지가 전부다. 그러므로 읽히는 것을 수로 적는 일에 집중한다.
(1) 그림이 말하는 것.
import matplotlib.pyplot as plt
import numpy as np
import statsmodels.api as sm
from scipy import stats
# 그림에서 읽으려는 것을 먼저 수로 적어 둔다.
e = np.sort(model.resid.values)
N = len(e)
q = stats.norm.ppf(np.arange(1, N + 1) / (N + 1)) # sm.qqplot 의 기본 위치
m, s = e.mean(), e.std(ddof=1)
line = m + s * q # line='s' 가 그리는 기준선
print(f"n = {N}, 잔차 표준편차 s = {s:.4f}")
print(f"정렬 잔차와 이론 분위수의 상관 r = {np.corrcoef(e, q)[0, 1]:.5f} (r^2 = {np.corrcoef(e, q)[0, 1] ** 2:.5f})")
print(f"Shapiro-Wilk W = {stats.shapiro(model.resid).statistic:.5f}")
k = np.argmax(np.abs(e - line))
print(f"기준선에서 가장 먼 점: 순위 {k + 1}, 관측 {e[k]:+.4f}, 기준선 {line[k]:+.4f}"
f" (차이 {e[k] - line[k]:+.4f} = {abs(e[k] - line[k]) / s:.2f}s)")
print(f"왜도 {stats.skew(e):+.4f}, 초과첨도 {stats.kurtosis(e):+.4f}")
# Q-Q 그림이 가리는 것: 집단마다 잔차의 흩어짐이 다른지는 보이지 않는다.
g = model.model.data.frame["group"]
print("\n집단별 잔차 표준편차 (Q-Q 그림에는 드러나지 않는다)")
print(model.resid.groupby(g).std().round(4).to_string())
# line='s'는 표본의 평균과 표준편차로 정한 기준선이다.
# line='45'(y=x)는 잔차가 표준화되어 있을 때만 맞으므로 여기서는 쓰지 않는다.
sm.qqplot(model.resid, line='s')
plt.title("Q-Q Plot of Residuals")
plt.show()
출력:
n = 60, 잔차 표준편차 s = 1.0050
정렬 잔차와 이론 분위수의 상관 r = 0.99115 (r^2 = 0.98238)
Shapiro-Wilk W = 0.98696
기준선에서 가장 먼 점: 순위 60, 관측 +2.6418, 기준선 +2.1454 (차이 +0.4965 = 0.49s)
왜도 +0.0390, 초과첨도 -0.0756
집단별 잔차 표준편차 (Q-Q 그림에는 드러나지 않는다)
group
A 0.8702
B 1.0335
C 1.1450

"기준선을 잘 따른다"의 정량적 내용은 \(r = 0.99115\)다. 정렬된 잔차와 이론 분위수의 상관이 이 값이고, Q-Q 그림의 직선성은 바로 이 상관을 눈으로 재는 일이다.
모양에 관한 두 수도 함께 읽어야 한다. 왜도 \(+0.0390\)이므로 S자 휘어짐이 없고(치우침 없음), 초과첨도 \(-0.0756\)이므로 꼬리가 두껍지도 얇지도 않다. 앞의 "확인 방법"이 열거한 세 가지 이상 징후 가운데 어느 것도 나타나지 않는다.
꼬리에서 벗어난 점도 재 두어야 한다. 가장 멀리 떨어진 것은 최대 잔차로, 관측값 \(+2.6418\)인데 기준선은 \(+2.1454\)를 기대한다. 차이 \(0.4965\)는 잔차 표준편차의 \(0.49\)배다. 표본 하나의 최댓값이 \(0.5\) 표준편차만큼 기대에서 벗어나는 일은 \(n = 60\)에서 흔하다. 최댓값의 분포 자체가 넓기 때문이며, 꼬리의 한두 점으로 정규성을 판단하면 안 되는 까닭이 이것이다.
상관과 Shapiro-Wilk \(W\)의 관계도 짚어 둔다. \(r^2 = 0.98238\)이고 \(W = 0.98696\)으로 \(W\)가 조금 크다. \(W\)의 분자 \(\left(\sum a_i x_{(i)}\right)^2\)에 쓰이는 가중값 \(a_i\)가 순서통계량의 공분산까지 반영한 최적 가중값이어서, 등가중 상관보다 큰 값을 주기 때문이다. 둘은 가깝지만 같지 않다.
(2) 그림이 가리는 것. 이 자료에는 실제로 가정 위반이 하나 들어 있다. 집단별 모표준편차를 \(1.0,\ 1.3,\ 1.6\)으로 다르게 주었으므로 등분산성이 깨져 있다. 잔차 표준편차가 집단 A \(0.8702\), B \(1.0335\), C \(1.1450\)으로 단조증가하는 것이 그 흔적이다.
그런데 Q-Q 그림에는 이것이 전혀 드러나지 않는다. 잔차를 한 덩어리로 모아 정렬한 뒤 분위수만 비교하므로 어느 점이 어느 집단에서 왔는지가 지워진다. 표준편차가 다른 세 정규분포의 혼합은 초과첨도를 조금 올리지만(\(0.87, 1.03, 1.15\) 정도의 차이로는 \(0.01\) 수준이라 \(-0.0756\) 안에 묻힌다) 여전히 정규에 아주 가까운 모양이어서, 정규성 진단은 아무 경보를 울리지 않는다.
같은 이유로 Q-Q 그림은 관측 순서에 관한 정보도 지운다. 잔차가 서로 상관되어 있어도(독립성 위반) 정렬해 버리면 알 수 없다. 그래서 정규성 Q-Q 그림, 등분산성의 잔차 대 적합값 그림, 독립성의 순서 대 잔차 그림이 서로를 대신할 수 없다. 세 그림은 같은 잔차를 보지만 각각 다른 축을 버린다.
Shapiro-Wilk 검정¶
Shapiro-Wilk 검정은 자료가 정규분포에서 추출되었다는 귀무가설을 평가한다. 결과가 유의하면(\(p < 0.05\)) 정규성으로부터의 이탈을 시사한다.
여기서 \(x_{(i)}\)는 정렬된 표본값이고, \(a_i\)는 정규분포에서 크기 \(n\)인 표본의 순서통계량의 평균, 분산, 공분산으로부터 만들어지는 상수이다.
보기 3. Shapiro-Wilk 검정. 보기 1의 잔차 \(60\)개에 돌린다.
(1) 위 \(W\)의 식을 보면 분자는 정렬된 값 \(x_{(i)}\)만 쓰고 분모는 값들의 제곱합이다. 그러므로 \(W\)는 자료의 순서에 무관하다. 이것을 보이고, 같은 잔차를 \(1000\)번 섞어 \(W\)가 변하지 않음을 확인하시오. 같은 순열에 Durbin-Watson 통계량을 돌려 대조하고, 여기서 "Shapiro-Wilk가 통과했으므로 가정이 확인되었다"가 왜 성립할 수 없는지 밝히시오.
(2) 이 검정이 표본크기에 따라 무엇을 잡고 무엇을 놓치는지 모의실험으로 재시오. 이 쪽 자료의 \(p = 0.77\)을 어떻게 읽어야 하는가.
풀이
(1) 해석적으로. 식을 다시 본다.
가중값 \(a_i\)는 \(n\)에만 의존하는 상수다(정규 순서통계량의 평균·분산·공분산으로 정해진다). 자료가 들어가는 자리는 두 곳뿐이다.
- 분자의 \(x_{(i)}\)는 정렬된 값이다. 자료를 어떤 순서로 주어도 정렬하면 같은 수열이 된다.
- 분모 \(\sum_i (x_i - \bar x)^2\)은 \(\bar x\)와 각 \(x_i^2\)의 합으로 쓰이므로 덧셈의 순서와 무관하다.
곧 \(W\)는 자료를 값들의 모둠(multiset)으로만 본다. 어떤 순열 \(\pi\)에 대해서도
이다. \(W\)는 순열불변량이다. 따라서 자료의 순서에 담긴 정보 — 자기상관, 추세, 군집 — 는 \(W\)에 원리적으로 들어올 수 없다.
여기서 결론이 하나 나온다. Shapiro-Wilk로는 독립성을 확인할 수 없다. 시계열 자료에 이 검정을 돌려 \(p > 0.05\)를 얻고 "가정이 확인되었다"고 적으면 틀린 것이다. 확인된 것은 정규성뿐이고, 그것도 잔차를 한 덩어리로 모은 주변분포의 정규성이다. 독립성은 독립성 쪽의 Durbin-Watson과 순서 대 잔차 그림이, 등분산성은 등분산성 쪽의 Levene과 잔차 대 적합값 그림이 각각 따로 본다.
수치적으로.
import numpy as np
from scipy.stats import shapiro
from statsmodels.stats.stattools import durbin_watson
# 잔차 전체를 한 번에 넣는다. 집단별로 따로 검정하면 다중검정 문제가 생기고,
# 분산분석이 요구하는 것도 "각 집단의 잔차"가 아니라 하나의 오차 분포다.
stat, p_value = shapiro(model.resid)
print(f"Shapiro-Wilk Test: W = {stat:.4f}, p-value = {p_value:.4f}")
# 같은 잔차를 섞어서 1000번 다시 돌린다. 순서를 바꾸면 W 가 바뀌는가.
e = model.resid.values
rng_sh = np.random.default_rng(7)
perms = [rng_sh.permutation(e) for _ in range(1000)]
W = np.array([shapiro(v).statistic for v in perms])
d = np.array([durbin_watson(v) for v in perms])
print(f"\n원래 순서의 W = {stat!r}")
print(f"섞은 1000개의 W 최소 = {W.min()!r}")
print(f" W 최대 = {W.max()!r}")
print(f" 폭 = {W.max() - W.min():.3e} (상대 {(W.max() - W.min()) / stat:.2e})")
print(f"정렬한 잔차의 W = {shapiro(np.sort(e)).statistic!r}")
print(f"뒤집은 잔차의 W = {shapiro(e[::-1]).statistic!r}")
print(f"\n비교 — 순서를 보는 통계량은 이렇게 움직인다")
print(f"원래 순서의 d = {durbin_watson(e):.4f}")
print(f"섞은 1000개의 d 최소 {d.min():.4f}, 최대 {d.max():.4f}, 폭 {d.max() - d.min():.4f}")
출력:
Shapiro-Wilk Test: W = 0.9870, p-value = 0.7711
원래 순서의 W = 0.9869641022270246
섞은 1000개의 W 최소 = 0.9869641022270237
W 최대 = 0.9869641022270246
폭 = 8.882e-16 (상대 9.00e-16)
정렬한 잔차의 W = 0.9869641022270246
뒤집은 잔차의 W = 0.9869641022270246
비교 — 순서를 보는 통계량은 이렇게 움직인다
원래 순서의 d = 2.1101
섞은 1000개의 d 최소 1.1633, 최대 2.6866, 폭 1.5233
\(W\)가 \(1000\)개의 순열에서 사실상 한 값이다. 최소와 최대의 차이가 \(8.9 \times 10^{-16}\), 상대로 \(9.0\times 10^{-16}\)이니 배정밀도의 마지막 비트다. 정렬한 순서와 거꾸로 뒤집은 순서는 원래 값과 마지막 자리까지 같다.
남은 \(10^{-16}\)의 흔들림조차 통계가 아니다. 유도한 대로 \(W\)는 수학적으로 순열불변이고, 흔들리는 까닭은 분모의 제곱합을 받은 순서대로 더하기 때문이다. 부동소수 덧셈은 결합법칙을 지키지 않으므로 더하는 순서가 바뀌면 끝자리가 달라진다. 그것이 전부다.
대조가 요점이다. 똑같은 \(1000\)개의 순열에 Durbin-Watson을 돌리면 \(d\)가 \(1.1633\)에서 \(2.6866\)까지, 폭 \(1.5233\)으로 움직인다. \(d\)는 순서를 보는 통계량이므로 섞으면 값이 바뀌고, \(W\)는 보지 않으므로 바뀌지 않는다. \(W\)의 폭 \(10^{-15}\) 대 \(d\)의 폭 \(1.5\)가 "이 검정은 순서를 못 본다"의 정량적 내용이다.
(2) 이론이 예측하는 값. 자료가 정규면 기각률은 \(n\)에 상관없이 명목수준 \(0.05\)다. 정규가 아니면 기각률은 \(n\)이 커질수록 \(1\)로 간다. 그러므로 "\(p > 0.05\)"는 "정규다"가 아니라 "이 \(n\)으로는 이 정도 벗어남을 구별할 수 없다"는 말이다.
import numpy as np
from scipy import stats
# 표본크기에 따라 검정력이 어떻게 달라지는가. 이론값은 정규일 때 0.05 다.
rng_n = np.random.default_rng(2026)
B = 4000
print(f"반복 {B}회, 명목수준 0.05, 몬테카를로 오차 "
f"{np.sqrt(0.05 * 0.95 / B):.4f}")
cases = [
("정규 (가정 성립)", lambda m: rng_n.normal(0, 1, m)),
("t_10 (완만한 이탈)", lambda m: rng_n.standard_t(10, m)),
("지수 (심한 이탈)", lambda m: rng_n.exponential(1.0, m)),
]
ns = [10, 25, 60, 200, 800]
print("\n" + " ".join(f"{'n=' + str(m):>8s}" for m in ns) + " 자료 분포")
for name, draw in cases:
row = []
for m in ns:
hit = sum(stats.shapiro(draw(m)).pvalue < 0.05 for _ in range(B))
row.append(hit / B)
print(" ".join(f"{v:8.4f}" for v in row) + f" {name}")
출력:
반복 4000회, 명목수준 0.05, 몬테카를로 오차 0.0034
n=10 n=25 n=60 n=200 n=800 자료 분포
0.0503 0.0493 0.0568 0.0575 0.0535 정규 (가정 성립)
0.0747 0.1108 0.1605 0.3510 0.8285 t_10 (완만한 이탈)
0.4457 0.9260 1.0000 1.0000 1.0000 지수 (심한 이탈)
첫 줄이 검정의 눈금을 확인해 준다. 정규 자료에서 기각률이 \(0.0493\)에서 \(0.0575\) 사이이고, 몬테카를로 오차 \(0.0034\)를 감안하면 다섯 \(n\) 전부에서 \(0.05\)와 두 오차 안쪽이다. 검정이 제대로 작동한다.
둘째 줄이 "작은 표본에서는 못 잡는다"를 보여 준다. \(t_{10}\)은 초과첨도가 \(6/(10-4) = 1.0\)인 분포다. 눈에 띄는 벗어남인데도 \(n = 10\)에서 \(0.0747\), \(n = 25\)에서 \(0.1108\)로 거의 잡지 못한다. \(n = 800\)이 되어야 \(0.8285\)다.
셋째 줄이 반대쪽을 보여 준다. 지수분포는 왜도가 \(2\)인 심한 이탈이고, \(n = 60\)에서 이미 기각률이 \(1.0000\)이다. 심한 이탈은 작은 표본에서도 잡힌다.
두 줄을 함께 보면 이 검정의 성격이 드러난다. 같은 분포 벗어남이 \(n\)에 따라 "없다"에서 "확실하다"로 바뀐다. 그런데 분산분석이 비정규성에 다치는 정도는 \(n\)이 커질수록 작아진다(중심극한정리). 곧 검정이 기각하기 시작하는 지점과 분산분석이 실제로 위험해지는 지점이 서로 반대로 간다. \(n = 800\)에서 \(t_{10}\)을 \(83\%\) 기각하지만 그 표본크기에서 \(F\)-검정은 이미 거의 멀쩡하고, \(n = 10\)에서 \(t_{10}\)을 \(7\%\)만 기각하지만 그 표본크기에서는 정규성이 정말로 필요하다.
그러므로 이 쪽 자료의 \(p = 0.7711\)을 "정규성이 확인되었다"로 읽으면 안 된다. 잔차가 \(60\)개인데 위 표에서 \(n = 60\), \(t_{10}\)의 기각률이 \(0.1605\)다. \(t_{10}\)만큼 꼬리가 두꺼운 자료였어도 여섯 번에 한 번만 걸린다. 자료를 실제로 정규분포에서 만들었으니 통과한 것이 당연하고 검정이 제대로 작동한다는 확인이기도 하지만, 통과 자체가 정규성의 증거로는 약하다. Q-Q 그림(보기 2)과 함께 보아야 한다.
표본크기에 대한 민감성
Shapiro-Wilk 검정은 표본이 크면 지나치게 민감해져 사소한 이탈까지 통계적으로 유의하다고 표시할 수 있다. 반대로 표본이 작으면 의미 있는 이탈을 탐지할 검정력이 부족할 수 있다. 형식적 검정은 언제나 시각적 검토(Q-Q 그림, 히스토그램)와 함께 쓰라.
잔차의 히스토그램¶
잔차를 히스토그램으로 그리면 분포의 모양을 빠르게 시각적으로 평가할 수 있다.
보기 4. 잔차의 히스토그램. 보기 1의 잔차 \(60\)개를 \(20\)개 구간에 담아 그린다.
(1) 그림을 그리고 무엇을 읽을 수 있는지 말하시오. 살펴볼 것으로 꼽히는 봉우리의 수가 구간 수에 얼마나 민감한지 수치로 보이시오.
(2) 이 그림이 가리는 것은 무엇인가. 이 자료에 실제로 들어 있는 구조 가운데 히스토그램으로 볼 수 없는 것을 적으시오.
풀이
이 보기에는 유도할 식이 없다. 그림에서 무엇을 읽어야 하는지가 전부다. 그런데 이 그림은 읽는 사람이 고른 구간 수에 모양이 달라지는 특이한 성질이 있으므로 그것을 먼저 재야 한다.
(1) 그림이 말하는 것.
import matplotlib.pyplot as plt
import numpy as np
# 그림에서 읽으려는 것을 먼저 수로 적어 둔다.
e = model.resid.values
print(f"n = {len(e)}, 표준편차 {e.std(ddof=1):.4f}, "
f"범위 [{e.min():.4f}, {e.max():.4f}]")
print(f"\n{'구간 수':>7s} {'구간 폭':>8s} {'구간당 평균':>10s} {'빈 구간':>7s} "
f"{'봉우리':>6s} 구간별 도수")
for nb in [8, 12, 20, 30]:
cnt, edges = np.histogram(e, bins=nb)
peaks = sum(1 for j in range(nb)
if cnt[j] > 0
and (j == 0 or cnt[j] > cnt[j - 1])
and (j == nb - 1 or cnt[j] >= cnt[j + 1]))
print(f"{nb:7d} {edges[1] - edges[0]:8.3f} {len(e) / nb:10.2f} "
f"{int((cnt == 0).sum()):7d} {peaks:6d} {cnt}")
# 히스토그램은 Q-Q 그림보다 거칠지만 치우침과 봉우리 수를 한눈에 보여 준다.
# 계급 수에 따라 모양이 달라지므로 단독으로 판단하지 말고 Q-Q 그림과 함께 본다.
plt.hist(model.resid, bins=20, density=True, alpha=0.7, edgecolor='black')
plt.xlabel("Residuals")
plt.ylabel("Density")
plt.title("Histogram of Residuals")
plt.show()
출력:
n = 60, 표준편차 1.0050, 범위 [-2.5224, 2.6418]
구간 수 구간 폭 구간당 평균 빈 구간 봉우리 구간별 도수
8 0.646 7.50 0 2 [ 2 4 15 8 16 11 2 2]
12 0.430 5.00 0 3 [ 1 1 4 10 6 7 11 11 5 2 1 1]
20 0.258 3.00 3 5 [1 0 1 1 3 5 5 5 4 4 6 7 6 5 3 2 0 0 1 1]
30 0.172 2.00 8 10 [1 0 0 1 0 1 0 6 2 5 4 1 3 1 4 5 5 3 3 6 2 2 2 1 0 0 0 1 0 1]

관측값 \(60\)개를 \(20\)개 구간에 담았으니 구간당 평균 \(3\)개다. 이 정도면 히스토그램이 울퉁불퉁해 보이는 것이 당연하며, 그 요철을 분포의 특징으로 읽으면 안 된다.
얼마나 안 되는지가 "봉우리" 열에 있다. 똑같은 잔차 \(60\)개인데 구간 수를 \(8 \to 12 \to 20 \to 30\)으로 바꾸면 봉우리가 \(2 \to 3 \to 5 \to 10\)개로 늘어난다. \(30\)개 구간에서는 \(60\)개 점에서 봉우리 \(10\)개를 세게 되는데, 이 자료는 정규분포에서 나온 것이므로 참 봉우리는 하나다. 세어 낸 봉우리는 전부 구간을 가늘게 썰어 생긴 잡음이다.
빈 구간도 함께 늘어난다. \(8\)개 구간에서는 빈 구간이 없는데 \(30\)개 구간에서는 \(8\)개가 빈다. 구간당 평균이 \(2\) 아래로 내려가면 히스토그램은 분포가 아니라 표본의 우연을 그리고 있는 것이다.
모양에 관한 판단은 보기 2에서 이미 수로 재어 두었다. 왜도 \(+0.0390\)이라 치우침이 없고, 초과첨도 \(-0.0756\)이라 꼬리가 두껍지도 얇지도 않다. 아래 세 가지 이상 징후 가운데 어느 것도 없다.
살펴볼 것:
- 치우침: 분포가 0을 중심으로 대칭이 아니다.
- 두꺼운 꼬리(첨도): 정규성 아래에서 기대되는 것보다 극단값이 많다.
- 이봉성: 봉우리가 둘이면 빠뜨린 집단 변수가 있음을 시사할 수 있다.
(2) 그림이 가리는 것. 셋이다.
첫째, 꼬리를 보여 주지 못한다. 정규성이 추론에 영향을 주는 곳은 꼬리인데, \(20\)개 구간에서 양 끝 구간의 도수가 \(1,\ 0,\ 1,\ 1\)과 \(2,\ 0,\ 0,\ 1,\ 1\)이다. 점 한두 개로는 꼬리가 두꺼운지 얇은지 판정할 수 없다. Q-Q 그림은 꼬리의 점들을 하나씩 이론 분위수와 맞추어 놓으므로 같은 자료에서 훨씬 많은 것을 보여 준다. 표본이 작을 때 정규성 판단에는 Q-Q 그림이 히스토그램보다 낫다.
둘째, 이 자료에 실제로 들어 있는 등분산성 위반을 가린다. 집단별 모표준편차를 \(1.0,\ 1.3,\ 1.6\)으로 다르게 주었으므로 이 잔차는 표준편차가 다른 세 정규분포의 혼합이다(보기 2에서 집단별 잔차 표준편차가 \(0.8702,\ 1.0335,\ 1.1450\)인 것을 확인했다). 그런데 히스토그램은 잔차를 한 덩어리로 담으므로 어느 점이 어느 집단에서 왔는지가 지워진다. 그리고 분산이 다른 정규분포의 혼합은 여전히 종 모양이라 히스토그램 모양으로는 구별되지 않는다.
셋째, 순서를 지운다. 도수만 세므로 잔차가 어떤 순서로 관측되었는지는 남지 않는다. 보기 3에서 Shapiro-Wilk가 순서에 무관함을 보았는데, 히스토그램도 같은 한계를 갖는다. 같은 히스토그램을 주는 자료가 \(60!\)가지 순서로 존재하고, 그 가운데는 심하게 자기상관된 것도 있다.
덧붙여, 자주 같이 쓰이는 상자그림은 이봉성을 아예 못 보인다. 다섯 수 요약(최소·1사분위·중위수·3사분위·최대)만 그리므로 봉우리가 둘인 분포와 하나인 분포가 같은 상자로 그려질 수 있다. 히스토그램이 상자그림보다 나은 유일한 점이 봉우리를 보여 준다는 것인데, 위에서 본 대로 그 봉우리마저 구간 수에 달려 있다.
그러므로 정규성 진단의 순서는 Q-Q 그림이 먼저이고, 히스토그램은 치우침을 한눈에 보려는 보조 수단이다. 봉우리를 셀 때는 구간 수를 몇 가지로 바꾸어 보아 그 봉우리가 남는지 확인해야 한다.
정규성이 어긋날 때¶
- 자료 변환: 로그, 제곱근, Box-Cox 변환으로 치우침을 줄여 잔차를 더 정규에 가깝게 만들 수 있다(정규성을 얻기 위한 변환 참조).
- 비모수 대안: Kruskal-Wallis 검정은 평균 대신 중앙값을 비교하며 정규성을 요구하지 않는다(Kruskal-Wallis 검정 참조).
- 붓스트랩: 재표본추출 방법은 분포 가정 없이 타당한 추론을 제공한다(붓스트랩 원리 참조).
연습문제¶
연습문제 1. 집단이 \(k = 3\)개, 집단당 \(n = 8\)인 일원배치 분산분석에서 잔차에 대한 Shapiro-Wilk 검정이 \(p = 0.02\)를 주었다. 연구자는 분산분석을 포기해야 하는가? 근거를 설명하라.
풀이
꼭 그럴 필요는 없다. 형식적 검정 결과를 시각적 진단(Q-Q 그림, 히스토그램)과 함께 보아야 한다. 집단당 \(n = 8\)뿐이므로 Shapiro-Wilk 검정이 F-검정에 심각한 영향을 주지 않는 완만한 이탈을 탐지했을 수 있다. 분산분석은 특히 집단 크기가 같을 때 약한 비정규성에 꽤 로버스트하다.
그러나 Q-Q 그림에서 두꺼운 꼬리, 강한 치우침, 이상점이 드러나면 대안을 고려해야 한다. 분산 안정화 변환(로그, 제곱근)을 적용하거나, 더 로버스트한 Welch 분산분석을 쓰거나, Kruskal-Wallis 검정 같은 비모수 대안으로 옮길 수 있다.
연습문제 2. 분산분석 잔차의 Q-Q 그림에서 가운데는 기준선을 따르지만 양 꼬리에서 위로 휘는 점들이 보인다. 이 패턴은 잔차 분포에 대해 무엇을 뜻하며 분산분석의 추론에 어떤 영향을 줄 수 있는가?
풀이
양 꼬리에서 위로 휘는 점들은 두꺼운 꼬리(급첨) 분포를 나타낸다. 잔차에 정규분포가 예측하는 것보다 극단값이 많다는 뜻이며, 초과 첨도가 있다는 뜻이다.
두꺼운 꼬리는 집단 내 분산 추정값(MSW)을 부풀려 F-통계량을 작게 만들고 검정을 보수적으로(검정력이 낮게) 만든다. 게다가 극단값이 집단 평균에 지나치게 큰 영향을 주는 영향점일 수도 있다. 연구자는 이상점을 확인하고, 로버스트 방법을 고려하거나, 이탈이 심하면 비모수 검정을 쓰는 편이 좋다.
연습문제 3. 표본이 클 때(예: 집단당 \(n \geq 30\)) 정규성 가정이 작은 표본에서보다 덜 결정적인 이유를 설명하라. 분산분석의 어떤 결과가 정규성 없이도 유효하고, 어떤 결과가 그렇지 않은가?
풀이
표본이 크면 중심극한정리에 의해 모집단 분포가 무엇이든 집단 평균의 표본분포가 근사적으로 정규가 된다. F-검정은 집단 평균을 비교하므로 그 기준분포가 근사적으로 옳다.
여전히 유효한 결과: 집단 평균의 OLS 추정값은 정규성과 무관하게 불편이다. 표본이 크면 F-검정의 p-값도 근사적으로 타당하다.
유효하지 않을 수 있는 결과: 개별 관측값에 대한 예측구간은 여전히 정규성을 요구한다. 표본이 작으면 F-통계량의 정확한 분포가 정규성에 의존하므로, 정규성이 어긋나면 p-값이 부정확할 수 있다.
연습문제 4. 연습문제 1의 상황(\(k=3\), \(n=8\))에서 샤피로-윌크 검정이 얼마나 믿을 만한지 재라. 표본이 커지면 어떻게 되는가?
풀이
import warnings
warnings.filterwarnings("ignore")
import numpy as np
from scipy import stats
rng = np.random.default_rng(4141)
B = 6_000
dists = {
"정규 (H0 참)": lambda n: rng.normal(0, 1, n),
"t(10) 약한 두꺼움": lambda n: rng.standard_t(10, n),
"t(5)": lambda n: rng.standard_t(5, n),
"균등": lambda n: rng.uniform(-1, 1, n),
"지수 (치우침 2)": lambda n: rng.exponential(1, n),
"로그정규 (치우침 6)": lambda n: np.exp(rng.normal(0, 1, n)),
}
ns = [8, 15, 30, 50, 100]
print("샤피로-윌크의 기각률 (명목 0.05)")
print(f"{'분포':>20s} " + " ".join(f"{'n=' + str(n):>8s}" for n in ns))
for lab, f in dists.items():
row = [sum(stats.shapiro(f(n)).pvalue < 0.05 for _ in range(B)) / B
for n in ns]
print(f"{lab:>20s} " + " ".join(f"{r:8.4f}" for r in row))
샤피로-윌크의 기각률 (명목 0.05)
분포 n=8 n=15 n=30 n=50 n=100
정규 (H0 참) 0.0497 0.0522 0.0515 0.0493 0.0507
t(10) 약한 두꺼움 0.0717 0.0893 0.1188 0.1567 0.2363
t(5) 0.0993 0.1555 0.2508 0.3558 0.5720
균등 0.0690 0.1247 0.3840 0.7545 0.9952
지수 (치우침 2) 0.3400 0.6715 0.9668 0.9995 1.0000
로그정규 (치우침 6) 0.4793 0.8273 0.9925 1.0000 1.0000
\(n=8\)에서 \(t(5)\)를 잡을 확률이 0.099다. 명목 수준과 거의 같다. 사실상 아무 정보도 주지 못한다.
| 분포 | \(n=8\) | \(n=100\) |
|---|---|---|
| \(t(10)\) | 0.072 | 0.236 |
| \(t(5)\) | 0.099 | 0.572 |
| 균등 | 0.069 | 0.995 |
| 지수 | 0.340 | 1.000 |
\(n=8\)에서 잡을 수 있는 것은 심한 치우침뿐이다. 지수(0.34)와 로그정규(0.48)만 절반 가까이 잡는다.
그럼 연습문제 1의 \(p=0.02\)는 무슨 뜻인가. \(n=24\)(전체 잔차)에서 유의가 나왔다면 꽤 심한 위반일 가능성이 높다. 검정력이 낮은 상황에서 나온 유의는 효과가 크다는 신호다(승자의 저주의 역설적 이점).
그러나 \(n\)이 크면 정반대다. \(n=100\)에서 \(t(10)\)이 0.236으로 잡히는데, \(t(10)\)의 비정규성은 \(F\) 검정에 사실상 무해하다(가정 개요 연습문제 6). 유의한데 무해한 상황이다.
검정력이 \(n\)과 함께 커지는 방향과 \(F\)의 로버스트성이 커지는 방향이 반대다.
| \(n\) | 샤피로의 검정력 | \(F\)의 로버스트성 | 결과 |
|---|---|---|---|
| 작음 | 낮음 | 낮음 | 문제를 못 본다 |
| 큼 | 높음 | 높음 | 무해한 것을 본다 |
두 구간 모두에서 검정이 오도한다. 이것이 정규성 검정 대신 그림과 지표를 보라는 권고의 근거다.
연습문제 1의 답. \(p=0.02\)만으로 분산분석을 포기하지 않는다. 대신
- Q-Q 그림으로 위반의 모양을 본다(꼬리인가 치우침인가).
- 치우침이면 변환이나 순열검정을 고려한다.
- 두꺼운 꼬리면 이상값을 확인하고, \(F\)는 대체로 견딘다.
- \(n=8\)의 표본에서는 어떤 판단도 잠정적임을 보고서에 밝힌다.
연습문제 5. 정규성을 원자료가 아니라 잔차로 확인해야 하는 이유를 수치로 보여라.
풀이
import warnings
warnings.filterwarnings("ignore")
import numpy as np
from scipy import stats
rng = np.random.default_rng(4242)
B = 8_000
print("k=3, n=15, 각 집단은 완벽한 정규. 집단 평균만 δ 씩 떨어져 있다.")
print(f"{'집단 평균 차이 δ':>16s} {'전체 원자료':>11s} {'잔차':>9s}")
for d in [0.0, 1.0, 2.0, 4.0]:
a = b = 0
for _ in range(B):
gs = [rng.normal(i * d, 1, 15) for i in range(3)]
y = np.concatenate(gs)
r = np.concatenate([g - g.mean() for g in gs])
a += stats.shapiro(y).pvalue < 0.05
b += stats.shapiro(r).pvalue < 0.05
print(f"{d:16.1f} {a / B:11.4f} {b / B:9.4f}")
k=3, n=15, 각 집단은 완벽한 정규. 집단 평균만 δ 씩 떨어져 있다.
집단 평균 차이 δ 전체 원자료 잔차
0.0 0.0471 0.0491
1.0 0.0356 0.0522
2.0 0.1042 0.0503
4.0 0.8828 0.0467
\(\delta=4\)에서 원자료 검정이 88% 기각한다. 각 집단이 완벽한 정규인데도 그렇다.
| \(\delta\) | 원자료 | 잔차 |
|---|---|---|
| 0 | 0.047 | 0.049 |
| 1 | 0.036 | 0.052 |
| 2 | 0.104 | 0.050 |
| 4 | 0.883 | 0.047 |
이유는 자명하다. 집단 평균이 멀어지면 전체 자료가 세 봉우리를 갖는 혼합분포가 된다. 정규가 아닌 것이 당연하다.
모형이 가정하는 것은 \(Y\)가 아니라 \(\varepsilon\)의 정규성이다.
잔차 검정은 \(\delta\)에 무관하게 0.047~0.052를 유지한다. 집단 평균을 빼내면 혼합 구조가 사라지기 때문이다.
\(\delta=1\)에서 원자료 검정이 0.036으로 오히려 보수적인 것도 흥미롭다. 세 정규의 혼합이 약간 평평해져(음의 초과첨도) 샤피로가 덜 기각한다.
실무 함의 넷.
stats.shapiro(model.resid)를 쓴다.stats.shapiro(data["y"])가 아니다.- 집단별로 따로 검정하는 것도 가능하지만 \(n\)이 작아 검정력이 없다.
- 잔차의 Q-Q 그림이 표준 진단이다.
- 회귀에서도 마찬가지다. \(Y\)의 주변분포는 정규일 필요가 없다.
네 번째가 자주 오해된다. "종속변수가 정규여야 한다"는 흔한 오해인데, 모형이 요구하는 것은 조건부 분포의 정규성이다.
연습문제 6. 연습문제 2가 물은 Q-Q 그림 패턴을 사전으로 정리하라. 각 패턴에 해당하는 수치 지표를 함께 보여라.
풀이
import numpy as np
from scipy import stats
rng = np.random.default_rng(4747)
dists = {
"정규": lambda n: rng.normal(0, 1, n),
"t(5) 두꺼운 꼬리": lambda n: rng.standard_t(5, n),
"균등 (가벼운 꼬리)": lambda n: rng.uniform(-1, 1, n),
"지수 (오른쪽 치우침)": lambda n: rng.exponential(1, n),
"좌우 뒤집은 지수": lambda n: -rng.exponential(1, n),
"이산 (0/1/2)": lambda n: rng.choice([0.0, 1, 2], n, p=[.3, .4, .3]),
}
qs = [0.01, 0.05, 0.50, 0.95, 0.99]
print("표본 20만, 표준화 후 분위수 (Q-Q 그림의 세로축에 해당)")
print(f"{'분포':>20s} {'치우침':>7s} {'초과첨도':>8s} "
+ " ".join(f"{'q' + str(int(q * 100)):>8s}" for q in qs))
print(f"{'표준정규 기준':>20s} {0.0:7.2f} {0.0:8.2f} "
+ " ".join(f"{stats.norm.ppf(q):8.3f}" for q in qs))
for lab, f in dists.items():
x = f(200_000)
z = (x - x.mean()) / x.std(ddof=1)
print(f"{lab:>20s} {stats.skew(x):7.2f} {stats.kurtosis(x):8.2f} "
+ " ".join(f"{np.quantile(z, q):8.3f}" for q in qs))
표본 20만, 표준화 후 분위수 (Q-Q 그림의 세로축에 해당)
분포 치우침 초과첨도 q1 q5 q50 q95 q99
표준정규 기준 0.00 0.00 -2.326 -1.645 0.000 1.645 2.326
정규 -0.01 0.01 -2.334 -1.649 0.001 1.648 2.329
t(5) 두꺼운 꼬리 0.02 4.23 -2.608 -1.560 0.001 1.562 2.629
균등 (가벼운 꼬리) 0.00 -1.20 -1.693 -1.557 0.002 1.557 1.694
지수 (오른쪽 치우침) 2.00 5.89 -0.989 -0.948 -0.308 2.000 3.622
좌우 뒤집은 지수 -1.98 5.77 -3.594 -1.999 0.307 0.949 0.990
이산 (0/1/2) 0.00 -1.33 -1.288 -1.288 0.003 1.293 1.293
패턴 사전.
| Q-Q 그림의 모습 | 지표 | 예 |
|---|---|---|
| 직선 | 치우침 \(\approx0\), 초과첨도 \(\approx0\) | 정규 |
| 양 끝이 모두 바깥으로(S 뒤집힘) | 초과첨도 \(>0\) | \(t(5)\) |
| 양 끝이 모두 안쪽으로(S자) | 초과첨도 \(<0\) | 균등 |
| 오른쪽 끝만 위로 | 치우침 \(>0\) | 지수 |
| 왼쪽 끝만 아래로 | 치우침 \(<0\) | 뒤집은 지수 |
| 계단 | 값의 종류가 적음 | 이산 자료 |
연습문제 2의 패턴은 무엇인가. "가운데는 직선, 양 꼬리에서 위로 휜다"는 서술은 양쪽 꼬리가 모두 바깥이라는 뜻이므로 두꺼운 꼬리(\(t\) 분포형)다.
표에서 \(t(5)\)를 보자. \(q_{99}=2.629\)로 정규의 2.326보다 크고, \(q_1=-2.608\)로 \(-2.326\)보다 작다. 양쪽이 바깥이다. 반대로 \(q_{95}=1.562\)는 정규의 1.645보다 안쪽이다. 중간 부분은 오히려 좁다.
이것이 두꺼운 꼬리의 정확한 의미다. 꼬리가 두꺼우면 중심도 더 뾰족하다. 분산이 1로 고정된 채 꼬리만 늘어날 수는 없기 때문이다.
치우침과 첨도를 함께 봐야 한다. 지수분포의 초과첨도 5.89는 \(t(5)\)의 4.23보다 큰데, 이것은 치우침의 부산물이다. 치우친 분포는 자동으로 첨도가 커진다.
분산분석에 주는 함의.
| 패턴 | \(F\) 검정에 미치는 영향 | 처방 |
|---|---|---|
| 두꺼운 꼬리 | 경미(약간 보수적) | 대체로 무시 가능 |
| 가벼운 꼬리 | 경미 | 무시 |
| 치우침 | 균형이면 보수적, 불균형+이분산이면 치명적 | 변환, 순열 |
| 이산·계단 | 경우에 따라 | 순열, 정확검정 |
| 이상값 한두 개 | 큼 | 그 점을 조사 |
마지막 줄이 가장 흔한 실무 상황이다. Q-Q 그림에서 한두 점만 크게 벗어났다면 분포의 문제가 아니라 그 관측의 문제다.
연습문제 7. 비정규 자료에서 \(F\) 검정과 크러스컬-월리스 검정의 크기와 검정력을 비교하라. 언제 비모수로 가야 하는가?
풀이
import numpy as np
from scipy import stats
rng = np.random.default_rng(4343)
B = 6_000
print("k=3, n=15, 균형. 모든 분포를 분산 1 로 표준화. 이동폭 0.9")
print(f"{'분포':>20s} {'F 크기':>8s} {'KW 크기':>8s} {'F 검정력':>9s} {'KW 검정력':>9s}")
for lab, f in [
("정규", lambda n: rng.normal(0, 1, n)),
("t(5)", lambda n: rng.standard_t(5, n) / np.sqrt(5 / 3)),
("지수", lambda n: rng.exponential(1, n)),
("로그정규", lambda n: (np.exp(rng.normal(0, 1, n)) - np.exp(.5)) / 2.16),
("균등", lambda n: rng.uniform(-np.sqrt(3), np.sqrt(3), n)),
]:
a = b = c = e = 0
for _ in range(B):
gs = [f(15) for _ in range(3)]
a += stats.f_oneway(*gs).pvalue < 0.05
b += stats.kruskal(*gs).pvalue < 0.05
gs = [f(15), f(15), f(15) + 0.9]
c += stats.f_oneway(*gs).pvalue < 0.05
e += stats.kruskal(*gs).pvalue < 0.05
print(f"{lab:>20s} {a / B:8.4f} {b / B:8.4f} {c / B:9.4f} {e / B:9.4f}")
k=3, n=15, 균형. 모든 분포를 분산 1 로 표준화. 이동폭 0.9
분포 F 크기 KW 크기 F 검정력 KW 검정력
정규 0.0492 0.0455 0.6955 0.6628
t(5) 0.0443 0.0452 0.7088 0.7577
지수 0.0395 0.0425 0.7142 0.9103
로그정규 0.0367 0.0455 0.7807 0.9935
균등 0.0490 0.0465 0.6773 0.6020
크기는 둘 다 안전하다(0.037~0.049). 균형 설계에서는 \(F\)도 크러스컬-월리스도 명목 수준을 지킨다.
검정력이 갈린다.
| 분포 | \(F\) | KW | 승자 |
|---|---|---|---|
| 정규 | 0.696 | 0.663 | \(F\) (\(+5\%\)) |
| 균등 | 0.677 | 0.602 | \(F\) (\(+12\%\)) |
| \(t(5)\) | 0.709 | 0.758 | KW (\(+7\%\)) |
| 지수 | 0.714 | 0.910 | KW (\(+27\%\)) |
| 로그정규 | 0.781 | 0.994 | KW (\(+27\%\)) |
치우친 분포에서 크러스컬-월리스가 압도적이다. 로그정규에서 0.994 대 0.781이다.
정규에서의 손실은 작다(\(-5\%\)). 이론적으로 크러스컬-월리스의 점근 상대효율은 정규에서 \(3/\pi\approx0.955\)인데, 관측된 0.663/0.696 = 0.952와 잘 맞는다.
균등에서 손실이 더 크다(\(-12\%\)). 가벼운 꼬리에서는 순위로 바꾸면서 잃는 정보가 많다.
그러나 두 검정은 다른 가설을 검정한다.
| 귀무가설 | |
|---|---|
| \(F\) | 모든 평균이 같다 |
| 크러스컬-월리스 | 모든 분포가 같다(위치 이동 모형에서는 중앙값) |
분포의 모양이 집단마다 다르면 크러스컬-월리스가 "유의"해도 평균의 차이를 뜻하지 않는다. 산포만 달라도 기각할 수 있다.
언제 비모수로 가는가.
| 상황 | 선택 |
|---|---|
| 치우침이 심하고 위치 이동 모형이 타당 | 크러스컬-월리스 |
| 평균이 관심 모수 | \(F\) 또는 순열(연습문제 8) |
| 서열 자료(순서만 의미) | 크러스컬-월리스 |
| 이분산까지 있음 | 웰치(KW는 이분산에 취약) |
| 이상값이 지배 | KW 또는 절사평균 |
네 번째 줄에 주의하자. 크러스컬-월리스는 등분산을 가정한다. 이분산 + 비정규면 크러스컬-월리스도 무너진다.
연습문제 8. 순열 분산분석이 정규성 없이 정확한 크기를 주는지 확인하라. 이산 자료에서도 그런가?
풀이
원리. 귀무가설 아래에서 집단 라벨은 아무 의미가 없다. 라벨을 섞어 \(F\) 통계량의 분포를 직접 만들면 분포 가정 없이 기준분포를 얻는다.
import warnings
warnings.filterwarnings("ignore")
import numpy as np
from scipy import stats
def perm_p(gs, rng, R=400):
obs = stats.f_oneway(*gs).statistic
z = np.concatenate(gs)
ns = [len(g) for g in gs]
c = 0
for _ in range(R):
p = rng.permutation(z)
i, parts = 0, []
for n in ns:
parts.append(p[i:i + n])
i += n
c += stats.f_oneway(*parts).statistic >= obs
return (c + 1) / (R + 1)
rng = np.random.default_rng(4545)
B = 2_000
print("k=3, n=12, 모든 집단이 같은 분포, 명목 0.05")
print(f"{'분포':>16s} {'F 크기':>8s} {'순열 크기':>9s} {'KW 크기':>8s}")
for lab, f in [("정규", lambda n: rng.normal(0, 1, n)),
("t(3) 매우 두꺼움", lambda n: rng.standard_t(3, n)),
("로그정규", lambda n: np.exp(rng.normal(0, 1, n))),
("이항(0/1)", lambda n: rng.binomial(1, 0.2, n).astype(float))]:
a = b = c = 0
for _ in range(B):
gs = [f(12) for _ in range(3)]
a += stats.f_oneway(*gs).pvalue < 0.05
b += perm_p(gs, rng) < 0.05
c += stats.kruskal(*gs).pvalue < 0.05
print(f"{lab:>16s} {a / B:8.4f} {b / B:9.4f} {c / B:8.4f}")
k=3, n=12, 모든 집단이 같은 분포, 명목 0.05
분포 F 크기 순열 크기 KW 크기
정규 0.0440 0.0460 0.0420
t(3) 매우 두꺼움 0.0500 0.0595 0.0570
로그정규 0.0345 0.0510 0.0350
이항(0/1) 0.0605 0.0420 0.0455
순열검정이 네 경우 모두 0.042~0.060을 유지한다.
| 분포 | \(F\) | 순열 | KW |
|---|---|---|---|
| 정규 | 0.044 | 0.046 | 0.042 |
| \(t(3)\) | 0.050 | 0.060 | 0.057 |
| 로그정규 | 0.035 | 0.051 | 0.035 |
| 이항 0/1 | 0.061 | 0.042 | 0.046 |
이산 자료에서 특히 유용하다. 0/1 자료에서 \(F\) 검정이 0.061인데 순열은 0.042다. \(F\) 분포 근사가 값이 두 개뿐인 자료에서 부정확하기 때문이다.
로그정규에서 \(F\)의 0.035(보수적)를 순열이 0.051로 되돌린다. 보수성을 고친다는 것은 검정력을 되찾는다는 뜻이다.
순열검정의 세 장점.
| 장점 | 내용 |
|---|---|
| 분포 가정 불필요 | 어떤 분포에서도 크기가 정확 |
| 통계량을 자유롭게 고를 수 있다 | \(F\), 절사평균 차이, 중앙값 차이… |
| 평균을 검정한다 | 크러스컬-월리스와 달리 모수가 바뀌지 않는다 |
두 가지 한계.
- 이분산을 고치지 못한다. 라벨 교환 가능성이 분포가 완전히 같다를 전제하므로, 분산이 다르면 귀무가설이 이미 거짓이다. 웰치 통계량을 순열하면 부분적으로 개선된다.
- 계산 비용. \(R=400\)이어도 \(p\)의 해상도가 \(1/401\)이다. 정밀한 \(p\)가 필요하면 \(R\geq10{,}000\)이 필요하다.
\(p\) 계산에 \(+1\)을 더하는 이유.
관측된 자료 자체도 하나의 순열이기 때문이다. 이렇게 하면 \(\hat p\)가 결코 0이 되지 않고 크기가 정확해진다.
연습문제 9. 연습문제 3의 질문에 수치로 답하라. 정규성 없이도 유효한 결과와 그렇지 않은 결과를 구분하라.
풀이
import numpy as np
from scipy import stats
rng = np.random.default_rng(4646)
B = 20_000
print("지수분포(평균 1, 치우침 2), 명목 95%")
print(f"{'n':>5s} {'평균의 t 구간':>12s} {'예측구간':>10s}")
for n in [10, 30, 100, 500]:
a = b = 0
for _ in range(B):
x = rng.exponential(1, n)
m, s = x.mean(), x.std(ddof=1)
t = stats.t.ppf(0.975, n - 1)
a += abs(m - 1.0) < t * s / np.sqrt(n) # 평균의 구간
b += abs(rng.exponential(1) - m) < t * s * np.sqrt(1 + 1 / n) # 예측구간
print(f"{n:5d} {a / B:12.4f} {b / B:10.4f}")
xs = rng.exponential(1, (200_000, 10))
print("\n점추정의 불편성 (지수, n=10, 20만 회)")
print(f" 표본평균의 평균 = {xs.mean(1).mean():.6f} (참값 1)")
print(f" 표본분산의 평균 = {xs.var(1, ddof=1).mean():.6f} (참값 1)")
지수분포(평균 1, 치우침 2), 명목 95%
n 평균의 t 구간 예측구간
10 0.9041 0.9312
30 0.9260 0.9415
100 0.9434 0.9450
500 0.9497 0.9446
점추정의 불편성 (지수, n=10, 20만 회)
표본평균의 평균 = 1.000069 (참값 1)
표본분산의 평균 = 1.004299 (참값 1)
세 가지가 뚜렷이 구분된다.
| 결과 | 정규성 필요? | 근거 |
|---|---|---|
| 점추정의 불편성 | 불필요 | \(E[\bar X]=\mu\)는 항상 참 |
| 평균의 신뢰구간 | \(n\)이 크면 불필요 | 0.904 → 0.950 |
| 예측구간 | 언제나 필요 | 0.931 → 0.945에서 멈춤 |
(가) 점추정은 분포와 무관하다. \(E[\bar X]=\mu\)와 \(E[S^2]=\sigma^2\)은 분산만 유한하면 성립한다. 표에서 1.000069와 1.004299로 확인된다.
(나) 신뢰구간은 중심극한정리의 보호를 받는다. \(n=10\)의 0.904가 \(n=500\)에서 0.950으로 수렴한다. 느리지만 수렴한다.
(다) 예측구간은 수렴하지 않는다. \(n=100\)에서 0.945, \(n=500\)에서 0.9446이다. \(n\)을 아무리 키워도 0.95에 이르지 못한다.
왜 그런가. 예측구간은 새 관측 하나를 덮으려 한다.
\(n\to\infty\)에서 \(\bar X\to\mu\), \(S\to\sigma\)이므로 구간은 \(\mu\pm1.96\sigma\)로 수렴한다. 그런데 지수분포에서 \(P(|X-\mu|<1.96\sigma)\)는 0.95가 아니다.
표의 0.9446과 잘 맞는다. 중심극한정리는 \(\bar X\)의 분포에만 작용하고 \(X\) 하나의 분포에는 작용하지 않는다.
분산분석의 각 결과에 적용하면.
| 결과 | 정규성 필요? |
|---|---|
| 집단 평균의 점추정 | 불필요 |
| \(\text{MSE}\)의 불편성 | 불필요 |
| \(F\) 검정의 \(p\)-값 | \(n\)이 크면 대체로 무방 |
| 평균 차이의 신뢰구간 | \(n\)이 크면 무방 |
| 개별 관측의 예측구간 | 필요 |
| 허용구간(공정 규격) | 필요 |
| 분산에 대한 추론 | 언제나 필요 |
마지막 세 줄이 실무에서 자주 잊힌다. 공정관리의 규격 한계, 참조범위(의학 검사의 정상 범위), 개별 예측은 모두 분포의 꼬리에 의존하므로 중심극한정리의 도움을 받지 못한다.
처방. 예측·허용구간이 필요하면 비모수 방법(경험 분위수, 순위 기반 허용구간)을 쓰거나 분포를 명시적으로 모형화한다.
연습문제 10. 정규성 확인의 전체 절차를 정리하라.
풀이
무엇의 정규성인가.
잔차의 정규성이다. 원자료가 아니다(연습문제 5).
진단 순서.
| 순서 | 도구 | 보는 것 |
|---|---|---|
| 1 | Q-Q 그림 | 벗어남의 모양(연습문제 6) |
| 2 | 치우침·초과첨도 | 수치로 요약 |
| 3 | 히스토그램·상자그림 | 이상값, 다봉 |
| 4 | 샤피로-윌크 | 참고용(연습문제 4) |
네 번째를 마지막에 둔 이유. \(n\)이 작으면 검정력이 없고(\(n=8\)에서 \(t(5)\)를 0.10으로 잡음), \(n\)이 크면 무해한 위반도 잡는다.
핵심 수치 넷.
| 사실 | 값 |
|---|---|
| \(n=8\)에서 샤피로가 \(t(5)\)를 잡을 확률 | 0.099 |
| 집단 평균이 \(4\sigma\) 벌어질 때 원자료 검정 기각률 | 0.883 |
| 로그정규에서 KW의 검정력 우위 | \(+27\%\) |
| 지수분포 예측구간의 극한 피복확률 | 0.945(0.95가 아님) |
위반의 유형별 처방.
| 유형 | \(F\)에 미치는 영향 | 처방 |
|---|---|---|
| 두꺼운 꼬리 | 경미(보수적) | 대체로 그대로 진행 |
| 가벼운 꼬리 | 경미 | 그대로 |
| 치우침 | 균형이면 보수적 | 변환, 크러스컬-월리스, 순열 |
| 이상값 | 큼 | 그 관측을 조사, 로버스트 방법 |
| 이산·계단 | 근사가 부정확 | 순열검정 |
대안의 성격.
| 대안 | 검정하는 모수 | 크기 |
|---|---|---|
| 순열 분산분석 | 평균 | 정확 |
| 크러스컬-월리스 | 확률적 우위/중앙값 | 정확(등분산 하에서) |
| 변환 후 \(F\) | 변환 척도의 평균 | 근사 |
| 부트스트랩 | 평균 | 근사 |
순열검정이 가장 깔끔하다. 모수를 바꾸지 않으면서 크기를 정확히 지킨다. 이분산만 별도로 다루면 된다.
하지 말아야 할 것 넷.
- 원자료에 정규성 검정을 하지 않는다.
- \(p<0.05\)를 곧바로 "분산분석 불가"로 읽지 않는다.
- \(n\)이 크다고 검정 결과를 그대로 따르지 않는다.
- 예측구간·허용구간에 정규성 가정을 무심코 쓰지 않는다.
보고 형식.
잔차의 치우침 = 0.31, 초과첨도 = 0.48
Q-Q 그림에서 뚜렷한 체계적 이탈 없음 (양 꼬리에 각 1점)
샤피로-윌크 W = 0.978, p = 0.14 (n = 60)
→ 정규성 가정에 큰 문제 없음. 꼬리의 두 점은 관측 오류가 아님을 확인.
수치·그림·검정을 함께 적고, 이상값을 어떻게 처리했는지 밝힌다.
한 문장. 정규성은 통과/탈락을 가리는 관문이 아니라, 벗어남의 모양과 크기를 보고 대응을 정하기 위한 진단이다.
정리하며¶
정규성은 잔차에 대한 가정이며, 표본이 작을 때만 중요하다.
- 무엇을 보는가. 원자료가 아니라 잔차 \(e_{ij}=Y_{ij}-\bar Y_{i\cdot}\) 다. 집단마다 평균이 다르므로 원자료를 합쳐 보면 당연히 정규가 아니다.
- 집단당 \(n\ge30\) 이면 크게 걱정하지 않아도 된다. 중심극한정리가 집단 평균을 정규로 만들어 주기 때문이며, \(F\) 검정은 완만한 이탈에 강건하다.
- Q-Q 그림이 형식적 검정보다 낫다. 14장에서 보듯 정규성 검정은 표본이 크면 사소한 이탈에도 기각하고 작으면 큰 이탈도 놓친다. 검정 결과를 기계적으로 따르지 말 것.
- 심한 치우침이나 이상치가 진짜 문제다. 그때는 변환하거나 16장의 크루스칼–월리스 검정으로 간다.
- 정규성보다 등분산과 독립성이 더 중요하다. 우선순위를 그렇게 두는 것이 실무적이다.
다음 절 관측의 독립성 확인으로 넘어간다.