빅데이터분석기사 실기 학습자료 보충 - 통계검정 심화 · 등분산검정 · 비모수검정 · 불균형처리

2026. 4. 28. 20:33ㆍ빅데이터분석기사

11111

 

 
[샘플데이터] 보충 실습 데이터:
[
등분산/비모수 실습] df: group(A/B), score
   group  score
   A      78.2    ← A
그룹 (n=150)
   A      82.5
   B      71.3    ← B
그룹 (n=120)
   B      85.1
[
불균형 실습] train: target=0(560명), target=1(240명) → 70%:30% 불균형

 

1. 등분산 검정 완전 정복

1.1 Levene 검정 (시험 표준)

 
[기출패턴] 기출: "등분산 검정을 실시하고 결과에 따라 적절한 t-검정을 실시하라"

 

from scipy import stats
import pandas as pd
 
df = pd.read_csv("/data/data.csv")
g1 = df[df['group']=='A']['score'].dropna()
g2 = df[df['group']=='B']['score'].dropna()
 
# ── Levene 검정 ─────────────────────────────────────────────────
# H0: 두 집단의 분산이 같다 (등분산)
# H1: 두 집단의 분산이 다르다 (이분산)
# center='mean': 평균 기준 (기본), 'median': 중앙값 기준
lev_stat, lev_p = stats.levene(g1, g2)
print(f"Levene F-통계량: {round(lev_stat, 4)}")
print(f"Levene p-value : {round(lev_p, 4)}")
 
equal_var = lev_p > 0.05   # True=등분산, False=이분산
print(f"등분산 가정    : {'충족 → Student t (equal_var=True)' if equal_var else '불충족 → Welch t (equal_var=False)'}")
 
# 결과에 따라 t-검정 방법 자동 선택
t_stat, t_p = stats.ttest_ind(g1, g2, equal_var=equal_var)
print(f"
t-통계량: {round(t_stat, 4)}")
print(f"p-value : {round(t_p, 4)}")
print("결론:", "차이 있음 (★)" if t_p<=0.05 else "차이 없음")
────────────────────────────────────────────────────  실행 결과
Levene F-통계량: 0.8234    ← 작은 값 → 등분산에 가까움
Levene p-value : 0.3641    ← 0.05보다 크므로
 
등분산 가정    : 충족 → Student t (equal_var=True)
 
t-통계량: 2.1834    ← 양수: A그룹 평균이 B보다 높음
p-value : 0.0292    ← 0.05 미만
결론: 차이 있음 (★)

 

1.2 3단계 완전 검정 절차 (시험 표준 코드)

from scipy import stats
import pandas as pd
 
df = pd.read_csv("/data/data.csv")
g_A = df[df['group']=='A']['score'].dropna()
g_B = df[df['group']=='B']['score'].dropna()
 
# ─── Step1: 정규성 검정 ──────────────────────────────────────────
print("Step1: 정규성 검정 (Shapiro-Wilk)")
_, pA = stats.shapiro(g_A)
_, pB = stats.shapiro(g_B)
print(f"  A: p={round(pA,4)} → {'정규' if pA>0.05 else '비정규'}")
print(f"  B: p={round(pB,4)} → {'정규' if pB>0.05 else '비정규'}")
 
if pA > 0.05 and pB > 0.05:   # 양쪽 모두 정규분포일 때만 모수 검정
    # ─── Step2: 등분산 검정 ────────────────────────────────────────
    print("
Step2: 등분산 검정 (Levene)")
    _, lp = stats.levene(g_A, g_B)
    print(f"  p={round(lp,4)} → {'등분산' if lp>0.05 else '이분산'}")
    method = "Student t" if lp > 0.05 else "Welch t"
 
    # ─── Step3: t-검정 ─────────────────────────────────────────────
    print(f"
Step3: {method}-검정")
    t, p = stats.ttest_ind(g_A, g_B, equal_var=(lp>0.05))
else:
    # 정규성 불충족 → 비모수 검정으로 전환
    print("
Step2&3: 정규성 불충족 → Mann-Whitney U (비모수)")
    t, p = stats.mannwhitneyu(g_A, g_B, alternative='two-sided')
 
print(f"통계량: {round(t, 4)}")
print(f"p-value: {round(p, 4)}")
print("최종 결론:", "기각(차이있음) ★" if p<=0.05 else "채택(차이없음)")
────────────────────────────────────────────────────  실행 결과
Step1: 정규성 검정 (Shapiro-Wilk)
  A: p=0.1823 → 정규    ← 둘 다 정규
  B: p=0.2341 → 정규
 
Step2: 등분산 검정 (Levene)
  p=0.3641 → 등분산    ← p>0.05: 등분산
 
Step3: Student t-검정
통계량: 2.1834
p-value: 0.0292
최종 결론: 기각(차이있음) ★    ← 0.05보다 작으므로

 

2. 비모수 검정 심화

2.1 Mann-Whitney U

from scipy import stats
import pandas as pd
 
df = pd.read_csv("/data/data.csv")
g1 = df[df['group']=='A']['score'].dropna()
g2 = df[df['group']=='B']['score'].dropna()
 
# Mann-Whitney U: 독립 2집단 비모수 검정
# H0: 두 집단의 분포(중앙값)가 같다
# alternative: 'two-sided'(양측), 'less'(단측), 'greater'(단측)
stat, p = stats.mannwhitneyu(g1, g2, alternative='two-sided')
print(f"U-통계량: {round(stat, 4)}")
print(f"p-value : {round(p, 4)}")
print("결론:", "차이 있음 (★)" if p<=0.05 else "차이 없음")
────────────────────────────────────────────────────  실행 결과
U-통계량: 28421.0000    ← U통계량 (두 집단 순위 비교)
p-value : 0.0182         ← 0.05 미만
결론: 차이 있음 (★)

 

2.2 Wilcoxon / Kruskal-Wallis

from scipy import stats
import pandas as pd
df = pd.read_csv("/data/data.csv")
 
# ── Wilcoxon 부호 순위 검정 (대응표본 비모수) ───────────────────────
before = df['before'].dropna().reset_index(drop=True)
after  = df['after'].dropna().reset_index(drop=True)
n = min(len(before), len(after))
# H0: 처리 전후 중앙값 차이 없음
w, pw = stats.wilcoxon(before[:n], after[:n])
print(f"Wilcoxon W={round(w,4)}, p={round(pw,4)}")
print("→", "전후 차이 있음 ★" if pw<=0.05 else "전후 차이 없음")
 
# ── Kruskal-Wallis (3집단+ 비모수, ANOVA 대체) ─────────────────────
groups = [df[df['dept']==d]['salary'].dropna() for d in df['dept'].unique()]
# H0: 모든 집단의 분포가 같다
h, ph = stats.kruskal(*groups)
print(f"
Kruskal-Wallis H={round(h,4)}, p={round(ph,4)}")
print("→", "집단 간 차이 있음 ★" if ph<=0.05 else "집단 간 차이 없음")
────────────────────────────────────────────────────  실행 결과
Wilcoxon W=1823.0000, p=0.0034
→ 전후 차이 있음 ★    ← 처리 효과 있음
 
Kruskal-Wallis H=89.2341, p=0.0000
→ 집단 간 차이 있음 ★    ← 적어도 한 집단이 다름

 

3. 불균형 데이터 처리 심화

from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import train_test_split
import pandas as pd
import xgboost as xgb
 
train = pd.read_csv("/data/train.csv")
X = train.drop('target',axis=1).select_dtypes(include='number').fillna(0)
y = train['target']
 
# 불균형 정도 파악
neg, pos = (y==0).sum(), (y==1).sum()
print(f"음성: {neg}, 양성: {pos}, 비율: {neg/pos:.1f}:1")
# 4:1 초과이면 반드시 불균형 처리 필요
 
X_tr,X_v,y_tr,y_v = train_test_split(X,y,test_size=0.2,random_state=42,stratify=y)
 
# 방법1: class_weight='balanced' — RF, LR, SVM 등에서 사용
# → 소수 클래스 샘플에 높은 가중치 자동 부여
rf = RandomForestClassifier(n_estimators=100, class_weight='balanced', random_state=42)
rf.fit(X_tr,y_tr)
print(f"RF(balanced) AUC: {roc_auc_score(y_v,rf.predict_proba(X_v)[:,1]):.4f}")
 
# 방법2: XGBoost scale_pos_weight = 음성 수 / 양성 수
# → 양성 클래스 오분류 시 더 큰 손실 부여
neg_tr, pos_tr = (y_tr==0).sum(), (y_tr==1).sum()
xgb_m = xgb.XGBClassifier(scale_pos_weight=neg_tr/pos_tr,
                            eval_metric='auc', random_state=42)
xgb_m.fit(X_tr,y_tr)
print(f"XGB(spw={neg_tr/pos_tr:.1f}) AUC: {roc_auc_score(y_v,xgb_m.predict_proba(X_v)[:,1]):.4f}")
────────────────────────────────────────────────────  실행 결과
음성: 560, 양성: 240, 비율: 2.3:1    ← 경미한 불균형 (4:1 미만)
 
RF(balanced) AUC: 0.8534    ← class_weight 적용
XGB(spw=2.3) AUC: 0.8721    ← scale_pos_weight=2.3 적용

 

4. 전유형 실전 모의고사

4.1 작업형 1유형 모의고사

 
[기출패턴] [1-1] IT 부서이고 2020년 이후 입사한 직원의 평균 급여 (소수점 2자리)

 

import pandas as pd
df = pd.read_csv("/data/data.csv")
df['hire_date'] = pd.to_datetime(df['hire_date'])
 
# 복합 조건: IT 부서 AND 2020년 이후 입사
mask   = (df['dept'] == 'IT') & (df['hire_date'].dt.year >= 2020)
result = df[mask]['salary'].mean()
print(round(result, 2))
────────────────────────────────────────────────────  실행 결과
4823.45    ← IT 부서 2020년+ 입사자의 평균 급여

 

 
[기출패턴] [1-2] 급여 IQR 이상치 제거 후 부서별 중앙값의 최댓값 (소수점 2자리)

 

import pandas as pd
df = pd.read_csv("/data/data.csv")
 
Q1, Q3 = df['salary'].quantile([0.25, 0.75])
IQR = Q3 - Q1
df_c   = df[(df['salary'] >= Q1-1.5*IQR) & (df['salary'] <= Q3+1.5*IQR)]
result = df_c.groupby('dept')['salary'].median().max()
print(round(result, 2))
────────────────────────────────────────────────────  실행 결과
5200.00    ← 이상치 제거 후 Finance 부서 중앙값이 최대

 

4.2 작업형 2유형 모의고사

 
[기출패턴] [2] train.csv로 RF 학습 → test.csv 예측 → submission.csv 저장

 

import pandas as pd, numpy as np
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_auc_score
from sklearn.preprocessing import LabelEncoder
 
train = pd.read_csv("/data/train.csv")
test  = pd.read_csv("/data/test.csv")
 
# 전처리 (train 통계로 test 처리)
for col in train.select_dtypes(include='number').columns:
    m = train[col].median()
    train[col].fillna(m, inplace=True); test[col].fillna(m, inplace=True)
le = LabelEncoder()
for col in train.select_dtypes(include='object').columns:
    if col == 'target': continue
    train[col]=le.fit_transform(train[col].astype(str))
    test[col] =le.transform(test[col].astype(str))
 
X,y    = train.drop('target',axis=1), train['target']
X_test = test.drop('id',axis=1,errors='ignore')
 
X_tr,X_v,y_tr,y_v = train_test_split(X,y,test_size=0.2,random_state=42,stratify=y)
rf = RandomForestClassifier(n_estimators=200,max_depth=6,class_weight='balanced',random_state=42)
rf.fit(X_tr,y_tr)
print(f"Val AUC: {roc_auc_score(y_v,rf.predict_proba(X_v)[:,1]):.4f}")
 
rf.fit(X,y)   # 전체 재학습
sub = pd.DataFrame({'id':test['id'],'survived':rf.predict_proba(X_test)[:,1]})
print(f"shape: {sub.shape}, 결측: {sub.isnull().sum().sum()}")
print(sub.head(3))
sub.to_csv('submission.csv',index=False)
print("저장 완료!")
────────────────────────────────────────────────────  실행 결과
Val AUC: 0.8734    ← 검증 AUC
 
shape: (200, 2), 결측: 0    ← 200행, 결측 없음 확인
   id  survived
0   1    0.7823    ← 0.78 확률로 양성
1   2    0.1234    ← 0.12 확률로 음성
2   3    0.9012    ← 0.90 확률로 양성
저장 완료!

 

4.3 작업형 3유형 모의고사

 
[기출패턴] [3-1] 치료법 A,B 회복기간 차이 검정 — t-통계량, p-value 소수점 4자리

 

from scipy import stats
import pandas as pd
df = pd.read_csv("/data/data.csv")
A = df[df['group']=='A']['days'].dropna()
B = df[df['group']=='B']['days'].dropna()
 
_, pA = stats.shapiro(A)    # 정규성 A
_, pB = stats.shapiro(B)    # 정규성 B
_, lp = stats.levene(A,B)  # 등분산
t, p  = stats.ttest_ind(A, B, equal_var=(lp>0.05))
 
print(round(t, 4))   # 검정통계량
print(round(p, 4))   # p-value
────────────────────────────────────────────────────  실행 결과
2.3814    ← t-통계량: A그룹 회복기간이 더 긺
0.0182    ← p-value: 0.05 미만 → 차이 있음 (★)

 

 
[기출패턴] [3-2] 연령대별 만족도 ANOVA + Tukey HSD — F-통계량, p-value 소수점 4자리

 

from scipy import stats
from statsmodels.stats.multicomp import pairwise_tukeyhsd
import pandas as pd
 
df = pd.read_csv("/data/data.csv")
# 연령대 그룹별 데이터 분리
gs = [df[df['age_group']==g]['score'].dropna() for g in df['age_group'].unique()]
 
f, p = stats.f_oneway(*gs)
print(round(f, 4))   # F-통계량
print(round(p, 4))   # p-value
 
if p <= 0.05:
    # 어떤 연령대 쌍이 다른지 확인
    tukey = pairwise_tukeyhsd(df['score'].dropna(),
                              df.loc[df['score'].notna(), 'age_group'])
    print(tukey.summary())
────────────────────────────────────────────────────  실행 결과
18.4231    ← F-통계량: 집단 간 차이 상당함
0.0000     ← p<0.05: 적어도 한 연령대 차이 있음
 
Multiple Comparison of Means - Tukey HSD
group1  group2  meandiff  p-adj  reject
청년    중년    -8.4231  0.001   True   ← 청년 < 중년 (만족도 차이 ★)
청년    장년    -3.1234  0.312   False  ← 차이 없음
중년    장년     5.2997  0.023   True   ← 중년 > 장년 (★)

 

2222