Python機器學習 預測慢性腎臟疾病 實戰教學

慢性腎臟疾病簡介

慢性腎臟疾病(CKD,Chronic kidney disease),腎臟由於長期的發炎(感染、免疫複合體傷害、炎症反應等)、糖尿病、高血壓或尿路阻塞等造成腎臟實質破壞,產生不可逆的變化,導致腎臟正常功能逐漸消失.稱為慢性腎衰竭。
多數患者在腎功能已下降到正常人三分之一以下,血清尿素氮開始上升時,仍無任何症狀;等到出現明顯尿毒症狀時,常常已是無法恢復的末期腎衰竭。[1]

https://pixabay.com/illustrations/kidney-anatomy-biology-body-cancer-3667909/

資料來源

Chronic KIdney Disease dataset
加州大學爾灣分校(University of California, Irvine, UCI)機器學習存儲庫
https://archive.ics.uci.edu/ml/datasets/Chronic_Kidney_Disease

資料相關訊息:

  • 該數據是在2015年07月在UCI公開
  • 可用於預測慢性腎臟疾病,在近兩個月的時間裡從醫院收集
  • 具有25個特徵(例如:紅細胞計數、白細胞計數等)
  • 目標是進行「分類」,即「ckd」或「notckd」 (ckd =慢性腎臟病)

資料欄位介紹

No 欄位 詳細說明 中譯 型態 資料值
1 age age 年齡 (numerical) years
2 bp blood pressure 血壓 (numerical) bp in mm/Hg
3 sg specific gravity 比重 (nominal) (1.005,1.010, 1.015,1.020, 1.025)
4 al albumin 白蛋白 (numerical) (0,1,2,3,4,5)
5 su sugar (nominal) (0,1,2,3,4,5)
6 rbc red blood cells 红血细胞 (nominal) (normal, abnormal)
7 pc pus cell 膿细胞 (nominal) (normal, abnormal)
8 pcc pus cell clumps (nominal) (present, notpresent)
9 ba bacteria (nominal) (present, notpresent)
10 bgr blood glucose random 血糖 (numerical) bgr in mgs/dl
11 bu blood urea 尿素 (numerical) bu in mgs/dl
12 sc serum creatinine 血肌酐 (numerical) sc in mgs/dl
13 sod sodium (numerical) sod in mEq/L
14 pot potassium (numerical) pot in mEq/L
15 hemo hemoglobin 血红蛋白 (numerical) hemo in gms
16 pvc packed cell volume 紅細胞壓積 (numerical)  
17 wc white blood cell count 白細胞計數 (numerical) wc in cells/cumm
18 rc red blood cell count 紅細胞計數 (numerical) rc in millions/cmm
19 htn hypertension 高血壓 (nominal) (yes,no)
20 dm diabetes mellitus 糖尿病 (nominal) (yes,no)
21 cad coronary artery disease 冠狀動脈疾病 (nominal) (yes,no)
22 appet appetite 食慾 (nominal) (good,poor)
23 pe pedal edema 足部水腫 (nominal) (yes,no)
24 ane anemia 貧血 (nominal) (yes,no)
25 class class 目標分類 (nominal) (ckd,notckd)

以上中譯是簡略翻譯…

使用工具

python 3.7

使用套件

  • pandas
  • sklearn
  • seaborn
  • matplotlib

還沒安裝的可裝一下

pip install pandas
pip install sklearn
pip install seaborn
pip install  matplotlib

引用套件

import numpy as np 
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

from sklearn import preprocessing, metrics
from sklearn.model_selection import train_test_split, cross_val_score, cross_val_predict

from sklearn import tree

資料預處理

在Kaggle已經有人幫我們轉成CSV檔了
我們可以直接使用
https://www.kaggle.com/mansoordaku/ckdisease

#資料載入
df= pd.read_csv('kidney_disease.csv')
#初步查看資料
df.info()
print(df)
print(df.describe())

總筆數400,扣掉id,共有25個欄位(24+1個目標變量)

對25個欄位進行資料清洗

  • rbc、pc欄位:abnormal取代為1,normal取代為0
  • ppc、ba欄位:present取代為1,notpresent取代為0
  • htn、dm、cad、ane欄位:yes取代為1,no取代為0
  • appet欄位:good取代為1,poor取代為0
  • classification欄位:ckd取代為1,notckd取代為0
  • 並且針對所有欄位刪除空值
#數據預處理

df = df.drop('id', axis=1)

#age
df = df[np.isfinite(df['age'])]
df['age'] = df['age'].astype(int)

#bp
df = df[np.isfinite(df['bp'])]
df['bp'] = df['bp'].astype(int)

#sg
df = df[np.isfinite(df['sg'])]

#al
df = df[np.isfinite(df['al'])]
df['al'] = df['al'].astype(int)

#su
df = df[np.isfinite(df['su'])]
df['su'] = df['su'].astype(int)


#rbc
#df = df.drop('rbc', axis=1)
df[['rbc']] = df[['rbc']].replace(to_replace={'abnormal':1,'normal':0})
df = df[np.isfinite(df['rbc'])]
df['rbc'] = df['rbc'].astype(int)

#pc
df[['pc']] = df[['pc']].replace(to_replace={'abnormal':1,'normal':0})
df = df[np.isfinite(df['pc'])]
df['pc'] = df['pc'].astype(int)

#pcc
df[['pcc']] = df[['pcc']].replace(to_replace={'present':1,'notpresent':0})
df = df[np.isfinite(df['pcc'])]
df['pcc'] = df['pcc'].astype(int)

#ba
df[['ba']] = df[['ba']].replace(to_replace={'present':1,'notpresent':0})
df = df[np.isfinite(df['ba'])]
df['ba'] = df['ba'].astype(int)

#bgr
df = df[np.isfinite(df['bgr'])]
df['bgr'] = df['bgr'].astype(int)

#bu
df = df[np.isfinite(df['bu'])]
df['bu'] = df['bu'].astype(int)

#sc
df = df[np.isfinite(df['sc'])]

#sod
#df = df.drop('sod', axis=1)
df = df[np.isfinite(df['sod'])]
df['sod'] = df['sod'].astype(int)

#pot
#df = df.drop('pot', axis=1)
df = df[np.isfinite(df['pot'])]

#hemo
df = df[np.isfinite(df['hemo'])]

#pcv
df[['pcv']] = df[['pcv']].replace(to_replace={'?':None})
df['pcv'] = df['pcv'].astype(float) #先轉成float(以免清除空值程式報錯)
df = df[np.isfinite(df['pcv'])]
df['pcv'] = df['pcv'].astype(int)

#wc
#df = df.drop('wc', axis=1)
df[['wc']] = df[['wc']].replace(to_replace={'?':None})
df['wc'] = df['wc'].astype(float) #先轉成float(以免清除空值程式報錯)
df = df[np.isfinite(df['wc'])]
df['wc'] = df['wc'].astype(int)

#rc
#df = df.drop('rc', axis=1)
df['rc'] = df['rc'].astype(float) #先轉成float(以免清除空值程式報錯)
df = df[np.isfinite(df['rc'])]

#htn
df[['htn']] = df[['htn']].replace(to_replace={'yes':1,'no':0})
df = df[np.isfinite(df['htn'])]
df['htn'] = df['htn'].astype(int)

#dm
df[['dm']] = df[['dm']].replace(to_replace={'yes':1,'no':0})
df = df[np.isfinite(df['dm'])]
df['dm'] = df['dm'].astype(int)

#cad
df[['cad']] = df[['cad']].replace(to_replace={'yes':1,'no':0, '\tno': None})
df['cad'] = df['cad'].astype(int)
df = df[np.isfinite(df['cad'])]

#appet
df[['appet']] = df[['appet']].replace(to_replace={'good':1,'poor':0})
df = df[np.isfinite(df['appet'])]
df['appet'] = df['appet'].astype(int)

#pe
df[['pe']] = df[['pe']].replace(to_replace={'yes':1,'no':0})
df = df[np.isfinite(df['pe'])]
df['pe'] = df['pe'].astype(int)

#ane
df[['ane']] = df[['ane']].replace(to_replace={'yes':1,'no':0})
df = df[np.isfinite(df['ane'])]
df['ane'] = df['ane'].astype(int)

#classification
df[['classification']] = df[['classification']].replace(to_replace={'ckd':1,'notckd':0})
df = df[np.isfinite(df['classification'])]
df['classification'] = df['classification'].astype(int)

最後剩下可用筆數為158筆

資料分析

目標變量(classification)

#查看目標變量數量
print(df['classification'].value_counts())

患有慢性腎臟疾病為43筆,沒有慢性腎臟疾病為115筆,共為158筆。

熱力圖

#熱力圖
#變量之間的相關性
corr_df = df.corr()

#右上進行遮罩
mask = np.zeros_like(corr_df, dtype=np.bool)
mask[np.triu_indices_from(mask)] = True

sns.heatmap(corr_df, mask=mask, vmax=.3, square=True)
plt.show()

年齡(age)

#age
#年齡分布圖
f,ax=plt.subplots(figsize=(7,6))
df['age'].plot(kind='hist', color='darkturquoise')
plt.title('age')
plt.show()
#result:大部分落在35~65乘客

#年齡與慢性腎臟疾病折線圖
plt.figure(figsize=(15,8))
sns.kdeplot(df["age"][df.classification == 1], color="darkturquoise", shade=True)
sns.kdeplot(df["age"][df.classification == 0], color="lightcoral", shade=True)
plt.legend(['yes', 'no'])
plt.title('age')
plt.show()
#result:患有慢性腎臟疾病年齡偏大

從上圖得知,患有慢性腎臟疾病年齡都偏大(藍線 = 患有CKD,紅線 = 沒有CKD)。

#年齡箱型圖
sns.boxplot( y='age', x='classification', data=df, palette='Set2')
plt.title('age')
plt.show()
#result:患有慢性腎臟疾病年齡偏大

從箱型圖也可以看到,患有慢性腎臟疾病年齡偏大(0=沒有CKD、1=有CKD)。

血糖與糖尿病(bgr、dm)

#dm 糖尿病
sns.countplot(data=df,x='dm', hue="classification")
plt.title('dm & classification')
plt.show()

患有糖尿病的人可能也有慢性腎臟病,從此圖得知,患有慢性腎臟病的人,同時也有糖尿病比率更高(dm=0沒有糖尿病、dm=1患有糖尿病)。

#bgr 血糖
plt.figure(figsize=(15,8))
sns.kdeplot(df["bgr"][df.classification == 1], color="darkturquoise", shade=True)
sns.kdeplot(df["bgr"][df.classification == 0], color="lightcoral", shade=True)
plt.legend(['yes', 'no'])
plt.title('bgr')
plt.show()

從血糖數值切入,此圖很明顯看到正常人血糖呈現正常(紅線),而患有慢性腎臟病的人(藍線),血糖值呈現比較沒有那麼穩定。

高血壓(htn)

#htn
sns.countplot(data=df,x='htn', hue="classification")
plt.title('htn & classification')
plt.show()

患有高血壓的人可能也有慢性腎臟病,從此圖得知,大部分患有慢性腎臟病的人,有高血壓比率更高。

貧血(ane)

#ane
sns.countplot(data=df,x='ane', hue="classification")
plt.title('ane & classification')
plt.show()

sns.barplot(x = 'ane', y ='classification', data=df, color="darkturquoise")
plt.title('ane & classification')
plt.show()

從此圖得知,患有慢性腎臟病的人可能同時也有足部水腫(ane:0=沒有貧血、1=有貧血)。

尿素(bu)

plt.figure(figsize=(15,8))
sns.kdeplot(df["bu"][df.classification == 1], color="darkturquoise", shade=True)
sns.kdeplot(df["bu"][df.classification == 0], color="lightcoral", shade=True)
plt.legend(['yes', 'no'])
plt.title('bu')
plt.show()

最常檢驗的腎功能項目是血液尿素氮(BUN),從此圖得知,患有慢性腎臟病的人尿素值普遍呈現異常。

模型預測

資料劃分(訓練集與測試集)

#資料拆分

X = df.loc[:,df.columns!='classification'].values
Y = df['classification'].values

X_train, X_test, Y_train, Y_test = train_test_split( X, Y, stratify=Y, test_size=0.3) #這裡訓練集70%:測試集30%

print('訓練集大小: ', X_train.shape, Y_train.shape)  # 訓練集樣本大小
print(pd.value_counts(Y_train,sort = True).sort_index())


print('測試集大小: ', X_test.shape, Y_test.shape)  # 測試集樣本大小
print(pd.value_counts(Y_test,sort = True).sort_index())

將資料劃分為訓練集與測試集,比例為70:30。

決策樹(Decision tree)

#決策樹
clf = tree.DecisionTreeClassifier()
clf = clf.fit(X_train,Y_train)


train_acc = clf.score(X_train, Y_train)     
print('訓練集準確度: ', train_acc)

predicted = clf.predict(X_test)

accuracy = metrics.accuracy_score(Y_test, predicted)
auc  = metrics.roc_auc_score(Y_test, predicted)

print('準確度 (Accuracy): ', accuracy)
print('AUC: ', auc)

使用決策樹,在訓練集準確度達到100%,而測試集準確度部分也有97.91%、整體AUC面積96.15%。

結論

慢性腎臟病成因相當多,可能包括腎臟本身的問題,包括先天疾病等後天病症,如果患者控制不好也可能導致慢性腎臟病,讓身體出現更多併發症 。根據結果可知道可利用一些患者生理特徵來預測有沒有得病,並可能找出一些併發症,來幫助醫生做診斷確診。

參考文獻

[1] 延緩慢性腎臟病的惡化 //www.mmh.org.tw/taitam/ckd/education_doc/Doc_06.pdf

Default image
jimmy
Leave a Reply