빅데이터분석기사 실기 학습자료 보충 - 통계검정 심화 · 등분산검정 · 비모수검정 · 불균형처리
2026. 4. 28. 20:33ㆍ빅데이터분석기사
11111
2222

| [샘플데이터] 보충 실습 데이터: [등분산/비모수 실습] 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 ← 중년 > 장년 (★) |
'빅데이터분석기사' 카테고리의 다른 글
| 빅데이터분석기사 실기 학습자료 3과목 파트3 - 선형 회귀분석 · 로지스틱 회귀 · 실전 풀이 (0) | 2026.04.28 |
|---|---|
| 빅데이터분석기사 실기 학습자료 3과목 파트2 - t-검정 · ANOVA · 카이제곱 · 비모수 검정 (0) | 2026.04.28 |
| 빅데이터분석기사 실기 학습자료 3과목 파트1 - 기술통계 정규성 검정 상관분석 실전 (0) | 2026.04.28 |
| 빅데이터분석기사 실기 학습자료 2과목 파트4 - 앙상블 심화 · Pipeline · 특성 중요도 · 과적합 진단 · 최종 제출 전략 (0) | 2026.04.28 |
| 빅데이터분석기사 실기 학습자료 2과목 파트3 - 회귀 알고리즘 완전정복 · 비지도학습(클러스터링/PCA) (0) | 2026.04.28 |