왜 사후검정이 필요할까?
분산분석(ANOVA)은 평균 차이가 "어디선가" 유의미하게 난다는 건 알려주지만,
"어디 그룹끼리?", 즉 구체적으로 어떤 그룹 간에 차이가 있는지는 알려주지 않습니다.
그래서 등장하는 것이 바로 사후검정(Post-hoc Test)!
예시
지역 A, B, C에서 커피 소비량을 조사했더니 ANOVA 결과가 유의미했다고 합시다.
그럼 이제 질문은?
"A랑 B가 차이가 나?",
"A랑 C는 어때?",
"B랑 C는??"
➡️ 이렇게 모든 그룹쌍을 비교해봐야 하는데,
그냥 다 t-test 하면 1종 오류(Type I Error)가 엄청나게 쌓임.
용어 설명
용어 | 의미 |
1종 오류 | 귀무가설이 맞는데도 기각함 (FP) |
2종 오류 | 귀무가설이 틀렸는데도 채택함 (FN) |
검정력 | 대립가설이 맞을 때, 귀무가설을 올바르게 기각하는 확률 |
유의수준(α) | 1종 오류를 허용하는 기준치 (보통 0.05) |
실험
👉 "다중 비교(Multiple Comparisons)는 1종 오류(Type I Error)를 증가시킨다."
우리는 실제로는 집단 간 차이가 전혀 없는 3개 그룹을 생성해봅시다.
이 그룹들 사이를 여러 번 t-검정으로 비교하면
"유의미한 차이가 없는데도 유의하다고 나올 확률" (1종 오류) 이 얼마나 되는지 볼 수 있습니다.
✅ 실험 코드: 3개 그룹 비교에서 t-test 1000번 반복
import numpy as np
from scipy.stats import ttest_ind
np.random.seed(42)
def run_ttest_simulation(n, iterations):
type1_error_count = 0
for _ in range(iterations):
# 실제로는 모두 평균 100인 동일한 그룹
group_a = np.random.normal(loc=100, scale=10, size=n)
group_b = np.random.normal(loc=100, scale=10, size=n)
group_c = np.random.normal(loc=100, scale=10, size=n)
# 3쌍 비교: A-B, A-C, B-C
p1 = ttest_ind(group_a, group_b).pvalue
p2 = ttest_ind(group_a, group_c).pvalue
p3 = ttest_ind(group_b, group_c).pvalue
# 유의수준 0.05보다 작은 p-value가 하나라도 있으면 → 1종 오류 발생
if p1 < 0.05 or p2 < 0.05 or p3 < 0.05:
type1_error_count += 1
return type1_error_count / iterations
# 1000 번 실행해보자
error_rate = run_ttest_simulation(30, 1000)
print(f"1종 오류가 발생한 비율: {error_rate:.3f}")
- 실제로는 평균이 모두 같은 3개의 그룹을 만듭니다.
- 가설을 설정하고, t-test 를 1000번 진행합니다.
- H0: 세 쌍의 평균이 모두 같다.
- H1: 세 쌍의 평균이 같지 않다.
- 실제로는 평균이 같으므로, 귀무가설이 기각되어서는 안됩니다.
- 하지만, 귀무가설이 참인데도 기각되는 오류가 발생하기도 합니다. 이를 1종오류라고 부릅니다.
실행 결과
1종 오류가 발생한 비율: 0.138
- t-test 를 여러번 진행하면서 오류의 발생 비율이 점점더 높아지고 있습니다.
- 즉, 실제로는 차이가 전혀 없는데도,
1000번 중 약 13.9%에서 유의미한 차이라고 판단됨
✅ 그럼 사후 검정을 사용하면?
from statsmodels.stats.multicomp import pairwise_tukeyhsd
import pandas as pd
# 가짜 데이터 준비
group = np.array(['A']*30 + ['B']*30 + ['C']*30)
value = np.random.normal(loc=100, scale=10, size=90)
df = pd.DataFrame({'group': group, 'value': value})
# Tukey HSD 수행
tukey = pairwise_tukeyhsd(endog=df['value'], groups=df['group'], alpha=0.05)
print(tukey)
실행 결과:
Multiple Comparison of Means - Tukey HSD, FWER=0.05
===================================================
group1 group2 meandiff p-adj lower upper reject
---------------------------------------------------
A B 0.0038 1.0 -6.4535 6.461 False
A C -0.405 0.9877 -6.8623 6.0523 False
B C -0.4088 0.9875 -6.866 6.0485 False
---------------------------------------------------
- 사후검정을 사용하면 p값이 엄청 큰 것을 볼 수 있습니다.
- 당연히 같은 평균값으로 데이터들을 만들었으니 각 그룹끼리 비교했을 때도, 평균이 같다고 나와야 합니다.
- 사후검정을 진행하면 t test를 여러번 진행한 것과 달리 p값이 정상적인 것을 볼 수 있습니다. (1에 수렴)
그래서 !!
이러한 다중 비교의 함정을 피하기 위해 사후검정을 사용해서 보다 상세한 결과값을 얻을 수 있습니다.
대표적인 사후검정 방법
1. 본페로니 보정 (Bonferroni)
- 가장 간단하고 직관적인 방법
- 유의수준을 비교 횟수만큼 나눠서 보수적으로 검정
- 예: 유의수준 0.05 / 비교 3쌍 = 각 쌍당 0.0167
예시 데이터 생성
import numpy as np
import pandas as pd
odors = ['Lavender', 'Rosemary', 'Peppermint']
# 각 향기에서 손님이 머물다 간 시간(분) 데이터
# 향기가 머무는 시간에 영향을 미치는지 확인하자.
minutes_lavender = [10, 12, 11, 9, 8, 12, 11, 10, 10]
minutes_rose = [14, 15, 13, 16, 14, 15, 14, 13, 14, 16]
minutes_mint = [18, 17, 18, 16, 17, 19, 18, 17]
anova_data = pd.DataFrame({
'odor': np.array(["Lavender"] * 9 + ["Rosemary"] * 10 + ["Peppermint"] * 8),
'minutes': minutes_lavender + minutes_rose + minutes_mint
})
각 향기에 따라 손님이 머무르는 시간이 달라지는지를 확인해보는 예제 데이터를 생성해보겠습니다.
시각화
import seaborn as sns
import matplotlib.pyplot as plt
# 시각화
sns.boxplot(x='odor', y='minutes', data=anova_data, palette='Set2')
plt.title('향기별 머무는 시간')
plt.ylabel('머무는 시간(분)')
plt.xlabel('')
plt.show()
- 시각화 한 결과값을 보면 각 향기별로 평균이 전부 다른 것을 볼 수 있습니다.
본페로니 검정 수행 (Python 코드)
mc = MultiComparison(anova_data['minutes'], anova_data['odor'])
result = mc.allpairtest(stats.ttest_ind, method='bonf')
print(result[0]) # Bonferroni 적용된 결과
출력 결과:
Test Multiple Comparison ttest_ind
FWER=0.05 method=bonf
alphacSidak=0.02, alphacBonf=0.017
====================================================
group1 group2 stat pval pval_corr reject
----------------------------------------------------
Lavender Peppermint -12.7729 0.0 0.0 True
Lavender Rosemary -7.3878 0.0 0.0 True
Peppermint Rosemary 6.4552 0.0 0.0 True
----------------------------------------------------
- 3쌍 전부 p값이 상당히 작은 것을 볼 수 있습니다. (귀무가설 기각)
- 즉, 세쌍 모두 평균 값이 유의미하게 다릅니다.
2. 튜키 검정 (Tukey's Honestly Significant Difference)
- 가장 많이 쓰이는 사후검정 중 표준
- 모든 그룹 쌍 간 평균 차이를 동시에 비교
- 등분산성 가정이 필요하며, 전체 유의수준을 조정해서 검정함
튜키 검정 수행 (python 코드)
등분산성 만족여부 검사 (levene test)
minutes_lavender = [10, 12, 11, 9, 8, 12, 11, 10, 10]
minutes_rose = [14, 15, 13, 16, 14, 15, 14, 13, 14, 16]
minutes_mint = [18, 17, 18, 16, 17, 19, 18, 17]
# Levene Test 수행
stat, p = levene(minutes_lavender, minutes_mint, minutes_rose)
print(f"Levene 검정 통계량: {stat:.4f}")
print(f"p-value: {p:.4f}")
if p > 0.05:
print("✅ 등분산성 만족 → Tukey HSD 사용 가능")
else:
print("❌ 등분산성 불만족 → 다른 방법 고려 필요")
- Levene 검정은 여러 그룹 간 분산이 같은지(등분산성)를 검정하는 통계적 방법입니다.
- 이상치에 민감하지도 않고,
- 정규성 가정도 필요 없기에 실무에서 자주 쓰입니다.
정규성 검정 결과
Levene 검정 통계량: 0.2816
p-value: 0.7570
✅ 등분산성 만족 → Tukey HSD 사용 가능
- p-value 가 상당히 크게 나왔기 때문에
- 등분산성을 만족한다고 볼 수 있습니다.
튜키 검정 수행
from statsmodels.stats.multicomp import pairwise_tukeyhsd
tukey = pairwise_tukeyhsd(
endog=anova_data['minutes'], # 종속변수
groups=anova_data['odor'], # 그룹
alpha=0.05 # 유의수준
)
print(tukey)
출력 결과
Multiple Comparison of Means - Tukey HSD, FWER=0.05
===========================================================
group1 group2 meandiff p-adj lower upper reject
-----------------------------------------------------------
Lavender Peppermint 7.1667 0.0 5.801 8.5324 True
Lavender Rosemary 4.0667 0.0 2.7753 5.3581 True
Peppermint Rosemary -3.1 0.0 -4.4332 -1.7668 True
-----------------------------------------------------------
결과 해석
비교 | 결과 | 의미 |
라벤더 vs 페퍼민트 | 유의미한 차이 있음 | 라벤더가 평균 7분 더 길다 |
라벤더 vs 로즈마리 | 유의미한 차이 있음 | 라벤더가 평균 4분 더 길다 |
페퍼민트 vs 로즈마리 | 유의미한 차이 있음 | 페퍼민트가 평균 3분 더 짧다 |
- 분산분석(ANOVA)를 사용했을 때는 세 그룹간 평균 차이가 있는지 없는지만 확인할 수 있었는데,
- 사후 검정을 사용하면 보다 자세한 결과값을 얻을 수 있습니다.
앞서 시각화 한 결과값과 비교했을 때, 사후 검정을 진행하면 꽤나 많은 정보를 얻을 수 있다는 것을 알 수 있습니다.
결론
단계 | 내용 |
1. ANOVA 수행 | 평균 차이가 있는지 전체적으로 확인 |
2. 사후검정 수행 | 어디 그룹 간 차이인지 파악 |
사후검정이 중요한 이유 | 다중비교에서 발생할 수 있는 1종 오류 제어 |
2025.04.07 - [Data] - LS 빅데이터 스쿨 / 분산분석(ANOVA) / 수치형 데이터 비교 / 카이제곱 검정과의 차이점
LS 빅데이터 스쿨 / 분산분석(ANOVA) / 수치형 데이터 비교 / 카이제곱 검정과의 차이점
LS 빅데이터 스쿨에서 분산분석에 대해 배웠습니다.저번 시간에는 범주형 변수 분석에 유용한 카이제곱 검정에 대해 배웠으니, 이번 시간에는 수치형 데이터를 분석에 사용하는 분산분석에 대
kiminchae.tistory.com
'Data > 통계' 카테고리의 다른 글
T-검정과 F-검정 / 독립 표본 t 검정 / 대응 표본 t 검정 / Python 예제 / 시각화 (0) | 2025.04.10 |
---|---|
비모수 검정 모수 검정 비교 / 윌콕슨 순위합 검정 / 맨휘트니 U 검정 / 브루너 문젤 검정 / 크루스칼 왈리스 검정 (0) | 2025.04.08 |
분산분석(ANOVA) / 수치형 데이터 비교 분석 / 카이제곱 검정과의 차이점 (0) | 2025.04.07 |
카이제곱(Chi-Square) 검정 / 범주형 데이터 분석 / 독립성 검정 / 동질성 검정 / 적합도 검정 (1) | 2025.04.07 |
정규성 검정 / Shapiro-Wilk / Anderson-Darling / Kolmogorov–Smirnov / Q-Q Plot (0) | 2025.04.04 |