두 분산에 대한 F-검정¶
개요¶
두 분산에 대한 F-검정은 독립인 두 정규모집단의 분산을 비교한다. 두 집단의 변동성이 같은지 평가하는 데 쓰이며, 이는 합동 이표본 t-검정의 전제 조건이다. 등분산이라는 귀무가설 아래에서 검정통계량은 F-분포를 따른다. 카이제곱 분산 검정과 마찬가지로 정규성에서 벗어나는 데 민감하다.
검정의 구성¶
가설: 분산이 \(\sigma_1^2\)과 \(\sigma_2^2\)인 독립인 두 정규모집단에 대해,
- 양측: \(H_0\colon \sigma_1^2/\sigma_2^2 = \theta_0\) 대 \(H_1\colon \sigma_1^2/\sigma_2^2 \neq \theta_0\)
- 단측: \(H_0\colon \sigma_1^2/\sigma_2^2 = \theta_0\) 대 \(H_1\colon \sigma_1^2/\sigma_2^2 > \theta_0\)
가장 흔한 경우는 \(\theta_0 = 1\)(등분산 검정)이다.
검정통계량: 크기 \(n_1\), \(n_2\)인 표본의 표본표준편차 \(S_1\), \(S_2\)가 주어졌을 때, \(H_0\)과 정규성 가정 아래에서
이다.
분산비는 1 을 중심으로 대칭하게 흩어지지 않는다¶

\(H_0\)이 참이면 \(S_1^2/S_2^2\)은 1 근처에 있으리라 기대하기 쉽다. 왼쪽 그림은 그 기대가 절반만 맞다는 것을 보여 준다. \(n_1 = n_2 = 10\)일 때 이 비의 중앙값은 정확히 1이지만, 가장 흔히 나오는 값(최빈값)은 0.64이고 평균은 1.29다. 정규난수로 20만 쌍을 만들어 재 보면 중앙값 1.003, 평균 1.287, 1보다 클 확률 0.502로 이론값과 일치한다. 1보다 클 확률과 작을 확률은 정확히 반반인데도 모양은 전혀 대칭이 아니다. 아래쪽은 0이라는 벽에 눌려 있고 위쪽은 열려 있기 때문이다.
오른쪽 그림은 두 분산이 정말 같을 때 분산비가 들어가는 95% 범위를 표본크기별로 그린 것이다. \(n_1 = n_2 = 10\)이면 그 범위가 \((0.25,\ 4.03)\)이다. 두 공정의 산포가 완전히 같아도, 열 개씩 재어서는 분산비가 4분의 1에서 4배 사이 어디든 나올 수 있다. 1에서 아래로는 0.75, 위로는 3.03이니 덧셈으로 재면 네 배 차이지만, 나눗셈으로 재면 양쪽 모두 4.03이다. 이 그림의 가로축이 로그 눈금인 것은 그 때문이다. 분산비의 흩어짐은 덧셈이 아니라 곱셈으로 읽어야 좌우가 맞는다.
그래서 분산비의 신뢰구간도 점추정값을 한가운데 두지 않는다. 연습문제 6에서 얻는 \(\sigma_1^2/\sigma_2^2\)의 95% 구간 \((0.783,\ 4.501)\)을 보면, 점추정값 1.836에서 아래로 1.05, 위로 2.67로 덧셈 거리가 2.5배 차이 난다. 그런데 나눗셈으로 읽으면 아래로 \(\div 2.35\), 위로 \(\times 2.45\)로 거의 같다. 분산비는 "몇을 더하고 뺀 범위"가 아니라 "몇 배에서 몇 배 사이"로 보고해야 한다. 표본을 키워도 이 성질은 사라지지 않는다. \(n_1 = n_2 = 100\)에서도 등분산 아래의 95% 범위가 \((0.67,\ 1.49)\)로 여전히 1을 곱셈으로 감싸며, 표준편차로 옮기면 0.82배에서 1.22배에 해당한다.
보기 1. 분산비 검정 계산기. 아래 함수의 주석은 "자유도의 순서가 중요하다. 바꿔 넣으면 오류 없이 조용히 틀린 p-값이 나온다"고 경고한다. 그 경고가 정확히 무엇을 말하는지 따져 둔다.
(1) \(1/F_{d_1,d_2} \sim F_{d_2,d_1}\)을 써서, 두 집단의 역할을 바꾸면서 자유도와 대립가설의 방향을 함께 바꾸면 p-값이 변하지 않음을 보이시오. 양측 p-값에도 같은 불변성이 성립하는가?
(2) \(n_1 = 15\), \(s_1 = 1.3\), \(n_2 = 12\), \(s_2 = 0.9\)에서 그 불변성을 확인하고, 자유도를 함께 바꾸는 것을 잊었을 때 나오는 틀린 p-값이 얼마나 어긋나는지 재시오.
풀이
(1) 해석적으로. \(F_{d_1,d_2}\)는 독립인 두 카이제곱의 비 \((U/d_1)/(V/d_2)\)이고(\(U \sim \chi^2_{d_1}\), \(V \sim \chi^2_{d_2}\)), 역수를 취하면 \((V/d_2)/(U/d_1)\)이므로
이다. 그러므로 임의의 \(x > 0\)에서
이 근사가 아니라 등식으로 성립한다.
이제 두 호출을 견준다. 첫 호출은 \(F = s_1^2/s_2^2\)을 자유도 \((d_1, d_2)\)로 읽어 우측 꼬리확률 \(P(F_{d_1,d_2} \ge F)\)를 준다. 집단을 바꾼 호출은 통계량이 \(s_2^2/s_1^2 = 1/F\), 자유도가 \((d_2, d_1)\)이고 방향이 좌측이므로 \(P(F_{d_2,d_1} \le 1/F)\)를 준다. 위 등식이 바로 이 둘이 같다는 말이다. \(\square\)
양측 p-값은 \(2\min\{G(F),\, 1-G(F)\}\) 꼴인데(\(G\)는 해당 \(F\) 분포의 분포함수) 집단을 바꾸면 두 꼬리가 서로 자리를 맞바꾼다. \(\min\)은 두 인수의 순서에 무관하므로 양측 p-값도 불변이다.
자유도를 바꾸지 않으면 무엇이 깨지는가. 통계량만 \(1/F\)로 바꾸고 자유도를 \((d_1,d_2)\)로 둔 채 좌측 꼬리를 읽으면 \(P(F_{d_1,d_2} \le 1/F)\)를 계산하게 되는데, 이것은 \(P(F_{d_2,d_1} \le 1/F)\)와 다른 수다. \(d_1 \ne d_2\)이면 두 분포가 다르기 때문이다. 예외 없이 틀리지만 오류도 경고도 나지 않는다.
(2) 수치적으로. 먼저 함수다.
from scipy.stats import f
def test_ratio_two_variances(n1, s1, n2, s2, theta0=1.0,
alt="two-sided", alpha=0.05):
"""H0: sigma1^2 / sigma2^2 = theta0. **정규모집단**을 가정한다.
F-검정은 정규성에서 벗어나는 데 특히 약하다. 꼬리가 조금만 두꺼워도
제1종 오류율이 크게 부풀기 때문에, 실무에서 등분산 사전검정으로 쓰는 것은
권장되지 않는다(그냥 Welch를 쓰는 편이 낫다).
s1, s2에는 ddof=1로 계산한 표본표준편차를 넣는다.
"""
# 자유도의 **순서**가 중요하다. 분자 쪽이 df1이다.
# s1과 s2를 바꿔 넣으면 자유도도 함께 바꿔야 하며, 그러지 않으면
# 오류 없이 조용히 틀린 p-값이 나온다.
df1, df2 = n1 - 1, n2 - 1
F_stat = (s1 ** 2 / s2 ** 2) / theta0
if alt == "two-sided":
p = 2 * min(f.cdf(F_stat, df1, df2),
1 - f.cdf(F_stat, df1, df2))
elif alt == "less":
p = f.cdf(F_stat, df1, df2)
else:
p = 1 - f.cdf(F_stat, df1, df2)
return F_stat, p, (p < alpha)
이제 (1)을 확인한다.
import numpy as np
# (1) 1/F_{d1,d2} ~ F_{d2,d1} 을 꼬리확률로 확인한다.
d1, d2 = 14, 11
print(" x P(F_{14,11} >= x) P(F_{11,14} <= 1/x)")
for x in (0.5, 1.0, 2.0864197530864197, 5.0):
print(f"{x:7.4f} {f.sf(x, d1, d2):.12f} {f.cdf(1 / x, d2, d1):.12f}")
# 집단을 바꿀 때 세 대립가설이 모두 짝을 맞추는가
print("\n집단을 바꾸고 자유도와 방향까지 함께 바꾼 경우")
for alt_a, alt_b in (("greater", "less"), ("less", "greater"),
("two-sided", "two-sided")):
_, pa, _ = test_ratio_two_variances(15, 1.3, 12, 0.9, alt=alt_a)
_, pb, _ = test_ratio_two_variances(12, 0.9, 15, 1.3, alt=alt_b)
print(f" {alt_a:>9} / {alt_b:<9} p = {pa:.15f} {pb:.15f}"
f" 차 = {abs(pa - pb):.2e}")
# 자유도를 함께 바꾸는 것을 잊으면
print("\n자유도를 바꾸지 않고 s1, s2 만 맞바꾼 경우 (틀린 계산)")
F_wrong = 0.9**2 / 1.3**2
for alt in ("less", "two-sided"):
_, p_ok, _ = test_ratio_two_variances(12, 0.9, 15, 1.3, alt=alt)
p_bad = (f.cdf(F_wrong, d1, d2) if alt == "less"
else 2 * min(f.cdf(F_wrong, d1, d2), f.sf(F_wrong, d1, d2)))
print(f" {alt:>9}: 올바른 p = {p_ok:.6f}, 틀린 p = {p_bad:.6f}"
f" (상대오차 {abs(p_bad - p_ok) / p_ok:.1%})")
출력:
x P(F_{14,11} >= x) P(F_{11,14} <= 1/x)
0.5000 0.888783358782 0.888783358782
1.0000 0.509190497951 0.509190497951
2.0864 0.112914221518 0.112914221518
5.0000 0.005436708133 0.005436708133
집단을 바꾸고 자유도와 방향까지 함께 바꾼 경우
greater / less p = 0.112914221518176 0.112914221518176 차 = 4.16e-17
less / greater p = 0.887085778481824 0.887085778481824 차 = 0.00e+00
two-sided / two-sided p = 0.225828443036351 0.225828443036351 차 = 8.33e-17
자유도를 바꾸지 않고 s1, s2 만 맞바꾼 경우 (틀린 계산)
less: 올바른 p = 0.112914, 틀린 p = 0.098065 (상대오차 13.2%)
two-sided: 올바른 p = 0.225828, 틀린 p = 0.196131 (상대오차 13.2%)
항등식이 네 자리 모두에서 성립한다. \(x = 0.5, 1, 2.0864, 5\)에서 두 확률이 소수 열두째 자리까지 같다. \(x = 1\)에서 \(0.509190\)으로 \(0.5\)가 아닌 것도 눈여겨볼 만하다. \(F_{14,11}\)의 중앙값은 1이 아니다(1보다 조금 작다).
세 대립가설이 모두 짝을 맞춘다. 차가 \(10^{-17}\) 수준으로, 부동소수점 반올림뿐이다. 양측도 \(0.225828443036351\)로 같아 (1)의 \(\min\) 논증이 확인된다.
자유도를 바꾸는 것을 잊으면 \(13.2\%\) 어긋난다. 올바른 단측 p-값 \(0.112914\) 대신 \(0.098065\)가 나온다. 이 자료에서는 둘 다 \(0.05\)보다 커서 판정이 같지만, 경계 근처였다면 결론이 갈렸을 것이다. 오류 메시지가 없는 틀림이 가장 위험하다. 함수를 쓸 때 \(n\)과 \(s\)를 짝으로 묶어 넘기는 습관이 그 자리를 막는다.
보기 2. 두 분산에 대한 F-검정. \(n_1 = 15\)에서 \(s_1 = 1.3\), \(n_2 = 12\)에서 \(s_2 = 0.9\)를 얻어 \(H_0\colon \sigma_1^2 = \sigma_2^2\) 대 \(H_1\colon \sigma_1^2 > \sigma_2^2\)을 검정한다.
(1) \(F\)와 단측 p-값을 구하고, \(\alpha = 0.05\)에서 기각하려면 관측된 \(s_1/s_2\)가 얼마 이상이어야 하는지 구하시오.
(2) 이 p-값은 두 모집단이 정규라는 가정 위에 서 있다. 가정이 깨졌을 때의 실제 오류율을 첨도만으로 예측하고, 이 표본크기에서 모의실험으로 확인하시오.
풀이
(1) 해석적으로. 통계량은
이고 자유도는 \((14, 11)\)이다. 단측 p-값은 \(P(F_{14,11} \ge 2.086420) = 0.112914\)로 기각하지 못한다.
기각의 문턱은 \(F_{0.95}(14,11) = 2.738648\)이므로, 관측된 표준편차의 비가
이어야 한다. 관측값은 \(1.3/0.9 = 1.444444\)로 모자란다. 표준편차가 \(1.44\)배, 분산으로는 \(2.09\)배 차이인데도 기각하지 못하며, 기각하려면 표준편차가 \(1.65\)배는 되어야 한다. 양측이라면 채택역이 \(F\)로 \((0.323145,\ 3.358810)\), 표준편차의 비로는 \((0.568,\ 1.833)\)이다. 열다섯 개와 열두 개로는 "산포가 1.8배까지 달라도 통과"인 셈이다.
(2) 첨도가 모든 것을 정한다. 평균 검정에서는 표본이 커지면 중심극한정리가 비정규성을 씻어 주지만, \(F\) 검정은 분자와 분모가 둘 다 2차 적률이라 그 보호를 받지 못한다. 5.3절 비정규 모집단에서의 분산비에서 유도한 결과를 옮기면, \(\log(S_1^2/S_2^2)\)의 참 표준편차가 정규이론이 믿는 것의
배이고(\(\beta_2\)는 모집단 첨도), 따라서 명목 \(\alpha\) 양측검정의 극한 오류율이
이다. 단측은 같은 식에서 \(z_{1-\alpha/2}\)를 \(z_{1-\alpha}\)로 바꾼 한쪽 꼬리다. 표본크기가 들어가지 않는다. 자료를 아무리 모아도 이 값으로 가고, 정규(\(\beta_2 = 3\))에서만 \(r = 1\)이 되어 명목이 지켜진다.
\(\alpha = 0.05\)에서 네 모집단의 극한 양측 오류율은 균등(\(\beta_2 = 1.8\)) \(0.0019\), 정규 \(0.0500\), 지수와 \(t_5\)(\(\beta_2 = 9\)) \(0.3271\), 로그정규(\(\beta_2 = 113.9\)) \(0.7942\)다.
수치적으로.
F_stat, p, reject = test_ratio_two_variances(
n1=15, s1=1.3, n2=12, s2=0.9, theta0=1.0, alt="greater"
)
print("F:", F_stat, "p:", p, "reject:", reject)
# 두 집단의 역할을 바꾸면 F는 역수가 되고 대립가설의 방향도 뒤집힌다.
# 제대로 바꾸면 p-값은 같아야 한다.
F2, p2, reject2 = test_ratio_two_variances(
n1=12, s1=0.9, n2=15, s2=1.3, theta0=1.0, alt="less"
)
print("F:", F2, "p:", p2, "reject:", reject2)
출력:
F: 2.0864197530864197 p: 0.11291422151817565 reject: False
F: 0.47928994082840237 p: 0.11291422151817561 reject: False
import numpy as np
from scipy import stats
n1, n2 = 15, 12
d1, d2 = n1 - 1, n2 - 1
# 기각에 필요한 관측 분산비
up_one = stats.f.ppf(0.95, d1, d2)
hi = stats.f.ppf(0.975, d1, d2)
lo = stats.f.ppf(0.025, d1, d2)
print(f"관측된 s1/s2 = {1.3 / 0.9:.6f}, s1^2/s2^2 = {1.3**2 / 0.9**2:.6f}")
print(f"단측 5% 임계값 F_0.95(14,11) = {up_one:.6f}"
f" → s1/s2 가 {np.sqrt(up_one):.6f} 이상이어야 기각")
print(f"양측 5% 채택역 ({lo:.6f}, {hi:.6f})"
f" → s1/s2 로는 ({np.sqrt(lo):.6f}, {np.sqrt(hi):.6f})")
# 정규성이 깨지면 이 임계값이 무슨 뜻을 잃는가
rng = np.random.default_rng(11)
B = 200_000
gens = (
("정규", 3.0, lambda size: rng.standard_normal(size)),
("균등", 1.8, lambda size: rng.uniform(0, 1, size)),
("t(5)", 9.0, lambda size: rng.standard_t(5, size)),
("지수", 9.0, lambda size: rng.exponential(1.0, size)),
("로그정규", 113.9, lambda size: rng.lognormal(0.0, 1.0, size)),
)
print(f"\nH0 가 참인 자료로 재는 실제 오류율 (반복 {B:,} 회, 오차 약 ±0.001)")
print(f"{'모집단':>9} {'beta2':>7} {'양측 실제':>10} {'양측 극한':>10}"
f" {'단측 실제':>10} {'단측 극한':>10}")
for name, b2, gen in gens:
x1, x2 = gen((B, n1)), gen((B, n2))
F = x1.var(1, ddof=1) / x2.var(1, ddof=1)
r = np.sqrt(2 / (b2 - 1))
print(f"{name:>9} {b2:7.1f} {np.mean((F < lo) | (F > hi)):10.4f} "
f"{2 * stats.norm.sf(1.959964 * r):10.4f} "
f"{np.mean(F > up_one):10.4f} {stats.norm.sf(1.644854 * r):10.4f}")
출력:
관측된 s1/s2 = 1.444444, s1^2/s2^2 = 2.086420
단측 5% 임계값 F_0.95(14,11) = 2.738648 → s1/s2 가 1.654886 이상이어야 기각
양측 5% 채택역 (0.323145, 3.358810) → s1/s2 로는 (0.568458, 1.832706)
H0 가 참인 자료로 재는 실제 오류율 (반복 200,000 회, 오차 약 ±0.001)
모집단 beta2 양측 실제 양측 극한 단측 실제 단측 극한
정규 3.0 0.0496 0.0500 0.0504 0.0500
균등 1.8 0.0076 0.0019 0.0119 0.0047
t(5) 9.0 0.1403 0.3271 0.1088 0.2054
지수 9.0 0.2465 0.3271 0.1723 0.2054
로그정규 113.9 0.4405 0.7942 0.2743 0.4134
(1)이 맞는다. \(F = 2.086420\), 단측 \(p = 0.112914\)이고 둘째 호출이 \(F\)의 역수 \(0.479290\)에서 같은 p-값을 준다(보기 1의 항등식). 기각 문턱 \(s_1/s_2 \ge 1.654886\)도 확인되었다.
(2)에서 정규 줄만 명목을 지킨다. 양측 \(0.0496\), 단측 \(0.0504\)로 반복 20만 회의 몬테카를로 오차 \(\pm0.001\) 안에서 \(0.05\)다. 나머지는 모두 벗어난다. 지수모집단에서 양측 실제 오류율이 \(0.2465\), 로그정규에서 \(0.4405\)다. 명목 5% 검정이 실제로는 25%와 44%다.
그런데 실제값이 극한값보다 작다. 지수에서 \(0.2465\) 대 \(0.3271\), 로그정규에서 \(0.4405\) 대 \(0.7942\)다. 어긋남이 아니라 5.3절이 설명한 수렴 방향이다. \(n\)이 작을 때는 \(F\) 분포 자체가 넓어 왜곡을 가려 주고, 표본을 키우면 그 가림막이 걷히면서 오류율이 올라간다. 5.3절의 모의실험에서 지수모집단이 \(n = 20\)에서 \(0.264\), \(n = 50\)에서 \(0.298\), \(n = 200\)에서 \(0.320\)으로 극한 \(0.327\)에 다가갔다. 여기 \((15, 12)\)의 \(0.2465\)는 그 수열의 앞자리에 놓이는 값이다.
균등 줄은 반대 방향의 같은 고장이다. \(\beta_2 = 1.8 < 3\)이라 실제 오류율이 \(0.0076\)으로 내려앉는다. 거짓 양성은 줄지만 그만큼 검정력을 버린 것이고, 첨도가 3에서 벗어나면 어느 쪽이든 명목이 지켜지지 않는다는 점이 요점이다.
그래서 이 함수의 독스트링이 말하는 바가 옳다. 등분산을 확인하려고 \(F\) 검정을 먼저 돌리는 것은, 정규성이라는 더 센 가정을 확인하지 않은 채 쓰는 일이 된다. 합동 \(t\)를 쓸지 말지 고민하는 대신 Welch를 쓰면 그 고민 자체가 없어진다(5.3절).
해석¶
\(s_1 = 1.3\), \(n_1 = 15\)와 \(s_2 = 0.9\), \(n_2 = 12\)로 \(H_0\colon \sigma_1^2 = \sigma_2^2\) 대 \(H_1\colon \sigma_1^2 > \sigma_2^2\)을 검정한다. F-통계량은
자유도 \((14, 11)\)에서 단측 p-값 \(P(F_{14,11} \geq 2.086)\)이 \(H_0\)의 기각 여부를 결정한다.
연습문제¶
연습문제 1. 두 생산라인의 표본에서 \(s_1 = 4.2\) (\(n_1 = 20\)), \(s_2 = 3.1\) (\(n_2 = 25\))을 얻었다. \(\alpha = 0.05\)에서 \(H_0\colon \sigma_1^2 = \sigma_2^2\) 대 \(H_1\colon \sigma_1^2 \neq \sigma_2^2\)을 검정하라.
풀이
F-통계량은
자유도는 \((19, 24)\)이다. 양측검정의 p-값은 \(2 \times \min(P(F \leq 1.836),\, P(F \geq 1.836))\)이다. 표나 소프트웨어로 구하면 \(P(F_{19,24} \geq 1.836) \approx 0.080\)이므로 양측 p-값은 약 \(0.160\)이다. \(0.160 > 0.05\)이므로 \(H_0\)을 기각하지 못한다. \(\square\)
연습문제 2. 독립인 두 카이제곱 확률변수의 비에서 F-검정통계량을 유도하라.
풀이
정규성 아래에서 \(i = 1, 2\)에 대해 \((n_i - 1)S_i^2/\sigma_i^2 \sim \chi^2_{n_i - 1}\)이고 둘은 독립이다. F-분포는 독립인 두 카이제곱 변수를 각각의 자유도로 나눈 비로 정의된다:
\(H_0\colon \sigma_1^2/\sigma_2^2 = \theta_0\) 아래에서 이는 \(F = (S_1^2/S_2^2)/\theta_0 \sim F_{n_1-1,\,n_2-1}\)로 간단해진다. \(\square\)
연습문제 3. 분산에 대한 F-검정이 평균에 대한 t-검정보다 비정규성에 민감한 이유를 설명하라. 어떤 대안이 있는가?
풀이
t-검정은 중심극한정리 덕분에 \(n\)이 적당하면 \(\bar{X}\)의 표본분포가 근사적으로 정규이므로 웬만한 비정규성에 로버스트하다. 그러나 \(S^2\)의 분포는 모집단의 첨도에 의존한다. 꼬리가 두꺼운 분포에서는 \(S^2\)의 변동이 카이제곱분포가 예측하는 것보다 훨씬 커서 F-비가 왜곡된다. 따라서 분산에 대한 F-검정은 비정규성 아래에서 제1종 오류율이 부풀려진다.
대안:
- Levene 검정: 평균으로부터의 절대편차를 쓰며 비정규성에 로버스트하다.
- Brown–Forsythe 검정: Levene 검정에서 평균 대신 중앙값을 쓴다.
- Bartlett 검정: 가능도비 검정이며 비정규성에는 여전히 민감하지만 분산분석 맥락에서 흔히 쓰인다.
- 붓스트랩 방법: 재표본추출에 기반한 비모수적 분산 비교. \(\square\)
연습문제 4. \(F \sim F_{\nu_1, \nu_2}\)이면 \(1/F \sim F_{\nu_2, \nu_1}\)임을 보여라.
풀이
정의에 의해 독립인 \(U \sim \chi^2_{\nu_1}\), \(V \sim \chi^2_{\nu_2}\)에 대해 \(F = (U/\nu_1)/(V/\nu_2)\)이다. 그러면
이는 \(\chi^2_{\nu_2}/\nu_2\)와 \(\chi^2_{\nu_1}/\nu_1\)의 비이다. 정의에 의해 이것이 \(F_{\nu_2,\nu_1}\)이다. \(\square\)
연습문제 5. \(\alpha = 0.10\), \(n_1 = 10\), \(n_2 = 8\)인 양측 F-검정에서 \(P(F_L < F < F_U) = 0.90\)이 되는 임계값 \(F_L\)과 \(F_U\)를 구하라.
풀이
자유도는 \((\nu_1, \nu_2) = (9, 7)\)이다. 위쪽 임계값은
아래쪽 임계값은 역수 성질을 쓴다:
채택역은 \(0.304 < F < 3.677\)이다. 관측된 F가 이 구간 밖이면 \(H_0\)을 기각한다. \(\square\)
연습문제 6. 연습문제 4의 성질 \(1/F\sim F_{\nu_2,\nu_1}\)을 수치로 확인하고, 이를 이용해 양측 임계값을 구하는 함수를 작성하라. 연습문제 1과 5의 답을 이 함수로 검산하라.
풀이
확인할 항등식.
즉 아래쪽 임계값은 자유도를 뒤바꾼 위쪽 임계값의 역수다.
import numpy as np
from scipy import stats
print(f"{'ν1':>4s} {'ν2':>4s} {'F(0.025)':>10s} {'F(0.975)':>10s} "
f"{'1/F(0.975; ν2,ν1)':>19s}")
for v1, v2 in [(9, 7), (19, 24), (15, 20), (5, 5), (30, 10)]:
lo = stats.f.ppf(0.025, v1, v2)
hi = stats.f.ppf(0.975, v1, v2)
check = 1 / stats.f.ppf(0.975, v2, v1)
print(f"{v1:4d} {v2:4d} {lo:10.5f} {hi:10.5f} {check:19.5f}")
def f_test_var_ratio(s1, n1, s2, n2, alpha=0.05):
"""양측 F 검정. (F, p, 아래 임계값, 위 임계값) 을 돌려준다."""
d1, d2 = n1 - 1, n2 - 1
F = s1**2 / s2**2
p = 2 * min(stats.f.cdf(F, d1, d2), stats.f.sf(F, d1, d2))
return (F, p,
stats.f.ppf(alpha / 2, d1, d2),
stats.f.ppf(1 - alpha / 2, d1, d2))
F, p, lo, hi = f_test_var_ratio(4.2, 20, 3.1, 25)
print(f"\n연습문제 1: F={F:.4f} p={p:.4f} 기각역 밖: ({lo:.4f}, {hi:.4f})")
print(f" → {'기각' if not (lo < F < hi) else '기각하지 못함'}")
_, _, lo, hi = f_test_var_ratio(1, 10, 1, 8, alpha=0.10)
print(f"연습문제 5: α=0.10, ν=(9,7) F_L={lo:.4f} F_U={hi:.4f}")
ν1 ν2 F(0.025) F(0.975) 1/F(0.975; ν2,ν1)
9 7 0.23826 4.82322 0.23826
19 24 0.40778 2.34515 0.40778
15 20 0.36286 2.57310 0.36286
5 5 0.13993 7.14638 0.13993
30 10 0.39822 3.31102 0.39822
연습문제 1: F=1.8356 p=0.1600 기각역 밖: (0.4078, 2.3452)
→ 기각하지 못함
연습문제 5: α=0.10, ν=(9,7) F_L=0.3037 F_U=3.6767
마지막 두 열이 소수점 다섯째 자리까지 일치한다. 항등식이 확인됐다.
\(\nu_1=\nu_2=5\)인 행에 주목하라. 임계값이 \((0.140,\ 7.146)\)이다. 분산비가 7배가 넘어야 기각한다는 뜻이다. 자유도가 작을 때 F 검정이 얼마나 둔한지 보여 준다.
왜 항등식이 유용한가.
- 옛 표에는 위쪽 꼬리만 실려 있었다. 아래쪽 임계값을 이 관계로 구했다.
- 수치적으로 안정적이다. 아주 작은 분위수를 직접 계산하는 것보다 역수를 취하는 쪽이 정밀도가 높을 수 있다.
- 개념적으로, 어느 집단을 "1번"이라 부르든 결론이 같아야 한다는 대칭성을 보장한다.
연습문제 1을 다시 보면. \(F=1.836\)이 \((0.408,\ 2.345)\) 안에 있어 기각하지 못한다. 다만 \(p=0.160\)이고 앞선 절에서 본 대로 "기각하지 못함"이 "분산이 같음"은 아니다. 실제로 \(\sigma_1^2/\sigma_2^2\)의 95% 구간은
로, 4.5배 차이까지 자료와 양립한다.
연습문제 7. 연습문제 3이 말한 비정규성 민감도를 고치는 방법으로, 로그 분산비의 첨도 보정 구간을 구현하고 포함률을 재어라.
풀이
아이디어. \(\log S^2\)은 \(S^2\)보다 훨씬 정규에 가깝고, 그 분산이 모집단 첨도 \(\beta_2\)로 표현된다.
정규분포는 \(\beta_2=3\)이라 이 값이 대략 \(2/(n-1)\)이 된다. 첨도를 자료에서 추정해 꽂으면 비정규성을 보정할 수 있다(슈메이커의 방법).
import numpy as np
from scipy import stats
def ci_F(x, y, alpha=0.05):
"""고전적 F 구간."""
n1, n2 = len(x), len(y)
f = x.var(ddof=1) / y.var(ddof=1)
return (f / stats.f.ppf(1 - alpha / 2, n1 - 1, n2 - 1),
f / stats.f.ppf(alpha / 2, n1 - 1, n2 - 1))
def ci_log_normal(x, y, alpha=0.05):
"""정규성을 가정한 로그 분산비 구간 (κ=3 고정)."""
n1, n2 = len(x), len(y)
c = np.log(x.var(ddof=1) / y.var(ddof=1))
se = np.sqrt(2 / (n1 - 1) + 2 / (n2 - 1))
z = stats.norm.ppf(1 - alpha / 2)
return np.exp(c - z * se), np.exp(c + z * se)
def ci_kurtosis(x, y, alpha=0.05):
"""첨도를 자료에서 추정해 보정한 구간."""
def kur(a):
m = a.mean()
return ((a - m)**4).mean() / (((a - m)**2).mean())**2
n1, n2 = len(x), len(y)
c = np.log(x.var(ddof=1) / y.var(ddof=1))
se = np.sqrt((kur(x) - (n1 - 3) / (n1 - 1)) / n1
+ (kur(y) - (n2 - 3) / (n2 - 1)) / n2)
z = stats.norm.ppf(1 - alpha / 2)
return np.exp(c - z * se), np.exp(c + z * se)
rng = np.random.default_rng(99)
M, n = 10_000, 30
gens = {"정규": lambda k: rng.standard_normal(k),
"균등": lambda k: rng.uniform(-1, 1, k),
"t(5)": lambda k: rng.standard_t(5, k),
"지수": lambda k: rng.exponential(1, k),
"로그정규": lambda k: rng.lognormal(0, 1, k)}
print(f"{'분포':>8s} {'F 구간':>9s} {'로그(κ=3)':>11s} {'첨도 보정':>11s}")
for name, gen in gens.items():
a = b = c = 0
for _ in range(M):
x, y = gen(n), gen(n) # 참 분산비 = 1
lo, hi = ci_F(x, y); a += lo <= 1 <= hi
lo, hi = ci_log_normal(x, y); b += lo <= 1 <= hi
lo, hi = ci_kurtosis(x, y); c += lo <= 1 <= hi
print(f"{name:>8s} {a / M:9.4f} {b / M:11.4f} {c / M:11.4f}")
분포 F 구간 로그(κ=3) 첨도 보정
정규 0.9469 0.9419 0.9205
균등 0.9973 0.9963 0.9512
t(5) 0.8319 0.8232 0.8912
지수 0.7260 0.7163 0.8508
로그정규 0.4742 0.4665 0.7415
첨도 보정이 크게 개선한다.
| 분포 | F 구간 | 첨도 보정 | 개선 |
|---|---|---|---|
| 정규 | 0.947 | 0.921 | \(-0.026\)(약간 손해) |
| \(t(5)\) | 0.832 | 0.891 | \(+0.06\) |
| 지수 | 0.726 | 0.851 | \(+0.13\) |
| 로그정규 | 0.474 | 0.742 | \(+0.27\) |
그래도 충분하지 않다. 로그정규에서 0.742는 여전히 0.95와 멀다. 첨도 자체를 30개로 추정하는 것이 불안정하기 때문이다. 4차 모멘트는 표본이 아주 커야 안정된다.
정규에서 0.921로 약간 손해를 본다. 알려진 \(\beta_2=3\) 대신 추정값을 쓴 대가다.
붓스트랩과 비교.
rng = np.random.default_rng(1234)
def ci_boot(x, y, B, alpha, rng):
"""로그 분산비의 백분위 붓스트랩 구간."""
n1, n2 = len(x), len(y)
bs = np.empty(B)
for b in range(B):
xb = x[rng.integers(0, n1, n1)]
yb = y[rng.integers(0, n2, n2)]
bs[b] = np.log(xb.var(ddof=1) / yb.var(ddof=1))
lo, hi = np.percentile(bs, [100 * alpha / 2, 100 * (1 - alpha / 2)])
return np.exp(lo), np.exp(hi)
M, n = 2_000, 30
for name, gen in [("정규", lambda k: rng.standard_normal(k)),
("t(5)", lambda k: rng.standard_t(5, k)),
("로그정규", lambda k: rng.lognormal(0, 1, k))]:
c = 0
for _ in range(M):
x, y = gen(n), gen(n)
lo, hi = ci_boot(x, y, 399, 0.05, rng)
c += lo <= 1 <= hi
print(f"{name:>8s} 붓스트랩 포함률 {c / M:.4f}")
정규 붓스트랩 포함률 0.9250
t(5) 붓스트랩 포함률 0.9175
로그정규 붓스트랩 포함률 0.8860
붓스트랩이 가장 고르다(0.886~0.925). 어느 분포에서도 크게 무너지지 않지만, 어디서도 0.95에 정확히 닿지 않는다. BCa 보정을 더하면 개선된다.
결론.
| 상황 | 권장 |
|---|---|
| 정규성이 확인됨 | F 구간 |
| 가벼운 이탈, \(n\ge30\) | 첨도 보정 |
| 심한 치우침 | 붓스트랩(BCa) |
| \(n\)이 작고 비정규 | 분산비의 구간 추정을 포기하거나 순위 기반 방법 |
가장 중요한 교훈. 분산비의 추론은 어떤 방법으로도 평균 비교만큼 신뢰할 수 없다. 2차 모멘트에 관한 추론은 본질적으로 4차 모멘트에 의존하고, 4차 모멘트는 추정하기 어렵다.
연습문제 8. 집단이 셋 이상일 때 모든 쌍에 F 검정을 하면 어떻게 되는가? 옴니버스 검정과 비교하라.
풀이
import numpy as np
from scipy import stats
from itertools import combinations
rng = np.random.default_rng(2718)
M, n = 10_000, 20
print(f"{'집단 수':>7s} {'쌍':>4s} {'쌍별 F(무보정)':>15s} {'본페로니':>10s} "
f"{'바틀렛':>8s} {'B-F':>8s}")
for k in [2, 3, 5, 8]:
n_pair = k * (k - 1) // 2
a = b = c = d = 0
for _ in range(M):
g = [rng.standard_normal(n) for _ in range(k)] # 모든 분산이 같다
ps = []
for i, j in combinations(range(k), 2):
F = g[i].var(ddof=1) / g[j].var(ddof=1)
ps.append(2 * min(stats.f.cdf(F, n - 1, n - 1),
stats.f.sf(F, n - 1, n - 1)))
ps = np.array(ps)
a += (ps < 0.05).any()
b += (ps < 0.05 / n_pair).any()
c += stats.bartlett(*g).pvalue < 0.05
d += stats.levene(*g, center='median').pvalue < 0.05
print(f"{k:7d} {n_pair:4d} {a / M:15.4f} {b / M:10.4f} "
f"{c / M:8.4f} {d / M:8.4f}")
집단 수 쌍 쌍별 F(무보정) 본페로니 바틀렛 B-F
2 1 0.0489 0.0489 0.0489 0.0390
3 3 0.1237 0.0449 0.0538 0.0392
5 10 0.2795 0.0390 0.0498 0.0325
8 28 0.4994 0.0310 0.0498 0.0284
집단이 8개면 분산이 모두 같은데도 50%의 확률로 "어떤 쌍이 다르다"는 결론이 나온다.
| \(k\) | 쌍 개수 | 무보정 FWER |
|---|---|---|
| 2 | 1 | 0.049 |
| 3 | 3 | 0.124 |
| 5 | 10 | 0.280 |
| 8 | 28 | 0.499 |
쌍의 개수가 \(k^2\)에 비례해 늘기 때문이다. 다만 쌍들이 서로 상관돼 있어(\(S_1^2/S_2^2\)와 \(S_1^2/S_3^2\)이 \(S_1^2\)을 공유) \(1-(1-\alpha)^{28}=0.76\)까지는 가지 않는다.
세 가지 해법.
1 — 본페로니. FWER이 0.031~0.045로 수준을 지킨다. 다만 \(k\)가 커질수록 보수적이 된다(0.031).
2 — 바틀렛 옴니버스. 0.049~0.054로 가장 정확하다. "어딘가 분산이 다른가"라는 하나의 질문에 답한다. 단, 앞 절에서 본 대로 정규성에 예민하다.
3 — 브라운·포사이드. 0.028~0.039로 약간 보수적이지만 비정규성에 강하다. 실무의 기본 선택이다.
옴니버스와 쌍별의 역할이 다르다.
| 질문 | 방법 |
|---|---|
| "분산이 모두 같은가" | 옴니버스(바틀렛·B-F) |
| "어느 집단이 다른가" | 쌍별 + 다중비교 보정 |
| ANOVA의 가정 확인 | 하지 말고 웰치 ANOVA를 쓴다 |
마지막 행이 중요하다. 일원배치 ANOVA 전에 등분산을 검정하는 것은 앞 절의 2단계 절차와 같은 문제를 일으킨다. 웰치 ANOVA를 기본으로 쓰면 그 단계 자체가 필요 없다.
쌍별 비교가 정말 필요한 경우. 품질관리에서 "어느 라인이 변동이 큰가"를 찾아 개선해야 할 때처럼, 후속 조치의 대상을 특정해야 하는 상황이다. 이때는 보정을 하되 탐색적이라고 명시한다.
연습문제 9. 공정능력지수 \(C_p\)를 두 공정에서 비교하는 문제로 분산비 검정을 적용하라. 유의성과 실무적 의미를 함께 논하라.
풀이
정의. 규격 하한 LSL, 상한 USL에 대해
\(C_p\)는 산포만, \(C_{pk}\)는 중심 이탈까지 반영한다. \(C_p\)가 \(\sigma\)의 역수에 비례하므로, 두 공정의 \(C_p\) 비는 표준편차 비의 역수다.
import numpy as np
from scipy import stats
LSL, USL = 9.5, 10.5
processes = [("기존 공정", 10.00, 0.12, 30),
("신 공정", 10.02, 0.09, 30)]
for name, mu, s, n in processes:
cp = (USL - LSL) / (6 * s)
cpk = min(USL - mu, mu - LSL) / (3 * s)
ppm = (stats.norm.cdf(LSL, mu, s) + stats.norm.sf(USL, mu, s)) * 1e6
print(f"{name}: 평균 {mu:.3f} SD {s:.3f} "
f"Cp={cp:.4f} Cpk={cpk:.4f} 불량 {ppm:.1f} ppm")
s1, n1 = 0.12, 30
s2, n2 = 0.09, 30
d1, d2 = n1 - 1, n2 - 1
F = s1**2 / s2**2
p = 2 * min(stats.f.cdf(F, d1, d2), stats.f.sf(F, d1, d2))
lo = F / stats.f.ppf(0.975, d1, d2)
hi = F / stats.f.ppf(0.025, d1, d2)
print(f"\n분산비 F = {F:.4f}, p = {p:.4f}")
print(f" σ1²/σ2² 의 95% CI ({lo:.4f}, {hi:.4f})")
print(f" σ1/σ2 의 95% CI ({np.sqrt(lo):.4f}, {np.sqrt(hi):.4f})")
print(f" Cp 비(기존/신) = {1 / np.sqrt(F):.4f}, "
f"95% CI ({1 / np.sqrt(hi):.4f}, {1 / np.sqrt(lo):.4f})")
기존 공정: 평균 10.000 SD 0.120 Cp=1.3889 Cpk=1.3889 불량 30.9 ppm
신 공정: 평균 10.020 SD 0.090 Cp=1.8519 Cpk=1.7778 불량 0.1 ppm
분산비 F = 1.7778, p = 0.1271
σ1²/σ2² 의 95% CI (0.8462, 3.7351)
σ1/σ2 의 95% CI (0.9199, 1.9326)
Cp 비(기존/신) = 0.7500, 95% CI (0.5174, 1.0871)
통계적으로는 유의하지 않다(\(p=0.127\)). 그런데 실무적으로는 이야기가 다르다.
| 기존 | 신 | 의미 | |
|---|---|---|---|
| \(C_p\) | 1.389 | 1.852 | 1.33 → 1.67 등급 상승 |
| \(C_{pk}\) | 1.389 | 1.778 | 중심이 약간 벗어나도 여전히 우수 |
| 이론 불량률 | 30.9 ppm | 0.1 ppm | 약 300배 차이 |
불량률 300배 차이가 "유의하지 않다"로 보고된다. 이것이 이 문제의 핵심이다.
왜 이런 일이 생기는가.
- F 검정의 검정력이 낮다. 앞 절에서 본 대로 분산비 1.78을 군당 30개로 잡을 확률은 30%가 안 된다.
- \(C_p\)는 \(\sigma\)의 꼬리에 민감하다. 정규 가정 아래 \(\pm3\sigma\) 바깥의 확률은 \(\sigma\)가 25% 줄면 극적으로 작아진다. 선형이 아닌 관계다.
따라서 결정을 \(p\)-값에 맡기면 안 된다.
- 구간이 말해 주는 것: \(C_p\) 비의 95% 구간이 \((0.517,\ 1.087)\)이다. 즉 신 공정이 최대 1.9배 좋을 수도, 약간 나쁠 수도 있다. 아직 결정 불가다.
- 필요한 조치: 표본을 늘린다. 앞 절의 계산으로 분산비 1.78을 80% 검정력으로 잡으려면 군당 90개 남짓이 필요하다.
실무 절차.
- 먼저 관리상태를 확인한다. 공정이 통계적 관리상태에 있지 않으면 \(C_p\) 자체가 무의미하다.
- 정규성을 확인한다. \(C_p\)의 불량률 해석은 정규성에 전적으로 의존한다. 치우친 공정에서는 \(C_p\)가 크게 오도한다.
- \(C_p\)와 \(C_{pk}\)를 함께 본다. \(C_p\)만 좋고 \(C_{pk}\)가 나쁘면 중심을 맞추는 것이 우선이다(여기서는 신 공정의 평균 10.02가 약간 벗어나 \(C_{pk}\)가 \(C_p\)보다 낮다).
- 구간으로 보고한다. \(C_p\)의 점추정만 보고하는 것은 \(n=30\)에서 위험하다.
- 비용으로 환산한다. 30.9 ppm과 0.1 ppm의 차이가 연간 얼마인지가 실제 의사결정 근거다.
통계적 유의성과 실무적 중요성이 어긋나는 전형적인 사례다. 여기서는 실무적 중요성이 압도적으로 크고 통계적 증거가 부족한 쪽이므로, 답은 "기각하지 못했으니 기존 공정 유지"가 아니라 "자료를 더 모으라"다.
연습문제 10. 분산비 검정을 쓸 때의 점검 목록을 만들어라.
풀이
사용 전 점검.
- [ ] 이것이 진짜 연구 질문인가? 평균 검정의 가정 확인이 목적이라면 하지 말고 웰치를 쓴다.
- [ ] 두 표본이 독립인가? 대응 자료라면 차이의 분산을 봐야 한다.
- [ ] 각 표본이 확률표본인가? 군집이나 시계열 구조가 있으면 유효 표본크기가 작다.
- [ ] 정규성을 그림으로 확인했는가? 정규분위수그림이 검정보다 유용하다.
- [ ] 이상점이 분산을 지배하지 않는가? 하나의 관측값이 \(S^2\)의 절반을 만들 수 있다.
- [ ] 표본이 충분한가? 분산비 2를 80% 검정력으로 잡으려면 군당 68개가 필요하다.
검정 선택.
| 조건 | 검정 |
|---|---|
| 정규에 가깝고 집단 2개 | F 검정 |
| 비정규, 집단 2개 | 브라운·포사이드 |
| 집단 3개 이상, 정규 | 바틀렛 |
| 집단 3개 이상, 비정규 | 브라운·포사이드 |
| 심한 치우침 | 로그변환 후 검정, 또는 붓스트랩 |
| 이상점 있음 | 로버스트 산포 측도(MAD, \(Q_n\)) 비교 |
보고 시 점검.
- [ ] 분산비의 신뢰구간을 함께 보고했는가
- [ ] 표준편차 척도로도 제시했는가(분산비 4 = 표준편차 2배)
- [ ] 어느 검정을 왜 썼는지 밝혔는가
- [ ] "기각하지 못함"을 "같음"으로 쓰지 않았는가
- [ ] 표본크기가 작다면 한계를 명시했는가
자주 하는 실수 다섯.
- 등분산 사전검정 후 검정 선택 — 2단계 절차의 문제(앞 절).
- 큰 쪽을 분자에 두고 상단 \(\alpha\) 임계값 — 수준이 두 배.
- 비정규 자료에 F 검정 — 로그정규에서 수준 0.48까지.
- \(p>0.05\)를 등분산의 증거로 사용 — 가장 흔하고 해로운 오해.
- 분산비를 배수로 잘못 읽기 — 분산비 4는 산포가 4배가 아니라 2배다.
핵심 요약 세 줄.
- 분산 비교는 평균 비교보다 훨씬 어렵다. 정규성에 예민하고 검정력이 낮다.
- 가정 확인용으로는 쓰지 않는다. 웰치·브라운·포사이드처럼 가정을 덜 요구하는 방법을 기본으로 삼는다.
- 분산 자체가 관심일 때는 구간과 실무적 의미를 함께 본다. 공정능력이나 품질 지표로 환산하면 \(p\)-값보다 훨씬 많은 것을 말해 준다.
정리하며¶
분산 비 검정의 구현에서 챙길 점들이다.
ddof=1을 쓴다. 통계량이 베셀 수정한 표본분산의 비이며,numpy기본값은ddof=0이다.- 양측 \(p\) 값에 주의한다. \(F\) 분포가 비대칭이라 작은 쪽 꼬리 확률의 두 배를 쓰되 \(1\) 을 넘지 않게 자른다.
- 자유도의 순서가 중요하다. \(F_{n_1-1,\,n_2-1}\) 에서 분자와 분모를 바꾸면 다른 분포이며, 뒤집기 성질 \(F_{1-\alpha,d_1,d_2}=1/F_{\alpha,d_2,d_1}\) 로 서로 연결된다.
- \(\theta_0\ne1\) 도 검정할 수 있다. "한 공정의 분산이 다른 공정의 두 배를 넘는가" 같은 물음이 그 형태다.
- 정규성 민감도를 다시 확인하라. 카이제곱 분산 검정과 마찬가지이며, 합동 \(t\) 검정의 사전 확인 용도로는 쓰지 않는다.
다음 절 이표본 평균 검정으로 넘어간다.