DecisionTreeClassifier 의사결정나무 : 소득수준 예측 예제
목적 : 성인 인구의 인구통게 / 직업 정보를 기반으로 고소득( >50K ) 여부 예측
모델 : DecisionTreeClassifier (의사결정나무)
데이터 : adult.csv
전처리 : 불필요 변수 제거, 범주형 -> 원핫인코딩
평가 : Confusion Matrix + Accuracy, Precision, Recall, F1 score => 모델의 예측결과를 정확하고 상세하게 평가하기 위함
컨퓨전 매트릭스로 모델의 세부적 오류를 점검하고, 성능평가지표를 통해 모델의 성능을 다양한 관점에서 종합적으로 평가할 수 있음.
+) 컨퓨전 매트릭스 : 분류 문제에서 모델이 얼마나 잘 예측했는지 나타내는 표
예측결과를 실제값과 비교해서 맞춘 것, 틀린 것을 보기 쉽게 정리.
클래스가 두 개 이상 (다중 분류)이거나 클래스 간 불균형이 있을 때 명확한 성능 파악 가능
<장점>
- 정확히 어느 부분에서 틀렸는지 알 수 있음
- 정확도 (Accuracy) 외에도 더 정밀한 평가 (Precision, Recall, F1-score 등) 가능해짐
- FP (잘못된 긍정), FN (잘못된 부정) 구분 가능
+) 성능평가지표 (metrics) : 모델이 얼마나 정확하게 예측했는지 정량적으로 평가하기 위해 사용하는 방법
- Accuracy (정확도): 전체 예측 중 정확히 예측한 비율
- Precision (정밀도): 모델이 긍정이라고 한 것 중 실제 긍정인 비율
- Recall (재현율): 실제 긍정인 것 중 모델이 긍정이라고 맞춘 비율
- F1-score: Precision과 Recall의 조화평균으로, 두 가지를 동시에 고려하는 지표
< 전체 흐름 요약 : 데이터 분석 순서 >
1. 데이터 준비 및 읽기
2. 전처리 (타겟 변환, 인코딩, 결측치 처리 등)
3. 데이터 분할 (train/test)
4. 모델 선택 및 학습 (의사결정나무)
5. 예측 수행
6. 평가 지표로 모델 성능 측정
7. 시각화로 모델 내부 구조 확인
< 필수 라이브러리 >
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn import tree # 의사결정나무
import matplotlib.pyplot as plt
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay # 컨퓨젼매트릭스, 시각화
import sklearn.metrics as metrics # 성능평가지표
# 데이터 로딩
df = pd.read_csv('asset/adult.csv')
df.info()
--
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 48842 entries, 0 to 48841
Data columns (total 15 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 age 48842 non-null int64
1 workclass 48842 non-null object
2 fnlwgt 48842 non-null int64
3 education 48842 non-null object
4 education_num 48842 non-null int64
5 marital_status 48842 non-null object
6 occupation 48842 non-null object
7 relationship 48842 non-null object
8 race 48842 non-null object
9 sex 48842 non-null object
10 capital_gain 48842 non-null int64
11 capital_loss 48842 non-null int64
12 hours_per_week 48842 non-null int64
13 native_country 48842 non-null object
14 income 48842 non-null object
dtypes: int64(6), object(9)
memory usage: 5.6+ MB
# 데이터 전처리
df['income'] = np.where(df['income']=='>50K', 'high', 'low')
-> income 열은 문자열 범주로 되어있음. 이진 분류용으로 가공
-> 연소득 5만달러 초과하면 hign, 그렇지 않으면 low
print(df['income'].value_counts(normalize=True)) #범주의 비율 출력
# low 0.760718
# high 0.239282
df = df.drop(columns='fnlwgt')
-> fnlwgt는 인구통계가중치로 모델 예측에 직접적 의미 없음 -> 삭제
# 원핫인코딩을 이용한 문자타입변수를 숫자타입으로 변환 (데이터를 1이나 0으로 표시)
target = df['income'] #1개변수 (타겟변수 = 종속변수)
df = df.drop(columns='imcome') #14ro qustn (예측변수 = 독립변수)
df = pd.get_dummies(df) #원핫인코딩
df['income'] = target
-> 원핫인코딩으로 문자열(범주형 변수) -> 수치형 벡터
타켓 income은 예측 대상이므로 따로 보관 후 다시 합침
# 데이터 분할
df_train, df_test = train_test_split(
df, test_size=0.3, stratify=df['income'],random_state=1234
)
-> 전체 데이터 70% 훈련 / 30% 테스트 분리
stratify 옵션으로 high/low 클래스 비율을 동일하게 유지
# 모델 생성 및 학습
clf = tree.DecisionTreeClassfier(random_state=1234, max_depth=3)
# 의사결정나무 생성 (최대 깊이 제한 : 3)
# 깊이가 깊을수록 복잡해지고, 과적합 위험 높아짐
train_x = df_train.drop(columns='income')
train_y = df_train['income']
model = clf.fit(X=train_x, y=train_y)
# 의사결정나무 시각화
# 그래프 설정
plt.rcParams.update({
'figure.dpi' : '100', #그래프 해상도
'figure.figsize' : [12:8] #그래프 크기
})
# 의사결정나무 그래프
tree.plot_tree (
model, #모델
feature_names = train_x.columns, #예측변수명들
class_names = ['high', 'low'], #타겟변수 클래스명 (알파벳 오름차순으로 맞추기)
proportion = True, #클래스 배분 비율 표시 여부
filled = True, #채움 여부
rounded = Ture, # 노드 테두리 둥글게 할래?
impurity = False, # 분순도 표시 여부
label = 'root' #제목 표시 위치
fontsize = 10 #글자 크기
)
plt.show()
< 의사결정나무 시각화 이해하기 >
데이터에는 사람들의 여러 정보 (결혼 여부, 나이, 교육 수준, 자본 이득 등)가 있고, 이 나무는 그런 정보를 이용해 소득이 높은(high) 사람과 낮은(low) 사람을 구분하는 방법을 알려준다
=> 즉 이 나무는 질문을 통해 high냐 low냐를 결정하고 있음.
* Node : 네모 상자
- 조건 (질문) : 어떤 기준으로 데이터를 나눌지 결정 (결혼했뉘? 자본 이득? 등)
- samples : 이 질문에서 고려하는 데이터의 비율
- value : [high 비율, low 비율] -> 소득이 높거나 낮은 사람들의 비율
- class : 해당 노드에서 가장 많은 클래스 (대부분 low 또는 high)
- 색상 : 파란색 계열 ( 대부분 low : 소득 낮음) | 주황색 계열 (대부분 high : 소득 높음)
* 실제로 해석해보자!
1. 첫 번째 질문 : 결혼 여부가 0.5 이하인가? = 결혼하지 않은 사람인가?
결혼하지 않은 사람은 왼쪽(True), 결혼한 사람은 오른쪽 (False)으로 나뉨 (일반적으로 원-핫 인코딩된 경우 기혼이면 1, 비혼이면 0으로 나타내서 0.5 이하면 비혼으로 분류함)
이 질문이 가장 먼저 나온 이유는 결혼 여부가 사람들의 소득 수준을 가장 명확히 구분할 수 있었기 때문
2. 왼쪽 가지 (True, 결혼하지 않은 사람들)
결혼하지 않은 사람들 중 자본이득(capital_gain)이 7073.5 이하인가?
이걸 다시 두 그룹으로 나누면 :
자본 이득이 작은 사람 (왼쪽) -> 대부분 소득 낮음 (low)
자본 이득이 큰 사람 (오른쪽) -> 그 중에서도 나이가 20세 이하인가? 를 질문해서 다시 나눈다.
- 나이가 어리면 왼쪽, 대부분 소득이 낮고(low)
- 나이가 많으면 오른쪽, 대부분 소득이 높다 (high)
=> 결혼하지 않고 + 자본 이득이 크고 + 나이가 많다면 소득이 높을가능성이 매우 높다.
3. 오른쪽 가지 (결혼한 사람들)
결혼한 사람들은 교육수준이 12.5 이하인가?를 질문해서 다시 나눔
- 교육수준이 낮은 사람(왼쪽)
다시 자본이득이 5095.5 이하인가? 질문을 통해서
자본이득이 작으면 대부분 소득이 낮고, 크면 소득이 높음
- 교육수준이 높은 사람 (오른쪽)
자본이득 질문 동일
결혼을 했고 + 교육수준이 높고 + 자본이득이 크면 대부분 소득 높을 가능성이 높음
4. 퍼센트의 의미
맨 위 노드에서 100%는 전체 데이터를 의미하고
아래 노드로 내려가면서 이 데이터가 나눠져서 비율이 줄어드는 것
각 노드에서 숫자가 점점 작아지면서 데이터를 작게 나눠가는 과정을 보여준다
예측을 위한 예측변수, 타겟 변수 추출 + 예측
test_x = df_test.drop(columns='income')
test_y = df_test['income']
# 예측
df_test['pred'] = model.predict(test_x)
#print(df_test)
--- 출력 ---
age education_num ... income pred
11712 58 10 ... low low
24768 39 10 ... low low
26758 31 4 ... low low
14295 23 9 ... low low
3683 24 9 ... low low
... ... ... ... ... ...
11985 24 13 ... low low
48445 35 13 ... high high
19639 41 9 ... high low
21606 29 4 ... low low
3822 31 13 ... low low
[14653 rows x 109 columns]
df_test['pred'] = model.predict(test_x) : 학습된 model(의사결정나무)을 test_x에 적용해 각 샘플의 예측 레이블을 반환
반환된 예측값을 df_test에 pred라는 새 칼럼으로 추가
원본 df_test에 예측 결과를 함께 보관하면, 오분류 사례를 식별하거나 추가 분석을 수행하기에 용이함
# 예측 성능 평가
# 컨퓨젼매트릭스
conf_mat = confusion_matrix(
y_true = df_test['income'], # 실제값
y_pred = df_test['pred'], # 예측값
labels = ['high', 'low'] # 레이블 (클래스 배치 순서, 문자 오름차순)
)
print(conf_mat)
# 그래프 설정 초기화 : 이전에 plt.rcParams.update()등으로 변경된 옵션이 있을 때, 히트맵에 영향이 가지 않도록 리셋
plt.rcdefaults()
# 컨퓨젼매트릭스를 히트맵으로 표시
p = ConfusionMatrixDisplay(
confusion_matrix = conf_mat, # 매트릭스 데이터
display_labels = ('high', 'low') # 타겟변수 클래스명
)
p.plot(cmap = 'Blues') # 컬러맵
plt.show()
혼동행렬은 모델이 어디서 주로 틀렸는지 (FN vs FP)를 파악하는 데 필수적.
히트맵으로 시각화 하면, 숫자 뿐 아니라 색 농도로 정.오분류 비율을 직관적으로 확인할 수 있음
labels와 display_labels 순서를 일치시켜야 축 순서 혼동을 방지
히트맵 이미지 해석
1. True Positive (TP) : 1801
실제 high를 올바르게 high로 예측한 건수
2. False Negative(FN) : 1705
실제 high를 잘못 low로 예측한 건수 -> 고소득자 누락
3. False Positive(FP) : 582
실제 low를 잘못 high로 예측한 건수 -> 저소득자를 고소득으로 오분류
4. True Negative(TN) : 10565
실제 low를 올바르게 low로 예측한 건수
< 성능평가 지표 >
1. 정확도 (Accuracy)
: 정답을 얼마나 맞췄니!
(매우 직관적이지만 불균형 클래스에서는 왜곡될 수 있으므로 주의)
# 정확도
acc = metrics.accuracy_score(
y_true = df_test['income'], # 실제값
y_pred = df_test['pred'] # 예측값
)
print(acc) # 0.8439227461953184
2. 정밀도 (Precision)
: 모델이 high라고 예측한 경우 실제 high인 비율
: 오탐지 (FP)를 줄이려 할 때 중요한 지표
: 보통 긍정 클래스의 정확성을 평가할 때 활용
- 신용평가, 추천시스템에 매우 중요
- 정밀도가 낮다면 모델의 high 예측을 신뢰하기 어렵다고 판단
# 정밀도
pre = metrics.precision_score(
y_true = df_test['income'], # 실제값
y_pred = df_test['pred'], # 예측값
pos_label = 'high' # 관심 클래스
)
print(pre) #0.7557700377675199
3. 재현율 (Recall)
: 실제 high 클래스 중에서 얼마나 많은 high를 놓치지 않고 잡았는지 비율
: 즉, False Nagative를 줄이는 데 중요한 지표
- 암진단, 사기탐지와 같은 민감한 분야에서는 FN이 매우 위험하기 때문에 재현율을 특히 중요시 함
- 재현율이 낮다면 중요한 사례를 놓치고 있다는 의미이므로 모델 조정이 필요
rec = metrics.recall_score(
y_true = df_test['income'], # 실제값
y_pred = df_test['pred'], # 예측값
pos_label = 'high' # 관심 클래스
)
print(rec) #0.5136908157444381
4. F1-score
: 정밀도와 재현율을 조화롭게 평가하는 조화평균
정밀도와 재현율 사이의 균형을 평가하는 데 최적의 지표
- 정밀도, 재현율이 모두 중요한 상황에서 사용됨
-
f1 = metrics.f1_score(
y_true = df_test['income'], # 실제값
y_pred = df_test['pred'], # 예측값
pos_label = 'high' # 관심 클래스
)
print(f1) #0.6116488368143997
<정밀도와 재현율 더 이해해보기>
정밀도 : 예측한 것이 얼마나 정확한지
재현율 : 놓치면 큰일남
* 정밀도와 재현율이 중요한 이유1 (금융거래 사기 탐지)
금융기관에서 실제 사기를 놓치면 큰 피해 발생 (FN이 매우 위험하다고 볼 수 있다)
대부분 재현율을 중요하게 생각 => 즉 사기일 가능성이 조금이라도 있으면 일단 잡아냄.
하지만 정밀도 역시 고려하지 않을 수 없음. 왜?
정밀도가 너무 낮으면 정상거래를 계속 사기로 잘못 판단해서 고객이 불편하고 은행원한테 화냄. (실제로 화 많이 냄 ㅠㅠ)
따라서 :
1차로 재현율을 극대화 해 일단 모든 의심 거래를 잡고,
2차로 사람이 직접 확인하는 절차를 통해 정밀도를 높여 정상거래를 분류
* 정밀도와 재현율이 중요한 이유2 ( 공항 보안 검색대 )
정밀도는 위험하다고 경고가 울린 사람 중 실제 위험한 사람의 비율
정밀도가 낮으면?
무고한 사람이 계속 걸려서 시간이 오래 걸리고 사람들의 불만이 증가
재현율은 실제 위험한 사람 중 시스템이 탐지해낸 비율
재현율이 낮으면, 진짜 위험한 사람이 통과해 큰 사고로 이어질 수 있다.
(마약탐지견은 재현율을 높이는 데 큰 역할을 하고 있지 않을까)
따라서 :
공항은 사고위험(FN)을 절대 놓치면 안되기 때문에 (생명직결) 재현율을 최우선으로 설정
그 다음 2차 검사를 통해 실제 위험이 아닌 사람을 빠르게 분류하여 정밀도를 높인다.
* 반대로 정밀도를 더 중요시하는 분야는?
리스크 최소화를 위해 대부분 재현율이 최우선시 될테지만,, 문득 궁금해진 건 정밀도를 우선시 하는 분야나 업무가 있을까?
-> 잘못된 긍정 (FP)의 피해가 크면 정밀도를 우선시 하겠지?
< 정밀도가 더 중요한 사례 >
- 이메일 스팸 필터링 : 중요한 메일이 스팸으로 가면 업무적 손해 발생
- 고비용 정밀 의료 검사 : 1차 검사는 일단 조금이라도 위험하면 다 잡아내지만, 비용이 크고 위험한 수술 등 2차 단계는 정밀도가 높아야 오진을 방지할 수 있음
- 신용평가 : 신용이 안좋은 사람을 좋다고 잘못평가하면 금융기관 손실로 직결
- 법적 판결 : 무죄를 유죄라고 판결하면 인권+윤리+언론의 질타