python, 전처리, 통계, 가설검정, 기계학습, 회귀, 분류, 군집, 모델 학습, 모델 평가
비모수 검정(Non-parametric Tests)은 모집단의 분포에 대한 가정 없이 데이터를 분석하는 통계적 방법이다. 정규성이나 등분산성 같은 모수 검정의 엄격한 가정을 만족하지 못할 때, 또는 데이터가 순서형이거나 이상치가 많을 때 사용한다. 비모수 검정은 원 데이터 값 대신 순위(rank)를 사용하여 집단 간 차이를 검정하므로 분포에 덜 민감하고 강건하다. 이 장에서는 주요 비모수 검정 방법과 그 적용 상황을 학습한다.
예제: 데이터 로드
import seaborn as snsimport pandas as pdimport numpy as npimport matplotlib.pyplot as pltfrom scipy import stats# 데이터 로드df = sns.load_dataset("penguins")print("데이터 크기:", df.shape)print("\n범주형 변수:", df.select_dtypes(include=['object', 'category']).columns.tolist())print("연속형 변수:", df.select_dtypes(include=[np.number]).columns.tolist())
=== 정규성 검정 (Shapiro-Wilk) ===
Adelie: p = 0.7166 (정규)
Gentoo: p = 0.0135 (비정규)
17.2 Mann-Whitney U 검정 (Wilcoxon Rank-Sum Test)
Mann-Whitney U 검정은 두 독립 집단의 분포 위치를 비교하는 비모수 검정으로, 독립표본 t-검정의 대안이다.
특징
대상: 두 독립 집단
대안 검정: 독립표본 t-검정
검정 대상: 두 집단의 분포가 같은지 (중앙값 또는 순위 합)
가정: 두 집단의 분포 형태가 유사 (위치만 다를 수 있음)
가설 설정
H₀ (귀무가설): 두 집단의 분포가 동일하다 (중앙값이 같다)
H₁ (대립가설): 두 집단의 분포가 다르다 (중앙값이 다르다)
검정 원리
두 집단의 데이터를 합쳐서 순위 매김
각 집단의 순위 합 계산
U 통계량 계산 (작은 집단의 순위 합이 작을수록 차이가 큼)
17.2.1 예제: Adelie vs Gentoo 부리 길이 비교
예제: 데이터 준비
from scipy.stats import mannwhitneyu# 두 종 선택g1 = df[df["species"] =="Adelie"]["bill_length_mm"].dropna()g2 = df[df["species"] =="Gentoo"]["bill_length_mm"].dropna()print("=== 집단 정보 ===")print(f"Adelie: n = {len(g1)}, 중앙값 = {g1.median():.2f}mm")print(f"Gentoo: n = {len(g2)}, 중앙값 = {g2.median():.2f}mm")# 기술 통계량print("\n=== 기술 통계량 ===")print("Adelie:")print(g1.describe())print("\nGentoo:")print(g2.describe())
=== 집단 정보 ===
Adelie: n = 151, 중앙값 = 38.80mm
Gentoo: n = 123, 중앙값 = 47.30mm
=== 기술 통계량 ===
Adelie:
count 151.000000
mean 38.791391
std 2.663405
min 32.100000
25% 36.750000
50% 38.800000
75% 40.750000
max 46.000000
Name: bill_length_mm, dtype: float64
Gentoo:
count 123.000000
mean 47.504878
std 3.081857
min 40.900000
25% 45.300000
50% 47.300000
75% 49.550000
max 59.600000
Name: bill_length_mm, dtype: float64
예제: Mann-Whitney U 검정
# Mann-Whitney U 검정u_stat, p_value = mannwhitneyu(g1, g2, alternative="two-sided")print("\n=== Mann-Whitney U 검정 ===")print(f"U-통계량: {u_stat:.4f}")print(f"p-value: {p_value:.4f}")alpha =0.05print(f"\n유의수준 {alpha} 기준:")if p_value < alpha:print("✓ 귀무가설 기각: 두 종의 부리 길이 분포가 유의하게 다름")print(f" 중앙값 차이: {g2.median() - g1.median():.2f}mm")else:print("✗ 귀무가설 채택: 두 종의 부리 길이 분포가 유의하게 다르지 않음")
=== Mann-Whitney U 검정 ===
U-통계량: 224.5000
p-value: 0.0000
유의수준 0.05 기준:
✓ 귀무가설 기각: 두 종의 부리 길이 분포가 유의하게 다름
중앙값 차이: 8.50mm
# 독립표본 t-검정과 비교from scipy.stats import ttest_indt_stat, p_t = ttest_ind(g1, g2)print("\n=== 모수 vs 비모수 비교 ===")print(f"t-검정 (모수): t = {t_stat:.4f}, p = {p_t:.4f}")print(f"Mann-Whitney (비모수): U = {u_stat:.4f}, p = {p_value:.4f}")print("\n→ 두 검정 모두 유사한 결론")
=== 모수 vs 비모수 비교 ===
t-검정 (모수): t = -25.0953, p = 0.0000
Mann-Whitney (비모수): U = 224.5000, p = 0.0000
→ 두 검정 모두 유사한 결론
17.3 Wilcoxon Signed-Rank Test
Wilcoxon 부호순위 검정은 동일 대상의 전후 변화를 비교하는 비모수 검정으로, 대응표본 t-검정의 대안이다.
특징
대상: 동일 대상의 두 측정값 (대응 표본)
대안 검정: 대응표본 t-검정
검정 대상: 차이의 중앙값이 0인지
가정: 차이의 분포가 대칭
가설 설정
H₀ (귀무가설): 차이의 중앙값이 0이다 (전후 차이 없음)
H₁ (대립가설): 차이의 중앙값이 0이 아니다 (전후 차이 있음)
검정 원리
각 쌍의 차이 계산
차이의 절댓값에 순위 매김
양수 차이와 음수 차이의 순위 합 비교
17.3.1 예제: 가상 전후 데이터
penguins 데이터셋에는 자연스러운 전후 데이터가 없으므로, 예제를 위해 가상 데이터를 생성한다.
예제: 가상 데이터 생성
from scipy.stats import wilcoxon# 시드 설정np.random.seed(42)# 전후 데이터 생성 (예: 처치 전후 부리 길이)before = df[df["species"] =="Adelie"]["bill_length_mm"].dropna().sample(30, random_state=42)after = before + np.random.normal(0.5, 1.0, size=len(before))print("=== 전후 데이터 ===")print(f"처치 전 중앙값: {before.median():.2f}mm")print(f"처치 후 중앙값: {after.median():.2f}mm")print(f"차이 중앙값: {(after - before).median():.2f}mm")
=== 전후 데이터 ===
처치 전 중앙값: 39.25mm
처치 후 중앙값: 39.04mm
차이 중앙값: 0.27mm
# 대응표본 t-검정과 비교from scipy.stats import ttest_relt_stat, p_t = ttest_rel(before, after)print("\n=== 모수 vs 비모수 비교 ===")print(f"Paired t-test (모수): t = {t_stat:.4f}, p = {p_t:.4f}")print(f"Wilcoxon (비모수): W = {w_stat:.4f}, p = {p_value:.4f}")
=== 모수 vs 비모수 비교 ===
Paired t-test (모수): t = -1.8979, p = 0.0677
Wilcoxon (비모수): W = 149.0000, p = 0.0879
17.4 Kruskal-Wallis Test
Kruskal-Wallis 검정은 세 개 이상 독립 집단의 분포를 비교하는 비모수 검정으로, 일원분산분석(ANOVA)의 대안이다.
특징
대상: 세 개 이상 독립 집단
대안 검정: 일원분산분석 (One-way ANOVA)
검정 대상: 모든 집단의 분포가 같은지
가정: 독립성 (분포 형태 가정 불필요)
가설 설정
H₀ (귀무가설): 모든 집단의 분포가 동일하다
H₁ (대립가설): 적어도 하나의 집단 분포가 다르다
검정 원리
모든 데이터를 합쳐서 순위 매김
각 집단의 순위 평균 계산
H 통계량 계산 (카이제곱 분포에 근사)
17.4.1 예제: 세 종의 부리 길이 비교
예제: 데이터 준비
from scipy.stats import kruskal# 세 종의 부리 길이groups = [ df[df["species"] == sp]["bill_length_mm"].dropna()for sp in df["species"].unique()]print("=== 집단 정보 ===")for species, group inzip(df["species"].unique(), groups):print(f"{species:12s}: n = {len(group):3d}, 중앙값 = {group.median():.2f}mm")
=== 집단 정보 ===
Adelie : n = 151, 중앙값 = 38.80mm
Chinstrap : n = 68, 중앙값 = 49.55mm
Gentoo : n = 123, 중앙값 = 47.30mm
예제: Kruskal-Wallis 검정
# Kruskal-Wallis 검정h_stat, p_value = kruskal(*groups)print("\n=== Kruskal-Wallis 검정 ===")print(f"H-통계량: {h_stat:.4f}")print(f"자유도: {len(groups) -1}")print(f"p-value: {p_value:.4f}")alpha =0.05print(f"\n유의수준 {alpha} 기준:")if p_value < alpha:print("✓ 귀무가설 기각: 종 간 부리 길이 분포가 유의하게 다름")print(" → 어느 종 간 차이인지 확인하려면 사후검정 필요")else:print("✗ 귀무가설 채택: 종 간 부리 길이 분포가 유의하게 다르지 않음")
=== Kruskal-Wallis 검정 ===
H-통계량: 244.1367
자유도: 2
p-value: 0.0000
유의수준 0.05 기준:
✓ 귀무가설 기각: 종 간 부리 길이 분포가 유의하게 다름
→ 어느 종 간 차이인지 확인하려면 사후검정 필요
예제: ANOVA와 비교
# 일원분산분석과 비교from scipy.stats import f_onewayf_stat, p_anova = f_oneway(*groups)print("\n=== 모수 vs 비모수 비교 ===")print(f"ANOVA (모수): F = {f_stat:.4f}, p = {p_anova:.4f}")print(f"Kruskal-Wallis (비모수): H = {h_stat:.4f}, p = {p_value:.4f}")print("\n→ 두 검정 모두 유사한 결론")
=== 모수 vs 비모수 비교 ===
ANOVA (모수): F = 410.6003, p = 0.0000
Kruskal-Wallis (비모수): H = 244.1367, p = 0.0000
→ 두 검정 모두 유사한 결론
예제: 시각화
# 시각화fig, axes = plt.subplots(1, 2, figsize=(14, 5))# 박스플롯df_clean = df[["species", "bill_length_mm"]].dropna()sns.boxplot(x="species", y="bill_length_mm", data=df_clean, ax=axes[0])axes[0].set_title("Bill Length by Species")axes[0].set_ylabel("Bill Length (mm)")# 바이올린 플롯sns.violinplot(x="species", y="bill_length_mm", data=df_clean, ax=axes[1])axes[1].set_title("Distribution of Bill Length")axes[1].set_ylabel("Bill Length (mm)")plt.tight_layout()plt.show()
17.4.2 사후검정 (Post-hoc Test)
Kruskal-Wallis 검정이 유의하면 쌍별 비교를 수행한다.
예제: Dunn’s Test (사후검정)
# Dunn's test는 scikit-posthocs 패키지 사용# import scikit_posthocs as sp# dunn_results = sp.posthoc_dunn(df_clean, val_col='bill_length_mm', group_col='species')# print(dunn_results)# 대안: 쌍별 Mann-Whitney U 검정 (Bonferroni 보정)from itertools import combinationsspecies_list = df["species"].unique()n_comparisons =len(list(combinations(species_list, 2)))alpha_bonf =0.05/ n_comparisonsprint(f"\n=== 쌍별 Mann-Whitney U (Bonferroni 보정) ===")print(f"비교 횟수: {n_comparisons}")print(f"보정된 유의수준: {alpha_bonf:.4f}\n")for sp1, sp2 in combinations(species_list, 2): g1 = df[df["species"] == sp1]["bill_length_mm"].dropna() g2 = df[df["species"] == sp2]["bill_length_mm"].dropna() u, p = mannwhitneyu(g1, g2)print(f"{sp1:12s} vs {sp2:12s}: U = {u:.4f}, p = {p:.4f} ", end="")print("→ 유의"if p < alpha_bonf else"→ 비유의")
=== 쌍별 Mann-Whitney U (Bonferroni 보정) ===
비교 횟수: 3
보정된 유의수준: 0.0167
Adelie vs Chinstrap : U = 101.0000, p = 0.0000 → 유의
Adelie vs Gentoo : U = 224.5000, p = 0.0000 → 유의
Chinstrap vs Gentoo : U = 5323.5000, p = 0.0018 → 유의
17.5 Friedman Test
Friedman 검정은 세 개 이상의 대응 집단(동일 대상의 반복 측정)을 비교하는 비모수 검정으로, 반복측정 ANOVA의 대안이다.
특징
대상: 세 개 이상 대응 집단 (동일 대상의 반복 측정)
대안 검정: 반복측정 ANOVA
검정 대상: 모든 조건의 분포가 같은지
가정: 각 블록(대상) 내에서 순위 매김 가능
가설 설정
H₀ (귀무가설): 모든 조건의 분포가 동일하다
H₁ (대립가설): 적어도 하나의 조건 분포가 다르다
17.5.1 예제: 가상 반복 측정 데이터
예제: 가상 데이터 생성
from scipy.stats import friedmanchisquare# 가상 반복 측정 데이터 (예: 3가지 처치 조건)np.random.seed(123)n =30cond1 = before.valuescond2 = before.values + np.random.normal(0.3, 0.8, size=len(before))cond3 = before.values + np.random.normal(0.8, 0.8, size=len(before))print("=== 조건별 중앙값 ===")print(f"조건 1: {np.median(cond1):.2f}mm")print(f"조건 2: {np.median(cond2):.2f}mm")print(f"조건 3: {np.median(cond3):.2f}mm")
=== 조건별 중앙값 ===
조건 1: 39.25mm
조건 2: 39.97mm
조건 3: 39.71mm
예제: Friedman 검정
# Friedman 검정f_stat, p_value = friedmanchisquare(cond1, cond2, cond3)print("\n=== Friedman 검정 ===")print(f"χ²-통계량: {f_stat:.4f}")print(f"자유도: {3-1}")print(f"p-value: {p_value:.4f}")alpha =0.05print(f"\n유의수준 {alpha} 기준:")if p_value < alpha:print("✓ 귀무가설 기각: 조건 간 분포가 유의하게 다름")else:print("✗ 귀무가설 채택: 조건 간 분포가 유의하게 다르지 않음")