-
피마 인디언 당뇨병 예측머신러닝 2024. 1. 16. 15:36
데이터 로드¶
In [1]:# 라이브러리 from IPython.core.interactiveshell import InteractiveShell InteractiveShell.ast_node_interactivity = 'all' import numpy as np import pandas as pd import matplotlib.pyplot as plt %matplotlib inline from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score, precision_score, recall_score, roc_auc_score from sklearn.metrics import f1_score, confusion_matrix, precision_recall_curve, roc_curve from sklearn.preprocessing import StandardScaler, Binarizer from sklearn.linear_model import LogisticRegression # https://www.kaggle.com/datasets/uciml/pima-indians-diabetes-database df = pd.read_csv('diabetes.csv') print(df['Outcome'].value_counts()) df.head(3)
Outcome 0 500 1 268 Name: count, dtype: int64
Out[1]:Pregnancies Glucose BloodPressure SkinThickness Insulin BMI DiabetesPedigreeFunction Age Outcome 0 6 148 72 35 0 33.6 0.627 50 1 1 1 85 66 29 0 26.6 0.351 31 0 2 8 183 64 0 0 23.3 0.672 32 1 - Pregnancies: 임신 횟수
- Glucose: 포도당 부하 검사 수치
- BloodPressure: 혈압(mm Hg)
- SkinThickness: 팔 삼두근 뒤쪽의 피하지방 측정값(mm)
- Insulin: 혈청 인슐린(mu U/ml)
- BMI: 체질량지수(체중(kg)/(키(m))^2)
- DiabetesPedigreeFunction: 당뇨 내력 가중치 값
- Age: 나이
- Outcome: 클래스 결정 값(0 또는 1)
전체 768개의 데이터 중에서 Negative 값 0이 500개, Positive 값 1이 268개로 Negative가 상대적으로 많음
In [2]:df.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 768 entries, 0 to 767 Data columns (total 9 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Pregnancies 768 non-null int64 1 Glucose 768 non-null int64 2 BloodPressure 768 non-null int64 3 SkinThickness 768 non-null int64 4 Insulin 768 non-null int64 5 BMI 768 non-null float64 6 DiabetesPedigreeFunction 768 non-null float64 7 Age 768 non-null int64 8 Outcome 768 non-null int64 dtypes: float64(2), int64(7) memory usage: 54.1 KB
Null 값은 없으며 자료형을 살펴보면 모두 숫자형이므로 별도의 처리가 필요하지 않은 것으로 보임
In [3]:# 피처 데이터 세트 X, 레이블 데이터 세트 y 추출 # 맨 끝이 Outcome 컬럼으로 레이블 값, 컬럼 위치 -1을 이용해 추출 X = df.iloc[:, :-1] y = df.iloc[:, -1] # 훈련 데이터 세트, 테스트 데이터 세트로 분리 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=156, stratify=y)
모델 학습 / 예측 / 평가¶
In [4]:# 정밀도 = TP / (FP + TP) # 재현율 = TP / (FN + TP) def get_clf_eval(y_test, pred=None, pred_proba=None): confusion = confusion_matrix(y_test, pred) accuracy = accuracy_score(y_test, pred) precision = precision_score(y_test, pred) recall = recall_score(y_test, pred) f1 = f1_score(y_test, pred) # ROC-AUC 추가 roc_auc = roc_auc_score(y_test, pred_proba) print('오차 행렬') print(confusion) print('정확도: {0:.4f}, 정밀도: {1:.4f}, 재현율: {2:.4f}, F1: {3:.4f}, AUC: {4:.4f}'.format(accuracy, precision, recall, f1, roc_auc))
In [5]:lr_clf = LogisticRegression(solver='liblinear') lr_clf.fit(X_train, y_train) pred = lr_clf.predict(X_test) pred_proba = lr_clf.predict_proba(X_test)[:, 1] get_clf_eval(y_test, pred, pred_proba)
Out[5]:
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.LogisticRegression(solver='liblinear')
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.LogisticRegression(solver='liblinear')
오차 행렬 [[87 13] [22 32]] 정확도: 0.7727, 정밀도: 0.7111, 재현율: 0.5926, F1: 0.6465, AUC: 0.8083
전체 데이터의 65%가 Negative이므로 재현율 성능에 초점을 맞춰 보기
정밀도 재현율 곡선¶
In [6]:def precision_recall_curve_plot(y_test, pred_proba_c1): # threshold ndarray와 이 threshold에 따른 정밀도, 재현율 ndarray 추출 precisions, recalls, thresholds = precision_recall_curve(y_test, pred_proba_c1) # X축을 threshold 값으로, Y축은 정밀도, 재현율 값으로 각각 Plot 수행, 정밀도는 점선으로 표시 plt.figure(figsize=(8, 6)) threshold_boundary = thresholds.shape[0] plt.plot(thresholds, precisions[0:threshold_boundary], linestyle='--', label='precision') plt.plot(thresholds, recalls[0:threshold_boundary], label='recall') # threshold 값 X축의 Scale을 0.1 단위로 변경 start, end = plt.xlim() plt.xticks(np.round(np.arange(start, end, 0.1), 2)) # X축, Y축 label과 legend, 그리고 grid 설정 plt.xlabel('Threshold value') plt.ylabel('Precision and Recall value') plt.legend() plt.grid plt.show()
In [7]:pred_proba_c1 = lr_clf.predict_proba(X_test)[:, 1] precision_recall_curve_plot(y_test, pred_proba_c1)
재현율 곡선을 보면 임곗값을 0.42 정도로 낮추면 정밀도와 재현율이 어느 정도 균형을 맞출 것, 하지만 두 개의 지표 모두 0.7이 안 되는 수치로 보임
데이터 전처리¶
In [8]:df.describe()
Out[8]:Pregnancies Glucose BloodPressure SkinThickness Insulin BMI DiabetesPedigreeFunction Age Outcome count 768.000000 768.000000 768.000000 768.000000 768.000000 768.000000 768.000000 768.000000 768.000000 mean 3.845052 120.894531 69.105469 20.536458 79.799479 31.992578 0.471876 33.240885 0.348958 std 3.369578 31.972618 19.355807 15.952218 115.244002 7.884160 0.331329 11.760232 0.476951 min 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.078000 21.000000 0.000000 25% 1.000000 99.000000 62.000000 0.000000 0.000000 27.300000 0.243750 24.000000 0.000000 50% 3.000000 117.000000 72.000000 23.000000 30.500000 32.000000 0.372500 29.000000 0.000000 75% 6.000000 140.250000 80.000000 32.000000 127.250000 36.600000 0.626250 41.000000 1.000000 max 17.000000 199.000000 122.000000 99.000000 846.000000 67.100000 2.420000 81.000000 1.000000 데이터 값을 보면 min() 값이 0으로 돼 있는 피처가 상당히 많음
ex) Glucose는 포도당 수치인데 min 값이 0인 것은 말이 되지 않음
In [9]:plt.hist(df['Glucose'], bins=100) plt.show()
Out[9]:(array([ 5., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 3., 0., 1., 1., 1., 1., 3., 4., 4., 6., 4., 7., 12., 9., 17., 10., 15., 20., 16., 20., 17., 20., 26., 22., 19., 25., 25., 20., 18., 21., 18., 17., 17., 21., 25., 14., 25., 12., 10., 10., 16., 13., 10., 11., 12., 16., 5., 9., 6., 11., 5., 10., 4., 9., 7., 6., 5., 5., 7., 4., 3., 6., 10., 4., 3., 5., 6., 2., 2., 5., 7., 2.]), array([ 0. , 1.99, 3.98, 5.97, 7.96, 9.95, 11.94, 13.93, 15.92, 17.91, 19.9 , 21.89, 23.88, 25.87, 27.86, 29.85, 31.84, 33.83, 35.82, 37.81, 39.8 , 41.79, 43.78, 45.77, 47.76, 49.75, 51.74, 53.73, 55.72, 57.71, 59.7 , 61.69, 63.68, 65.67, 67.66, 69.65, 71.64, 73.63, 75.62, 77.61, 79.6 , 81.59, 83.58, 85.57, 87.56, 89.55, 91.54, 93.53, 95.52, 97.51, 99.5 , 101.49, 103.48, 105.47, 107.46, 109.45, 111.44, 113.43, 115.42, 117.41, 119.4 , 121.39, 123.38, 125.37, 127.36, 129.35, 131.34, 133.33, 135.32, 137.31, 139.3 , 141.29, 143.28, 145.27, 147.26, 149.25, 151.24, 153.23, 155.22, 157.21, 159.2 , 161.19, 163.18, 165.17, 167.16, 169.15, 171.14, 173.13, 175.12, 177.11, 179.1 , 181.09, 183.08, 185.07, 187.06, 189.05, 191.04, 193.03, 195.02, 197.01, 199. ]), <BarContainer object of 100 artists>)
min() 값이 0으로 되어 있는 피처에 대해 0 값의 건수 및 전체 데이터 건수 대비 몇 퍼센트의 비율로 존재하는지 확인
확인할 피처는 'Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI' (출산 횟수는 제외)
In [10]:# 0 값을 검사할 피처명 리스트 zero_features = ['Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI'] # 전체 데이터 건수 total_count = df['Glucose'].count() # 피처별로 반복하면서 데이터 값이 0인 데이터 건수 추출, 퍼센트 계산 for feature in zero_features: zero_count = df[df[feature] == 0][feature].count() print('{0}의 0 건수는 {1}, 퍼센트는 {2:.2f} %'.format(feature, zero_count, 100*zero_count/total_count))
Glucose의 0 건수는 5, 퍼센트는 0.65 % BloodPressure의 0 건수는 35, 퍼센트는 4.56 % SkinThickness의 0 건수는 227, 퍼센트는 29.56 % Insulin의 0 건수는 374, 퍼센트는 48.70 % BMI의 0 건수는 11, 퍼센트는 1.43 %
SkinThickness와 Insulin의 0 값은 각각 29.56%, 48.7%로 상당히 많으나, 전체 데이터 건수가 많지 않아 데이터를 일괄적으로 삭제할 경우 학습이 제대로 안 될 수 있음
→ 따라서 위 피처의 0 값을 평균값으로 대체
In [11]:# zero_features 리스트 내부에 저장된 개별 피처들에 대해 0 값을 평균 값으로 대체 mean_zero_features = df[zero_features].mean() df[zero_features] = df[zero_features].replace(0, mean_zero_features)
0 값을 평균 값으로 대체한 데이터 세트에 피처 스케일링을 적용해 변환
로지스틱 회귀의 경우 일반적으로 숫자 데이터에 스케일링을 적용하는 것이 좋음
전처리 후 모델 재생성¶
In [12]:# 대체한 데이터 세트로 다시 학습 / 테스트 데이터 세트로 나누고 로지스틱 회귀를 적용해 성능 평가 지표 확인 X = df.iloc[:, :-1] y = df.iloc[:, -1] # StandardScaler 클래스를 이용해 피처 데이터 세트에 일괄적으로 스케일링 적용 scaler = StandardScaler() X_scaled = scaler.fit_transform(X) X_train, X_test, y_train, y_test = train_test_split(X_scaled, y, test_size=0.2, random_state=156, stratify=y)
전처리된 데이터 세트로 모델 학습 / 예측 / 평가¶
In [13]:lr_clf = LogisticRegression() lr_clf.fit(X_train, y_train) pred = lr_clf.predict(X_test) pred_proba = lr_clf.predict_proba(X_test)[:, 1] get_clf_eval(y_test, pred, pred_proba)
Out[13]:
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.LogisticRegression()
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.LogisticRegression()
오차 행렬 [[90 10] [21 33]] 정확도: 0.7987, 정밀도: 0.7674, 재현율: 0.6111, F1: 0.6804, AUC: 0.8433
데이터 변환과 스케일링을 통해 성능 수치가 일정 수준 개선되었으나 여전히 재현율 수치는 개선이 필요해 보임
분류 결정 임곗값을 변화시키면서 재현율 값의 성능 수치가 어느 정도 개선되는지 확인
재현율 값 성능 수치 개선¶
In [14]:# 임곗값을 0.3에서 0.5까지 0.03씩 변화시키면서 재현율과 다른 평가 지표의 값 변화 출력 thresholds = [0.3, 0.33, 0.36, 0.39, 0.42, 0.45, 0.48, 0.50] pred_proba = lr_clf.predict_proba(X_test) def get_eval_by_thresholds(y_test, pred_proba_c1, thresholds): # thresholds list 객체 내의 값을 차례로 iteration하면서 Evaluation 수행 for custom_threshold in thresholds: binarizer = Binarizer(threshold = custom_threshold) custom_predict = binarizer.fit_transform(pred_proba_c1) print('\n임계값 : ', custom_threshold) # 성능 평가 지표 함수 get_clf_eval(y_test, custom_predict, pred_proba[:, 1]) get_eval_by_thresholds(y_test, pred_proba[:, 1].reshape(-1, 1), thresholds)
임계값 : 0.3 오차 행렬 [[67 33] [11 43]] 정확도: 0.7143, 정밀도: 0.5658, 재현율: 0.7963, F1: 0.6615, AUC: 0.8433 임계값 : 0.33 오차 행렬 [[72 28] [12 42]] 정확도: 0.7403, 정밀도: 0.6000, 재현율: 0.7778, F1: 0.6774, AUC: 0.8433 임계값 : 0.36 오차 행렬 [[76 24] [15 39]] 정확도: 0.7468, 정밀도: 0.6190, 재현율: 0.7222, F1: 0.6667, AUC: 0.8433 임계값 : 0.39 오차 행렬 [[78 22] [16 38]] 정확도: 0.7532, 정밀도: 0.6333, 재현율: 0.7037, F1: 0.6667, AUC: 0.8433 임계값 : 0.42 오차 행렬 [[84 16] [18 36]] 정확도: 0.7792, 정밀도: 0.6923, 재현율: 0.6667, F1: 0.6792, AUC: 0.8433 임계값 : 0.45 오차 행렬 [[85 15] [18 36]] 정확도: 0.7857, 정밀도: 0.7059, 재현율: 0.6667, F1: 0.6857, AUC: 0.8433 임계값 : 0.48 오차 행렬 [[88 12] [19 35]] 정확도: 0.7987, 정밀도: 0.7447, 재현율: 0.6481, F1: 0.6931, AUC: 0.8433 임계값 : 0.5 오차 행렬 [[90 10] [21 33]] 정확도: 0.7987, 정밀도: 0.7674, 재현율: 0.6111, F1: 0.6804, AUC: 0.8433
임곗값이 0.48일 때 전체적인 성능 평가 지표를 유지하면서 재현율을 약간 향상시키는 좋은 임곗값으로 보임
앞에서 학습된 로지스틱 회귀 모델을 이용해 임곗값을 0.48으로 낮춘 상태에서 다시 예측
In [15]:# 임곗값 0.48로 설정한 Binarizer 생성 binarizer = Binarizer(threshold=0.48) # 위에서 구한 lr_clf의 predict_proba() 예측 확률 array에서 1에 해당한느 컬럼 값을 Binarizer 변환 pred_th_048 = binarizer.fit_transform(pred_proba[:, 1].reshape(-1, 1)) get_clf_eval(y_test, pred_th_048, pred_proba[:, 1])
오차 행렬 [[88 12] [19 35]] 정확도: 0.7987, 정밀도: 0.7447, 재현율: 0.6481, F1: 0.6931, AUC: 0.8433
- 별도의 데이터 전처리 과정 없이 추출한 성능 평가 지표
정확도: 0.7727, 정밀도: 0.7111, 재현율: 0.5926, F1: 0.6465, AUC: 0.8083
- 데이터 전처리(0인 값들 평균 값으로 대체) 후 추출한 성능 평가 지표
정확도: 0.7987, 정밀도: 0.7674, 재현율: 0.6111, F1: 0.6804, AUC: 0.8433
- 앞에서 학습된 모델을 이용해 임곗값을 낮춘 후 추출한 성능 평가 지표
정확도: 0.7987, 정밀도: 0.7447, 재현율: 0.6481, F1: 0.6931, AUC: 0.8433
'머신러닝' 카테고리의 다른 글
보스턴 주택 가격 회귀 (0) 2024.01.26 사용자 행동 인식 데이터 세트 (0) 2024.01.17 타이타닉 생존자 예측 (0) 2024.01.15 머신러닝 프로젝트 (0) 2023.12.05 머신러닝 개념 (0) 2023.12.04