01 독립성 검정
02 적합도 검정
03 클래스 불균등 처리
-01 언더샘플링
-02 오버샘플링
-03 가중치 조절
-04 임계값 이동
01 독립성 검정
- 범주형 자료 분석의 일종 (빈도분석 = 교차분석 = 카이제곱 검정)
- 멘델의 유전자 법칙은 적합도 검정~
- 두 범주형 자료가 독립적인지에 대한 여부를 확인하는 검정 형태
ex) 성별별 통신사 선호도가 존재하는지에 대한 여부, 타이타닉 데이터에서 성별별 생존 여부가 다른지에 대한 가설검정 등
- 가설의 형태
H0 : 두 변수가 서로 독립적이다. (연관성이 없다)
H1 : 두 변수는 서로 독립적이지 않다. (연관성이 있다)
- 카이제곱 통계량이 클수록 (유의확률이 작을수록) 두 변수의 연관성 강도가 커진다. ( ★ ★ ★ )
유의성 정도는 검정통계량의 크기로 비교한다.
검정통계량이 커질수록 유의확률은 감소한다.
가장 유의한 변수 찾기 -> 유의확률이 가장 작음 혹은 검정통계량이 가장 큼
따라서 가장 연관성이 있는 변수를 찾기 위해서는 카이제곱 검정의 검정통계량 / 유의확률을 확인하면 된다.
[ 예제 - 타이타닉 데이터에서 여러 변수와 생존여부 (target)의 연관성 확인 ]
import pandas as pd
df = pd.read_csv('titanic.csv')
df.columns
df.info()
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 PassengerId 891 non-null int64
1 Survived 891 non-null int64
2 Pclass 891 non-null int64
3 Name 891 non-null object
4 Sex 891 non-null object
5 Age 714 non-null float64
6 SibSp 891 non-null int64
7 Parch 891 non-null int64
8 Ticket 891 non-null object
9 Fare 891 non-null float64
10 Cabin 204 non-null object
11 Embarked 889 non-null object
dtypes: float64(2), int64(5), object(5)
memory usage: 83.7+ KB
## 1. 성별에 따른 생존여부 연관성 확인하기
# 성별, 생존 여부 교차표 생성
tab1 = pd.crosstab(df['Sex'], df['Survived'])
pd.crosstab(df['Sex'], df['Survived'])
Out[13]:
Survived 0 1
Sex
female 81 233
male 468 109
# 카이제곱 검정
from scipy.stats import chi2_contingency
import scipy.stats
dir(scipy.stats)
chi_value1, pvalue1, _, _ = chi2_contingency(tab1)
# 4개를 지정해야 하는데, 2개의 값만 필요한 경우 이름을 지정하지 않고 _ 로 물리적인 그릇을 만들어준다.
# _ 는 버리는 이름이라고 생각하면 된다.
print('카이제곱통계량:', chi_value1)
print('카이제곱통계량:', chi_value1)
카이제곱통계량: 260.71702016732104
print('유의확률:', pvalue1)
print('유의확률:', pvalue1)
유의확률: 1.1973570627755645e-58
# 결론) 유의확률이 0.05보다 매우 작기 때문에 영가설을 기각한다.
-> 성별별 생존여부가 유의한 차이를 가진다.
유의성 정도는 검정통계량의 크기로 비교한다.
검정통계량이 커질수록 유의확률은 감소한다.
가장 유의한 변수 찾기 -> 유의확률이 가장 작음 혹은 검정통계량이 가장 큼
따라서 가장 연관성이 있는 변수를 찾기 위해서는 카이제곱 검정의 검정통계량 / 유의확률을 확인하면 된다.
## 2. 객실등급 (Pclass) 과 생존 여부 교차표 생성
# 객실등급, 생존 여부 교차표 생성
tab2 = pd.crosstab(df['Pclass'], df['Survived'])
# 카이제곱 검정
dir(scipy.stats)
chi_value2, pvalue2, _, _ = chi2_contingency(tab2)
print('카이제곱통계량:', chi_value2)
print('카이제곱통계량:', chi_value2)
카이제곱통계량: 102.88898875696056
print('유의확률:', pvalue2)
print('유의확률:', pvalue2)
유의확률: 4.549251711298793e-23
# 결론) 유의확률이 0.05보다 매우 작기 때문에 영가설을 기각한다.
-> 객실등급별 생존여부가 유의한 차이를 가진다. (등급이 높을수록 생존자가 많아진다.)
## 3. Pclass vs 생존여부
chi_value1 > chi_value2
# 자유도에 따라 해석이 달라질 수 있다.
chi_value1 > chi_value2
Out[61]: np.True_
pvalue1 > pvalue2
# 자유도가 다른 경우 유의확률로 비교하는 것이 더 적절하다.
pvalue1 > pvalue2
Out[62]: np.False_
[ 연습문제 (빅분기 실기 대비) - 타이타닉 범주형 변수들 중 생존 여부와 가장 유의한 변수 찾기 ]
- 타이타닉 데이털르 사용하여 범주형인 독립변수와 종속변수 (생존여부)와의 독립성 검정 수행
- 그 결과 가장 유의(유의확률)한 변수의 검정통계량을 소숫점 셋째자리까지 반올림하여 출력하시오.
- (단, 결측치가 존재하는 행은 제외)
- 범주형 변수 : ['Pclass', 'Sex', 'Embarked', 'SibSp', 'Parch']
# 방법1
# Pclass vs Survived
tab1 = pd.crosstab(df['Pclass'], df['Survived'])
chi_value1, pvalue1, _, _ = chi2_contingency(tab1)
print('카이제곱통계량(Pclass):', chi_value1)
print('유의확률(Pclass):', pvalue1)
# Sex vs Survived
tab2 = pd.crosstab(df['Sex'], df['Survived'])
chi_value2, pvalue2, _, _ = chi2_contingency(tab2)
print('카이제곱통계량(Sex):', chi_value2)
print('유의확률(Sex):', pvalue2)
# Embarked vs Survived
tab3 = pd.crosstab(df['Embarked'], df['Survived'])
chi_value3, pvalue3, _, _ = chi2_contingency(tab3)
print('카이제곱통계량(Embarked):', chi_value3)
print('유의확률(Embarked):', pvalue3)
# SibSp vs Survived
tab4 = pd.crosstab(df['SibSp'], df['Survived'])
chi_value4, pvalue4, _, _ = chi2_contingency(tab4)
print('카이제곱통계량(SibSp):', chi_value4)
print('유의확률(SibSp):', pvalue4)
# Parch vs Survived
tab5 = pd.crosstab(df['Parch'], df['Survived'])
chi_value5, pvalue5, _, _ = chi2_contingency(tab5)
print('카이제곱통계량(Parch):', chi_value5)
print('유의확률(Parch):', pvalue5)
# 가장 유의한 변수 찾기
max(chi_value1, chi_value2, chi_value3, chi_value4, chi_value5)
min(pvalue1, pvalue2, pvalue3, pvalue4, pvalue5)
# 방법2
df_new = df[['Pclass', 'Sex', 'Embarked', 'SibSp', 'Parch', 'Survived']]
df_new = df_new.dropna()
X = df_new.drop(columns = 'Survived')
y = df_new['Survived']\
list_chi = [] ; list_p = []
for col in ['Pclass', 'Sex', 'Embarked', 'SibSp', 'Parch'] :
ctab = pd.crosstab(df_new[col], y)
chi_value, pvalue, _, _ = chi2_contingency(ctab)
list_chi.append(chi_value)
list_p.append(pvalue)
df_result = pd.DataFrame({'chisquare':list_chi, 'pvalue':list_p}, index = X.columns)
cname = df_result['pvalue'].idxmin()
result = df_result.loc[cname, 'chisquare']
print(round(result, 3))
list_chi = [] ; list_p = []
for col in ['Pclass', 'Sex', 'Embarked', 'SibSp', 'Parch'] :
ctab = pd.crosstab(df_new[col], y)
chi_value, pvalue, _, _ = chi2_contingency(ctab)
list_chi.append(chi_value)
list_p.append(pvalue)
df_result = pd.DataFrame({'chisquare':list_chi, 'pvalue':list_p}, index = X.columns)
cname = df_result['pvalue'].idxmin()
result = df_result.loc[cname, 'chisquare']
print(round(result, 3))
258.427
02 적합도 검정
- 각 클래스별 도수가 기대도수와 일치하는지를 검정
- ex) 멘델의 유전자 법칙, 3개 반 학생 수가 같은지에 대한 가설검정 등
- 가설형태
H0 : 기대분포와 일치한다.
H1 : 기대분포와 일치하지 않는다.
[ 예제 - 타이타닉 데이터를 사용하여 생존 / 사망 비율이 같은지에 대한 가설검정 ]
import pandas as pd
df = pd.read_csv('titanic.csv')
df.info()
# 실제 도수 확인
counts = df['Survived'].value_counts().sort_index() # 사망, 생존 순서대로
# 기대비율 -> 기대도수
n = len(df) # 전체도수
exp = [n*0.5, n*0.5] # 기대도수
# 카이제곱 검정
from scipy.stats import chisquare
chi_value, pvalue = chisquare(counts, exp)
print('카이제곱통계량:', chi_value)
print('유의확률:', pvalue)
print('카이제곱통계량:', chi_value)
카이제곱통계량: 48.09090909090909
print('유의확률:', pvalue)
유의확률: 4.069087128968317e-12
# 결론) 유의확률이 0.05보다 작으므로 영가설을 기각한다.
따라서 기대분포(두 도수가 같다)가 다르므로, 생존/사망자의 수가 유의하게 다르다고 할 수 있다.
03 클래스 불균등 처리
- 현상 : 분류 분석 시 각 클래스별 도수의 차이가 심한 경우
- 문제 : 소수 클래스의 학습이 충분하지 않으므로 소수클래스 예측률의 저하가 발생한다.
(3:7부터 불균등 시작, 2:8 ~ 1:9부터 심각)
- 방법:
-01 언더샘플링
다수 클래스 관측치를 소수 클래스 관측치수와 유사하게 젲거하여 균등하게 맞추는 샘플링 기법
1) RandomUnderSampler
- 다수 클래스 데이터를 랜덤하게 선택하여 제거
- 다수 : 소수 비율이 정확히 1 : 1이 됨
- 중요한 데이터의 손실 발생 위험이 존재 (가장 위험한 방법)
2) CondensedNearestNeighbour
- 뭉쳐져있는 다수 클래스를 중복으로 가정하고 제거 -> 결정결계와 먼 다수클래스를 제거
- 다수 클래스 데이터에서 랜덤하게 데이터를 선택하며, k개의 가장 가까운 이웃을 확인 후, 그 이웃이 다수클래스 (같은편)이면 제거
3) EditedNearestNeighbours
- 결정경계 근처에 있는 다수클래스 일부를 제거 -> 좀 더 단순한 결정 경계가 만들어짐
- 다수 클래스 데이터에서 랜덤하게 데이터 선택, k개의 가장 가까운 이웃을 확인, 그 이웃이 소수클래스 (다른편)이면 제거
4) TomekLinks
- TomekLink : 소수, 다수 클래스 각각 관측치 기준 가장 가까운 이웃이 서로가 되는 한 쌍
- TomekLink 쌍 중에서 다수 클래스를 제거하는 방식
- 결정경계가 복잡하지 않은 경우는 다수 클래스가 제거되지 않을 수 있음
5) OneSidedSelection (OSS)
- TomekLink + CNN
[ 예제 - 각 언더샘플링 기법 비교 ]
# 1. 데이터 로딩
import pandas as pd
df = pd.read_csv('undersampling_data.csv')
X = df[['x1','x2']]
y = df['y']
# 각 클래스 도수 / 비율 확인
y.value_counts()
y.value_counts(normalize = True) # 0:다수클래스 (91.6%), 1: 소수클래스(8.4%)
y.value_counts(normalize = True)
Out[138]:
y
0 0.916045
1 0.083955
Name: proportion, dtype: float64
# 2. 언더샘플링
from imblearn.under_sampling import RandomUnderSampler, CondensedNearestNeighbour, EditedNearestNeighbours, TomekLinks, OneSidedSelection
import imblearn.under_sampling
dir(imblearn.under_sampling)
m1 = RandomUnderSampler()
m2 = CondensedNearestNeighbour()
m3 = EditedNearestNeighbours()
m4 = TomekLinks()
m5 = OneSidedSelection()
X1, y1 = m1.fit_resample(X,y)
print(pd.Series(y1).value_counts(normalize = True))
print(pd.Series(y1).value_counts(normalize = True))
y
0 0.5
1 0.5
Name: proportion, dtype: float64
X2, y2 = m2.fit_resample(X,y)
print(pd.Series(y2).value_counts(normalize = True))
print(pd.Series(y2).value_counts(normalize = True))
y
1 0.803571
0 0.196429
Name: proportion, dtype: float64
X3, y3 = m3.fit_resample(X,y)
print(pd.Series(y3).value_counts(normalize = True))
print(pd.Series(y3).value_counts(normalize = True))
y
0 0.913958
1 0.086042
Name: proportion, dtype: float64
X4, y4 = m4.fit_resample(X,y)
print(pd.Series(y4).value_counts(normalize = True))
print(pd.Series(y4).value_counts(normalize = True))
y
0 0.915254
1 0.084746
Name: proportion, dtype: float64
X5, y5 = m5.fit_resample(X,y)
print(pd.Series(y5).value_counts(normalize = True))
print(pd.Series(y5).value_counts(normalize = True))
y
1 0.511364
0 0.488636
Name: proportion, dtype: float64
# 3. 시각화
import matplotlib.pyplot as plt
fig, ax = plt.subplots(2,3)
# 기본 산점도
ax[0,0].scatter(X[y==0,0], X[y==0,1], c='crimson', s=40, alpha = 0.8)
ax[0,0].scatter(X[y==1,0], X[y==1,1], c='steelblue', s=40, alpha = 0.7)
ax[0,0].set_title('original')
# RandomUnderSampler
ax[0,1].scatter(X[y==0,0], X[y==0,1], c='salmon', s=40, alpha = 0.8)
ax[0,1].scatter(X1[y1==0,0], X1[y1==0,1], c='crimson', s=40, alpha = 0.8)
ax[0,1].scatter(X1[y1==1,0], X1[y1==1,1], c='steelblue', s=40, alpha = 0.7)
ax[0,1].set_title('RandomUnderSampler')
# CondensedNearestNeighbour
ax[0,2].scatter(X[y==0,0], X[y==0,1], c='salmon', s=40, alpha = 0.8)
ax[0,2].scatter(X2[y2==0,0], X2[y2==0,1], c='crimson', s=40, alpha = 0.8)
ax[0,2].scatter(X2[y2==1,0], X2[y2==1,1], c='steelblue', s=40, alpha = 0.7)
ax[0,2].set_title('CondensedNearestNeighbour')
# EditedNearestNeighbours
ax[1,0].scatter(X[y==0,0], X[y==0,1], c='salmon', s=40, alpha = 0.8)
ax[1,0].scatter(X3[y3==0,0], X3[y3==0,1], c='crimson', s=40, alpha = 0.8)
ax[1,0].scatter(X3[y3==1,0], X3[y3==1,1], c='steelblue', s=40, alpha = 0.7)
ax[1,0].set_title('EditedNearestNeighbours')
# TomekLinks
ax[1,1].scatter(X[y==0,0], X[y==0,1], c='salmon', s=40, alpha = 0.8)
ax[1,1].scatter(X4[y4==0,0], X4[y4==0,1], c='crimson', s=40, alpha = 0.8)
ax[1,1].scatter(X4[y4==1,0], X4[y4==1,1], c='steelblue', s=40, alpha = 0.7)
ax[1,1].set_title('TomekLinks')
# OneSidedSelection
ax[1,2].scatter(X[y==0,0], X[y==0,1], c='salmon', s=40, alpha = 0.8)
ax[1,2].scatter(X5[y5==0,0], X5[y5==0,1], c='crimson', s=40, alpha = 0.8)
ax[1,2].scatter(X5[y5==1,0], X5[y5==1,1], c='steelblue', s=40, alpha = 0.7)
ax[1,2].set_title('OneSidedSelection')

-02 오버샘플링
소수 클래스 관측치를 다수 클래스 관측치수와 유사하게 임의로 생성하여 균등하게 맞추는 샘플링 기법
1) RandomOverSampler
- 소수 클래스 관측치를 동일하게 재생성 -> 데이터의 중복 발생
- 1:1의 비율로 데이터를 샘플하기 때문에 빈도를 잘 맞추어준다.
2) SMOTE
- 이웃들 중 하나를 선택하여 이웃과의 사이에 새로운 데이터를 생성
- 임의의 소수 클래스 데이터 선택, k개의 가장 가까운 이웃 데이터 확인, 그 중 랜덤하게 선택된 다수클래스와의 사이에 소수클래스 생성
- 결정경계 근처가 아닌 공간에도 소수 클래스 데이터가 생성될 수 있다. = 소수와 다수가 밀집한 공간이 아님에도 불구하고 소수클래스를 배치하면 결정경계가 이동할 수도 있다.
3) BorderlineSMOTE
- SMOTE와 결정경계가 아닌 지역에도 소수클래스를 생성하는 단점을 보완
- 소스클래스를 다음의 세 집단으로 구분하여 위험 지역에만 소수 클래스를 생성
> 위험 : 이웃 중 다수가 다수 클래스
> 안전 : 이웃 중 소수가 다수 클래스
> 심플 : 이웃 중 다수 클래스가 아예 없는 경우
- 임의의 소수 클래스 데이터 선택, k개의 가장 가까운 이웃 데이터 확인, 그 중 랜덤하게 선택된 다수 클래스와의 사이에 소수클래스 생성
- 결정경계 근처에만 소수 클래스 데이터가 생성됨
4) ADASYN
- 인근 다수클래스 비율 확인 -> 비율을 반영하여 소수클래스 생성
# 1. 데이터 로딩
import pandas as pd
df = pd.read_csv('oversampling_data.csv')
X = df[['x1','x2']].values
y = df['y'].values
# 각 클래스 도수 / 비율 확인
pd.Series(y).value_counts()
pd.Series(y).value_counts(normalize = True) # 0 : 다수클래스 (62%), 1 : 소수클래스 (38%)
# 2. 오버샘플링
from imblearn.over_sampling import RandomOverSampler, SMOTE, BorderlineSMOTE, ADASYN
import imblearn.over_sampling
m1 = RandomOverSampler(random_state=0)
m2 = SMOTE(random_state=0, k_neighbors=5)
m3 = BorderlineSMOTE(random_state=0, k_neighbors=5)
m4 = ADASYN(random_state=0, n_neighbors=5)
X1, y1 = m1.fit_resample(X,y)
print(pd.Series(y1).value_counts())
X2, y2 = m2.fit_resample(X,y)
print(pd.Series(y2).value_counts())
X3, y3 = m3.fit_resample(X,y)
print(pd.Series(y3).value_counts())
X4, y4 = m4.fit_resample(X,y)
print(pd.Series(y4).value_counts())
# 3. 시각화
import matplotlib.pyplot as plt
fig, ax = plt.subplots(2,3)
# 기본 산점도
ax[0,0].scatter(X[y==0,0], X[y==0,1], c='crimson', s=40, alpha = 0.8)
ax[0,0].scatter(X[y==1,0], X[y==1,1], c='steelblue', s=40, alpha = 0.7)
ax[0,0].set_title('original')
# RandomOverSampler
ax[0,1].scatter(X1[y1==0,0], X1[y1==0,1], c='crimson', s=40, alpha = 0.8)
ax[0,1].scatter(X1[y1==1,0], X1[y1==1,1], c='steelblue', s=40, alpha = 0.7)
ax[0,1].set_title('RandomOverSampler')
# SMOTE
ax[0,2].scatter(X2[y2==0,0], X2[y2==0,1], c='crimson', s=40, alpha = 0.8)
ax[0,2].scatter(X2[y2==1,0], X2[y2==1,1], c='steelblue', s=40, alpha = 0.7)
ax[0,2].set_title('SMOTE')
# BorderlineSMOTE
ax[1,0].scatter(X3[y3==0,0], X3[y3==0,1], c='crimson', s=40, alpha = 0.8)
ax[1,0].scatter(X3[y3==1,0], X3[y3==1,1], c='steelblue', s=40, alpha = 0.7)
ax[1,0].set_title('BorderlineSMOTE')
# ADASYN
ax[1,1].scatter(X4[y4==0,0], X4[y4==0,1], c='crimson', s=40, alpha = 0.8)
ax[1,1].scatter(X4[y4==1,0], X4[y4==1,1], c='steelblue', s=40, alpha = 0.7)
ax[1,1].set_title('ADASYN')

-03 가중치 조절
소수 클래스 예측 실패에 대해 패널티를 부여하는 기법
-04 임계값 이동
이진 클래스) 소수 클래스 확률이 0.5 이상이면 소수클래스로 예측하는데, 이 임계값을 낮춤으로써 소수 클래스가 더 많이 나올 수 있도록 조절하는 방식
다수 클래스) 각 클래스별 확률을 각 클래스별 도수로 나눈 값으로 클래스 예측을 수행하는 방식
'아이티윌_데이터 분석 55기 > 강의내용 필기_분석(Python)' 카테고리의 다른 글
| #10 회귀모형과 패널티 모형, 부스팅 이론 (0) | 2026.06.18 |
|---|---|
| #8 클래스 불균등 처리 (0) | 2026.06.15 |
| #6 교호작용 및 효과 검증 (0) | 2026.06.09 |
| #5 5일차_데이터 분석 과정, 로지스틱 회귀, 교호작용 (0) | 2026.06.08 |
| #4 4일차_복사 / SVM·RF 실습 / 모델링 파이프 라인 구축 / 분류 모델 / knn (0) | 2026.06.05 |