機器學習實戰:糖尿病預測及可視化分析
你一生中可能已經多次聽說過糖尿病。它如此普遍地流行。根據國際糖尿病聯合會的數據,2024年有超過340萬人死于糖尿病。這相當于全球各類死亡人數的9.3%。2024年,20-79歲成年人糖尿病患者最多的國家是中國、印度和美國。世界衛生組織(WHO)進一步指出,這種嚴重的疾病是2021年女性死亡的主要原因之一(具體而言,位列第八)。
那么,糖尿病到底是什么呢?糖尿病并非像常見的誤區所說的那樣,只是吃太多糖或碳水化合物而引起的疾病。
糖尿病是一種嚴重的慢性疾病,當人體無法產生足夠的胰島素或無法有效利用已產生的胰島素時,就會引發血糖升高。糖尿病的主要類型包括1型糖尿病、2型糖尿病和妊娠期糖尿病。1型糖尿病是一種自身免疫性疾病,會導致胰腺中胰島素分泌細胞的破壞,而2型糖尿病主要與胰島素抵抗有關。妊娠期糖尿病發生在懷孕期間,通常在分娩后消退。
在本文中,我們將深入探討一個專注于預測患者糖尿病的機器學習項目。該項目的主要目標是利用患者健康指標準確預測罹患糖尿病的可能性,從而促進早期發現并改善患者預后。
我們在執行此項目時采取的步驟如下:
- 理解數據
- 收集數據
- 數據清洗和驗證
- 探索性數據分析
- 特征預處理
- 模型訓練
- 模型評估
- 特征重要性
- 測試虛擬數據
理解數據
我必須戴上我的偵探眼鏡,深入醫療保健領域——不僅以學生和專業人士的身份,也以患者的身份。對我來說,這才是數據科學的真正意義所在:從微觀和宏觀兩個層面,全面理解你正在解決的問題。它還需要深厚的同理心——將人置于你構建的每一個解決方案的核心。
背景:一家流動醫療診所希望使用基本健康指標對糖尿病患者進行預篩查,以減少醫院擁擠并關注高危人群。
問題描述:使用 EDA 發現顯著影響糖尿病風險的因素。開發一個分類模型,根據 BMI、血糖水平、胰島素水平和年齡等屬性預測一個人是否患有糖尿病。
收集數據
一旦確定了最終目標,下一步就是收集與該目標相符的標記數據。[因版面限制,只展示部分代碼,本項目的數據集和完整代碼獲取:@公眾號:數據STUDIO 原文《機器學習實戰:糖尿病預測分析及可視化》 文末打賞任意金額便可獲取]。該數據集最初來自美國國家糖尿病、消化和腎臟疾病研究所。需要特別指出的是,這里的所有患者均為至少 21 歲的皮馬印第安血統女性。
工作流程的第一步是導入必要的庫。我們首先導入進行分析所需的模塊和庫。
# 用于數值運算、數據操作和數組處理
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
# 用于導入規范化和分類器特征
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import RandomizedSearchCV
from sklearn.neighbors import KNeighborsClassifier
from scipy.stats import randint, uniform
!pip install xgboost
import xgboost as xgb
# 用于評估模型指標
from sklearn.metrics import accuracy_score, classified_report, confusion_matrix, ConfusionMatrixDisplay, roc_curve, roc_auc_score
# 用于清除所有警告對話框
import warnings
warnings.filterwarnings( 'ignore' )
然后將數據加載到notebook中
df = pd.read_csv("diabetes.csv")
# 本項目的數據集和代碼獲取:@公眾號:數據STUDIO 原文《機器學習實戰:糖尿病預測及可視化分析》 文末打賞任意金額便可獲取
df
圖片
數據集中字段含義:
- Pregnancies:懷孕次數
- Glucose:口服葡萄糖耐量試驗(OGTT)中2小時血漿葡萄糖濃度
- BloodPressure:舒張壓(毫米汞柱)
- SkinThickness:三頭肌皮褶厚度(毫米)
- Insulin:2小時血清胰島素(μU/ml)。
- BMI:(體重(公斤)/(身高(米)2)
- DiabetesPedigreeFunction:根據家族史,對個人糖尿病遺傳易感性進行數值估計。范圍從 0.08 到 2.42。
- Age:數據集中患者的年齡
- Outcome:類別變量(0 或 1)指示患者是非糖尿病患者(0)還是糖尿病患者(1)
我們從數據集的形狀中獲得了 768 個觀測值(行)和 9 個屬性(列)
數據清洗和驗證
在開始任何建模或分析之前,必須仔細驗證和清理數據——這是數據科學中經常被低估的關鍵步驟。首先,我們用放大鏡檢查是否存在空值或缺失值、重復值或異常值。
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
乍一看,所有 768 行數據在所有屬性上都顯示為非空。然而,這并不一定意味著數據集是干凈的。它僅僅意味著沒有表示為NaN的缺失值。在類似的醫學數據集中,可以使用諸如 之類的占位符值0來表示缺失數據,尤其是在葡萄糖、胰島素或 BMI 等領域,因為 的值0在生理學上是不可信的。
我們已經在數據集中觀察到了一些異常零,為了確認這一點,我們做了以下操作:
df.describe().T
數據集中數值屬性的統計分析
我們可以看到,盡管患者0懷孕的可能性很大,但情況不應該如此,尤其是在血糖、胰島素或BMI、血壓和皮膚厚度等領域。
進一步探究,我們制作了一個匯總表,顯示了這些異常值的頻率。
# 制作一個 DataFrame 來讓我對數值有更多的了解
summary_table = pd.DataFrame({
'Dtype': df.dtypes, # 字典在內部
'Count': df.count(),
'Unique': df.nunique(),
'Null Values': df.isnull().sum (),
'Zero Values': [
(df[col] == 0 ).sum() for col in df.columns],
'Frequent Value (and number of appearences)' : [
f"{df[col].mode()[0]} ({df[col].value_counts().max ()})"for col in df.columns
]
})
summary_table
匯總表
這些0值在 SkinThickness 和 Insulin 等屬性中出現很多次,并且可能極大地影響其數據分布。
檢查重復行
我們的數據集中沒有重復的行。
屬性的直方圖分布
上圖旨在檢查每個屬性分布的偏度,以確定用什么統計量來填充這些0值。對于對稱分布(例如,SkinThickness & Glucose),我們稍后會用平均值替換這些缺失值NaN,然后再用平均值填充這些空值;而對于偏斜分布(例如,Insulin, BloodPressure 和 BMI),我們使用中位數來最小化誤差。
左偏(負偏): 如果低端(左側)的尾部比右側長或更長,則該分布被視為左偏分布。在這種情況下,大多數數據值會向高端(右側)聚集,而少數低異常值會使均值向下傾斜。因此,平均值通常小于中位數。
右偏(正偏): 右偏分布的尾部(右側)較長或拉伸。這表明大多數數據點都集中在低端(左側),而一些高離群值會將平均值向上拉高。在這種情況下,平均值通常大于中位數。
正態分布:關于均值對稱的概率分布
還可以用以下圖表示:
- 懷孕高峰期約為 1-2 次。平均懷孕次數約為 3.85 次,標準差為 3.37 次。這一特征表現為正偏度,偏度為 0.90。
- 葡萄糖水平大多在 100 至 125 毫克/分升之間,平均值約為 120 毫克/分升,標準差為 32。分布接近對稱,偏度為 0.17。
- 舒張壓的中心傾向接近 60-80mmHg,平均值為 69mmHg,分布(標準差)約為 。它顯示出負偏度,其偏度值為 -1.84
- 皮膚厚度平均值約為 20.54 毫米,標準偏差為 。數據接近對稱,偏度值為 0.11
- 胰島素水平主要集中在 0 左右,但平均值上升到 79.80 左右,表明數據呈正偏度分布。偏度值為 2.27,標準偏差為 115.24,相對較高。
- 體重指數值集中在 30-40 附近,平均體重指數為 32,標準差為 7.88。根據-0.43的偏度值,分布呈現輕微的負偏斜
- 糖尿病譜系系數 在 0.3-0.4 附近最為常見,平均值為 0.47,標準差為 0.33。
- 年齡在 20-30 歲左右達到峰值,平均年齡約為 33.27 歲,標準偏差為 11.76。數據分布呈右偏態,偏度值為 1.13。
columns_with_zeros = ['Glucose', 'BMI', 'Insulin', 'SkinThickness', 'BloodPressure'] #將不可能的零值替換為它們的空值
for column in columns_with_zeros:
df[column] = df[column].replace(0, np.nan)
binomial_columns_with_zeros = ['Glucose', 'SkinThickness'] #用空值替換不可能為零的值 - 平均值(近似對稱)
for column in binomial_columns_with_zeros:
df[column] = df[column].fillna(df[column].mean())
skewed_columns_with_zeros = ['BloodPressure', 'Insulin', 'BMI'] #用空值替換不可能為零的值 - 中位數(偏態)
for column in skewed_columns_with_zeros:
df[column] = df[column].fillna(df[column].median())
df
探索性數據分析
探索性數據分析 (EDA) 就像拼圖游戲一樣——數據科學家需要運用他們的分析直覺。在這個階段,我們開始使用可視化、匯總和統計工具來解答數據背后的what, how, where以及why。在這個階段,我分析了數據集的結構、分布和關系,以發現有意義的模式并識別糖尿病的潛在預測因子。
圖片
- 懷孕: 懷孕次數的平均值約為 4 次,中位數為 3 次,模式為 1 次,數值范圍為 0 至 17 次,標準差為 3.37 次。
- 葡萄糖: 葡萄糖水平平均約為 120.89 mg/dl,中位數為 117.00 mg/dl,模式為 99.00 mg/dl。數值范圍從 0 到 199 mg/dl,標準差為 31.97 mg/dl。
- 血壓:平均血壓約為 69.105 毫米汞柱,中位數為 72.00 毫米汞柱,模式為 70.00 毫米汞柱。測量范圍為 0 至 122 mmHg,標準差為 19.356 mmHg。
- 皮膚厚度: 皮膚厚度的平均值為 20.536 毫米,中位數為 23.00 毫米,模式為 0.00 毫米。范圍為 0 至 99 毫米,標準差為 15.95 毫米。
- 胰島素:胰島素水平平均約為 79.80 單位,中位數為 30.50 單位,模式為 0.00 單位。范圍為 0 至 846 單位,標準差為 115.24 單位。
- 體重指數: 平均體重指數(BMI)為 31.993,中位數和模式均為 32.00。數值范圍從 0 到 67.10,標準差為 7.884。
- 糖尿病譜系函數: 糖尿病血統函數的平均值為 0.472,中位數為 0.372,模式為 0.254。數值介于 0.08 和 2.42 之間,標準差為 0.331。
- 年齡:平均年齡為 33.24 歲,中位數為 29.00 歲,模式為 22.00 歲。年齡范圍在 21 至 81 歲之間,標準差為 11.76 歲。
圖片
- 懷孕次數中位數約為 3-4,有幾個較高的離群值(如 15+ 懷孕)。0 這樣的值不是離群值,這是合乎邏輯的。
- 葡萄糖中位數約為 110-120。由于數值較高(高達約 200),出現了明顯的右斜。包括 0 在內的極低值為異常值--在醫學上難以置信,可能是數據缺失或錯誤。
- 血壓中位數約為 70-75 mmHg。有幾個值為 0,明顯是異常值,不符合生理學原理。血壓分布比較對稱,但這些 0 值需要清理。
- 皮膚厚度中值約為 23-25 mm。0 的數量非常多--強烈表明數據缺失或有誤。也有一些上限離群值(約 100),但主要問題是 0。
- 胰島素極度右偏。數值差異巨大,有許多離群值(高達 800+)。0 很常見,可能表示數據缺失--在現實世界的數據集中,胰島素經常缺失。
- 體重指數中位數約為 32。0 值也是異常值--不太可能對體重指數有效。其他方面呈右偏分布,但分布相當緊湊。
- 糖尿病譜系系數中位數約為 0.4。右偏,有許多輕度和極端異常值(>1.5)。沒有明顯的無效值,但該屬性的差異很大。
- 年齡中位數約為 29-30。右偏,尾部延伸至 80 歲。沒有明顯的無效異常值;分布看起來很自然。
- 懷孕糖尿病患者的懷孕次數往往較多。結果 = 1 的懷孕次數中位數更高。糖尿病患者的分布范圍更廣。
- 血糖明顯區別:糖尿病患者的血糖水平明顯更高。在該數據集中,葡萄糖是一個強有力的指標--IQR 的重疊極少。兩組中都有異常值,但非糖尿病組的異常值更大。
- 血壓中位數相當接近,但糖尿病患者的數值略高。非糖尿病組的異常值較低。總體而言,血壓可能不是一個強有力的區分指標。
- 皮膚厚度糖尿病患者和非糖尿病患者之間的差異很微妙。兩組的中位數和均方根值相似。糖尿病患者的幾個異常值延伸得更遠,但這一特征似乎不那么具有決定性。
- 胰島素兩組的差異都很大,都有很多異常值。糖尿病患者的中位數似乎略高,但重疊程度較大。
- 體重指數糖尿病患者的體重指數普遍較高。兩組之間的中位數有明顯變化。分布略偏右,有一些異常值,但這是一個有用的特征。
- 糖尿病譜系系數糖尿病組的平均值較高。糖尿病患者中有許多高值異常值,表明遺傳風險更大。雖然分布有所重疊,但這一特征有助于預測。
- 年齡糖尿病患者往往年齡較大。中位數和 IQRs 有明顯差異,糖尿病組的高端值更多。這一特征似乎非常相關。
皮馬印第安人數據集中的結果分布
從上圖可以看出,我們有 500 名非糖尿病患者和 268 名糖尿病患者。這使得分布不平衡(即數據集中的目標變量或類別沒有被平等地表示出來),如果處理不當,可能會導致潛在的偏差或泛化能力差。
每個屬性的平均分布(按結果著色)
上方的條形圖顯示了每個數值特征的平均值,并以Outcome變量(指示一個人是否患有糖尿病)分隔開。這有助于理解兩組之間每個健康指標的平均值有何差異。我們注意到,糖尿病
患者和非糖尿病患者的胰島素、血糖、年齡和BMI水平之間存在明顯的一致性。這表明上述指標是患者患糖尿病的明確指標。妊娠期的一致性表明,有過多次妊娠的女性患妊娠期糖尿病的風險更高。
圖片
從非對角線(散點圖)中發現:
- 葡萄糖似乎是一個非常重要的特征。在大多數涉及葡萄糖的散點圖中,兩個結果類別有明顯的分離,橙色/紅色(可能是 “糖尿病”)點一般出現在葡萄糖值較高的地方
- 葡萄糖與胰島素:存在正相關。隨著葡萄糖的增加,胰島素也會增加。糖尿病 "組通常顯示較高的葡萄糖和胰島素值。
- 葡萄糖與體重指數: 呈正相關。較高的葡萄糖通常與較高的體重指數相關。糖尿病 "組的血糖和體重指數通常較高。
- 葡萄糖與年齡的關系:總體趨勢是,葡萄糖水平越高,年齡就越大,尤其是 “糖尿病” 結果。
- 體重指數似乎也是一個很好的區分特征。
- 體重指數與血壓:存在正相關關系,體重指數越高,血壓越高。
- 體重指數與年齡: 可能存在微弱的正相關。
- 懷孕次數與年齡:存在正相關,正如預期的那樣,年齡越大的人懷孕次數越多。
- 胰島素及其分布:涉及胰島素的散點圖突顯了其奇特的分布。許多點聚集在零點附近,這是用零值計算的缺失數據。對于非零胰島素值,與葡萄糖呈正相關,這表明隨著葡萄糖水平的升高,人體會分泌更多的胰島素來控制葡萄糖水平,但對于糖尿病患者來說,這種反應可能不足或無效。
- 皮膚厚度與體重指數: 皮膚厚度與體重指數之間有很強的正相關性,這在生理學上是意料之中的,因為兩者都與身體脂肪有關。
- 血壓及其影響:雖然血壓顯示了一些趨勢,但它本身似乎并不像葡萄糖或體重指數那樣具有強烈的區分作用。
相關性熱圖
上圖給出了屬性之間的相關性矩陣,范圍從 -1(強負相關性)、0(無相關性)和+1(強正相關性)。
與結果的相關性:
- Glucose和Outcome之間存在中等強度的正相關性。這是完全可以預料到的,因為高血糖水平是糖尿病的主要指標。這表明,隨著血糖水平的升高,出現積極結果(糖尿病)的可能性也會增大。
- BMI和Outcome之間存在中等強度的正相關性。
- BMI較高通常意味著患糖尿病的風險較高。
- Age和Outcome之間存在微弱的正相關性。患糖尿病的風險通常會隨著年齡的增長而增加。
- Pregnancies和Outcome之間存在微弱的正相關性,這可能與妊娠期糖尿病或多胎妊娠可能與以后患 2 型糖尿病的風險較高有關。
# 根據可靠的健康分類分層,對年齡、BMI、血壓進行分類屬性
# 本項目的數據集和代碼獲取:@公眾號:數據STUDIO 原文《機器學習實戰:糖尿病預測分析及可視化》 文末打賞任意金額便可獲取
df['AgeGroup'] = pd.cut(df['Age'], bins=[20, 30, 40, 50, 60, 100], labels= ['Young Adult', 'Early Middle Age', 'Late Middle Age', 'Early Senior', 'Senior'])
df['BMI_Group'] = pd.cut(df['BMI'], bins=[0, 18.5, 25, 30, 100], labels=['Underweight', 'Normal', 'Overweight', 'Obese'])
df['BloodPressure_Group'] = pd.cut(df['BloodPressure'], bins=[0, 60, 80, 90, 120, 200], labels=['Low', 'Normal', 'Pre-hypertension', 'Stage 1 Hypertension', 'Stage 2 Hypertension'])
df['Outcome_Group'] = df['Outcome'].map({0: 'No Diabetes', 1: 'Diabetes'})
df['Glucose_Category'] = pd.cut(df['Glucose'], bins=[0, 140, 200], labels=['Normal', 'Impaired glucose tolerance'])
df[['Glucose', 'Glucose_Category']]
# 根據提供的范圍定義胰島素類別
df['Insulin_Category'] = pd.cut(df['Insulin'],
bins=[0, 30, 100, 150, 1000], # Increased upper bound to include high values
labels=['< 30 (Possible Deficiency)', '30-100 (Normal)', '100-150 (Early Resistance)', '> 150 (Significant Resistance)'],
right=False) # 使用 right=False 使箱體不包括右邊緣,與描述相匹配
# 定義 DiabetesPedigreeFunction 的箱
bins = [0, 0.5, 1.0, 1.5, df['DiabetesPedigreeFunction'].max()]
labels = ['0-0.5', '0.5-1.0', '1.0-1.5', '>1.5']
# 為 DPF 箱體創建新列
df['Pedigree_Bin'] = pd.cut(df['DiabetesPedigreeFunction'], bins=bins, labels=labels, right=False)
為了增強可解釋性并揭示數據中更深層次的模式,我根據既定的醫療保健分層,將幾個連續的健康指標轉化為具有臨床意義的類別。這一步驟可以實現更直觀的分析和可視化,尤其是在比較不同人群的糖尿病結果時。
- Age按照生命階段分為以下幾個組:青年(21-30 歲)、中年早期(31-40 歲)、中年晚期(41-50 歲)、老年早期(51-60 歲)和老年(61 歲以上)。
- BMI使用 WHO 標準分類對值進行分組:體重過輕(<18.5)、正常(18.5–24.9)、超重(25–29.9)和肥胖(30+)。
- 根據Blood Pressure舒張壓截斷值,將血壓分為臨床范圍:低血壓、正常血壓、高血壓前期和1-2 期高血壓。
- 二元變量Outcome被映射到更易讀的標簽——糖尿病和非糖尿病,以便圖表和表格更加清晰。
- Glucose分為兩個主要診斷范圍:
正常(<140mg/dL)
糖耐量受損(140-199mg/dL)。這些閾值反映了 2 小時口服葡萄糖耐量測試 (OGTT) 中使用的標準指南,有助于識別可能處于糖尿病前期的個體。
- 根據Insulin胰島素敏感性和抵抗性的臨床解釋,將水平分為四類:
- < 30 μU/mL — 可能存在胰島素缺乏,
- 30–100 μU/mL — 正常2小時血清胰島素反應
- 100–150 μU/mL — 胰島素抵抗的早期跡象
- 150 μU/mL — 嚴重的胰島素抵抗。定義的分箱既反映了診斷范圍,也反映了數據集中觀察到的分布。使用right=False確保每個類別的上限不重疊
- 雖然這DiabetesPedigreeFunction并非傳統意義上的臨床指標,但它有助于根據家族史了解患者的糖尿病遺傳易感性。為了更直觀地分析這一特征,我將連續的DPF值劃分為四個分類箱。這些分類箱有助于突出顯示遺傳風險的增加與數據集中糖尿病患病率之間的關聯。
現在,進入分類圖表:
按結果對葡萄糖進行分類
這張堆疊條形圖顯示了兩組血糖類別中糖尿病陽性和陰性結果的百分比。它告訴我們,血糖受損的糖尿病患者比例要高得多。因此,這再次印證了我們的假設:血糖是最終糖尿病結果的關鍵決定因素。所有年齡段的人都應定期進行血糖檢測,即使患者尚未患糖尿病。
年齡分布——餅圖
從兩個餅圖來看,老年人在我們的數據集中沒有得到充分體現,因此我們可以對這個年齡段的分析持保留態度。
結果分布——年齡組
這張水平條形圖展示了各年齡段糖尿病狀況的分布情況。由此可見,患糖尿病的風險較高的人群是中年早期(31-40歲)和老年早期(51-60歲)。因此,強烈建議在40歲這個門檻上進行早期檢測和監測。
結果分布 — bmi 集團
由此可見,BMI 肥胖人群最容易患糖尿病。因此,對患者采取積極的體重管理策略是最可行的解決方案。
結果分布——分級妊娠
妊娠次數與糖尿病患病率之間似乎存在某種關聯。隨著妊娠次數的增加,確診患有糖尿病的女性比例也顯著增加。這進一步表明,多次妊娠的女性患妊娠期糖尿病的風險可能更大。
結果分布——dpf
從上面的多條形圖中可以看出,在譜系功能較高的人群中,糖尿病患者的相對比例往往較高。這也表明遺傳易感性可能是一個影響因素。
僅有一次觀察結果顯示患有 2 期高血壓(數據不足)
從我理解數據的背景來看,糖尿病患者患高血壓的可能性是普通人的兩倍。這更像是糖尿病的后遺癥,而非病因。糖尿病會損傷腎臟,導致水鹽潴留,進而導致血壓升高。因此,糖尿病對高血壓前期和一期高血壓患者的影響更為顯著。
結果分布——胰島素
胰島素抵抗通常是血糖水平受損的先兆。這意味著抵抗力顯著的個體最容易患糖尿病,超過一半的嚴重抵抗女性患有糖尿病。這個問題可以通過定期檢測患者的胰島素水平來解決。100mU/mL 應作為進一步監測和采取早期預防措施的標志。
結果分布——胰島素和葡萄糖
由于血糖和胰島素是手動設置的屬性,我們使用了屏蔽函數、運算符和位置尋址來整合先前創建的Glucose_Category和Insulin_Category列中的信息。這可以根據患者的血糖和胰島素水平識別特定的亞組,并僅關注屬于這些定義的高血糖和胰島素抵抗組的患者。
這里的數字高得驚人,如果病人的血糖水平處于這些高值范圍之間,那么他們患糖尿病的可能性就很大。
特征預處理
完成整個 EDA 后,需要準備用于建模的數據。首先,我們準備用于訓練機器學習模型的特征(自變量)。在監督學習中,我們將數據集分為特征(用于進行預測的數據)和目標變量(我們想要預測的內容)。
X = df.drop(columns=df.select_dtypes(exclude=np.number))
X.drop(columns=['Outcome'], inplace=True)
X
y = df['Outcome']
y
特征相關性
獨立特征之間的相關性:Glucose和Insulin之間存在相對較強的正相關性。這在生物學上是合理的,因為胰島素會根據血糖水平釋放以調節血糖水平。SkinThickness和BMI是獨立特征之間最強的相關性之一。這很合理,因為皮褶厚度測量值通常用于計算或與BMI相關。Pregnancies和Age也暗示了強烈的正相關性。這是合乎邏輯的,因為女性隨著年齡的增長通常會懷孕更多次。
弱/負相關性:注意一些非常弱或接近于零的相關性,例如BloodPressure和Insulin(0.045)或DiabetesPedigreeFunction和BloodPressure(-0.0024)。這些低值表明這些特征在很大程度上以線性方式相互獨立(例如Pregnancies和DiabetesPedigreeFunction(0.034))。
特征縮放
scaler = StandardScaler()
scaledX = scaler.fit_transform(X)
標準化是機器學習中常見的預處理步驟,旨在確保值較大的特征不會對模型造成不成比例的影響。這可以通過將數據變換為平均值為 0、標準差為 1 來實現。
特征指標
理解混淆矩陣
再次,你還記得我們說過這個數據集不平衡嗎?你的模型可能傾向于多數類,即使你獲得了很高的準確率,也可能在關鍵的地方表現不佳。95% 的準確率可能掩蓋了模型從未正確預測少數類(重要的類)的事實——而當它真正重要時,這可能會帶來災難性的后果。因此,我們不應該關注準確率,而是應該評估這些模型的指標:精度、準確率、F-1 分數和 AUC(曲線下面積)。
精確度是:“模型每次說‘糖尿病’時,有多少人實際上是糖尿病患者?”——如果你的模型說有 10 個人是糖尿病患者,但實際上只有 6 個人是糖尿病患者,那么你的精確度就是 0.6(非糖尿病患者也是如此)。
召回率是: “在所有實際的糖尿病病例中,模型捕獲了多少個?”——這對于非糖尿病類別也存在)。
F1 分數是: “精確度和召回率之間的平衡點在哪里?”——這通常是不平衡數據集的最佳指標。它是精確度和召回率的調和平均值。為了獲得較高的 F1 分數,你的召回率和精確度也必須很高。
AUC 是: “模型能多好地區分類別(例如,是與否)?”——這個值介于 0 到 1 之間。這往往會對模型的整體性能進行排名。
訓練-測試拆分驗證
為了評估機器學習模型的性能,必須將數據集拆分為兩部分:用于訓練模型的訓練集,以及用于評估模型在未知數據上表現的測試集。我們將數據集的 20% 分配給測試集,80% 分配給訓練集。我們使用 Scikit-learn 的訓練-測試拆分功能來執行此操作。
X_train,X_test,y_train,y_test = train_test_split(scaledX,y,test_size= 0.2,random_state= 42)
模型訓練
因為這是一個分類模型,所以我們將使用決策樹、隨機森林等分類模型。
我們從邏輯回歸開始:
# 訓練模型以使其根據輸入進行預測
log = LogisticRegression()
log.fit(X_train, y_train)
# 使用訓練好的對數模型對測試數據集進行預測
log_preds = log.predict(X_test)
評估:邏輯回歸的準確度評分和分類報告
評估:邏輯回歸的準確度評分和分類報告。
隨機森林分類
現在我們嘗試隨機森林。
forest = RandomForestClassifier(random_state=56) #為了便于復現
forest.fit(X_train, y_train) #訓練模型
forest_preds = forest.predict(X_test) #在未見過的數據上測試模型
評估:隨機森林的準確率得分和分類報告
評估:隨機森林的準確率得分和分類報告。
決策樹分類器
接下來是決策樹:
#初始化決策樹分類器
tree = DecisionTreeClassifier(random_state= 56 )
# 將模型擬合到訓練數據
tree.fit(X_train, y_train)
# 對測試數據進行預測
tree_preds = tree.predict(X_test)
評估:決策樹的準確度得分和分類報告
評估:決策樹的準確度得分和分類報告。
KNN分類器
K最近鄰分類器下一步:
# 初始化 KNN 分類器
knn = KNeighborsClassifier(n_neighbors= 25 ) # 您可以選擇鄰居的數量(k)
# 將模型擬合到訓練數據
knn.fit(X_train, y_train)
# 對測試數據進行預測
knn_preds = knn.predict(X_test)
評估:K最近鄰分類器的準確率和分類報告
評估:K最近鄰分類器的準確率和分類報告。
XGBoost分類器
# 創建 XGBoost 分類器
xgb_model = xgb.XGBClassifier(random_state= 42 )
# 訓練模型
xgb_model.fit(X_train, y_train)
# 進行預測
xgb_preds = xgb_model.predict(X_test)
評估:xgbclassifier 的準確率和分類報告
不同模型ROC對比
我們對比了多個模型訓練效果如圖所示:
圖片
超參數調整
我們對隨機森林和決策樹模型執行了 GridSearch 和 RandomizedSearchCV,希望在評估期間獲得更好的 F1 分數。
以下是所有模型的最終表現:
模型性能
調整隨機森林(隨機搜索)在少數結果中表現最佳,而 KNN 在多數結果中表現最佳。我們選擇部署調整隨機森林來測試我們的虛擬數據,但這還有待觀察。
這是所有使用的模型的 2*4 混淆矩陣。
圖片
特征重要性
這有助于我們對每個模型在訓練和測試中使用的屬性進行排名,以確定或選擇類別。
特征重要性
由此可以肯定的是:血糖是預測患者糖尿病的首要決定因素。BMI、年齡和胰島素也是預測結果的關鍵屬性。
測試一下:
特征氣泡圖
你看到這張氣泡圖了嗎?它告訴我們,在血糖和BMI水平較高時,有一簇大的橙色氣泡(結果 = 1),這表明BMI和血糖值較高的老年人更有可能獲得積極的結果。你會注意到,許多大的紫色圓圈位于血糖軸的下方。這意味著他們可能年齡較大、體重較重——但他們的血糖水平不高,所以他們不是糖尿病患者。
- 大橙色(上圖) = 高 BMI + 高血糖 + 年齡較大 = 高風險
- 大紫色(下圖) = 可能年紀大或 BMI 高,但血糖正常或有其他保護因素
胰島素抵抗氣泡圖
最上象限的圓圈大多巨大且呈橙色。這告訴我們,胰島素抵抗和血糖升高(見上圖)的情況不容樂觀。這幾乎肯定會導致糖尿病,或者至少是定時炸彈。這里大多數負面結果的血糖和胰島素水平都正常。
現在,令人瞠目結舌的事情發生了!因為我喜歡風險分析,所以我們喜歡用幾率來衡量概率。我們建立了一個簡單的風險評分系統,如果BMI > 30,則+1;如果血糖 > 140,則+1;如果年齡 > 40,則+1;如果胰島素 > 150,則+1;如果懷孕次數 > 3,則+1。我們計算了每個分數對應的糖尿病發病率,并將其列在0-1范圍內的熱圖上(根據符合的屬性,分數越接近1,患者患糖尿病的風險就越大)。
# 將基于最重要特征的風險評分邏輯應用于 DataFrame
def calculate_risk_score ( row ):
score = 0 # 初始化為 0 并按條件遞增
if row['BMI'] > 30:
score += 1
if row['Glucose'] > 140:
score += 1
if row['Age'] > 40:
score += 1
if row['Insulin'] > 150:
score += 1
if row['Pregnancies'] > 3:
score += 1
return score
df['RiskScore'] = df.apply(calculate_risk_score, axis=1)
# 計算每個風險評分的糖尿病發病率
risk_diabetes_rate = df.groupby('RiskScore')['Outcome'].mean().reset_index()
# 創建熱圖
plt.figure(figsize=(10, 6))
sns.heatmap(risk_diabetes_rate.set_index('RiskScore').T, annot=True, cmap='Reds', fmt=".2f", linewidths=.5)
plt.title('Diabetes Rate vs. Risk Score Heatmap')
plt.xlabel('Risk Score')
plt.ylabel('Outcome (1=Diabetes)')
plt.yticks([]) # 隱藏 y 軸標簽,因為它只有'結果 (1=糖尿病)'
plt.show()
風險評分熱圖
即使只超過 3 個閾值,患者也或多或少有可能患有糖尿病。超過 4 個閾值是一個巨大的災難性危險信號,我們希望迅速找到干預措施,而超過 5 個閾值幾乎肯定是糖尿病。
測試虛擬數據
我們將一個虛擬數據集傳遞給模型,以測試該患者是陽性還是陰性。我們將數據以數組的形式傳遞,順序與數據字典中的順序相同,然后縮放數據,使用部署的模型執行預測,并評估兩種結果的確定性概率。請看下面的代碼:
# 示例患者數據作為 NumPy 數組
# 確保特征的順序與訓練數據匹配
# 訓練數據 (X) 中的特征為:
# ['Pregnancies', 'Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI', 'DiabetesPedigreeFunction', 'Age']
# 示例患者數據(用實際患者數據替換)
# 這只是一個包含 8 個值的占位符數組。
# 您需要在此處提供實際患者數據。
patient_data_array = np.array([[ 2 , 130 , 70 , 30 , 128 , 35.0 , 0.82 , 43 ]])
# 使用與訓練數據相同的縮放器縮放患者數據
scaled_patient_data = scaler.transform(patient_data_array)
# 使用隨機搜索模型執行預測
prediction = best_tree_model.predict(scaled_patient_data)
# 預測結果是一個 numpy 數組,訪問第一個元素
prediction_result = prediction[ 0 ]
# 解釋預測
if prediction_result == 1:
print("Prediction: The patient is likely to have diabetes.")
else:
print("Prediction: The patient is likely not to have diabetes.")
# 獲取每個類的概率
prediction_proba = best_rs_forest_model.predict_proba(scaled_patient_data)
print(f"Probability of no diabetes (Class 0): {prediction_proba[0][0]:.4f}")
print(f"Probability of diabetes (Class 1): {prediction_proba[0][1]:.4f}")
根據虛擬數據進行預測
預計這位患者最有可能患有糖尿病——我叫她 Anaya。
這是一個顯示 Anaya 的用戶資料及其病史的儀表板。
anaya 的個人資料
我們還做了一個交互式的,要求用戶輸入。我們收集了用戶(例如患者)的醫療輸入,對輸入進行預處理,使其與經過訓練的機器學習模型(例如本例中的 KNN)使用的格式相匹配,預測患者是否可能患有糖尿病,并顯示結果的概率和解釋。
我們利用 try-except 驗證塊來確保數據float僅在數據類型中被接受并附加到列表數組中patient_data。
# 本項目的數據集和代碼獲取:@公眾號:數據STUDIO 原文《機器學習實戰:糖尿病預測分析及可視化》 文末打賞任意金額便可獲取
# 允許用戶輸入進行預測
def get_patient_input ():
"""從用戶那里獲取患者屬性作為輸入并進行驗證。"""
feature_names = ['Pregnancies', 'Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI', 'DiabetesPedigreeFunction', 'Age']
patient_data = []
print("Please enter the patient's details:")
for feature in feature_names:
whileTrue:
try:
value = float(input(f"Enter {feature}: "))
# 添加基本驗證(例如,非負值)
if feature in ['Pregnancies', 'Age'] and value < 0:
print("Value cannot be negative. Please enter again.")
continue
if feature in ['Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI'] and value < 0:
print("Value cannot be negative. Please enter again.")
continue
if feature == 'DiabetesPedigreeFunction'and value < 0:
print("Value cannot be negative. Please enter again.")
continue
patient_data.append(value)
break# 如果輸入有效,則退出循環
except ValueError:
print("Invalid input. Please enter a numerical value.")
except Exception as e:
print(f"An error occurred: {e}"))
return np.array([patient_data])
# 從用戶那里獲取輸入
user_patient_data = get_patient_input()
# 使用在訓練數據上適合的相同縮放器縮放用戶輸入數據
scaled_user_data = scaler。transform(user_patient_data)
user_prediction = knn.predict(scaled_user_data)
# 解釋預測
user_prediction_result = user_prediction[ 0 ]
if user_prediction_result == 1:
print("\nPrediction: Based on the input data, the patient is likely to have diabetes.")
else:
print("\nPrediction: Based on the input data, the patient is likely not to have diabetes.")
print("\nPrediction: Based on the input data, the patient is likely not to have diabetes.")
# 獲取每個類的概率
user_prediction_proba = knn.predict_proba(scaled_user_data)
print(f"Probability of no diabetes (Class 0): {user_prediction_proba[0][0]:.4f}")
print(f"Probability of diabetes (Class 1): {user_prediction_proba[0][1]:.4f}")
用戶輸入——預測
在這里,你可以看到,即使它預測患者可能患有糖尿病,也并不像第一個例子那樣確定。所以,誰知道呢?這可能是假陽性(也可能不是)。
當我們繼續探索健康與機器學習的交集時,記住:每個數據點不僅僅是一個統計數據;它代表著一個人類的故事。