一、數據集描述
本數據集內含十個屬性列
Pergnancies: 懷孕次數
Glucose:血糖濃度
BloodPressure:舒張壓(毫米汞柱)
SkinThickness:肱三頭肌皮膚褶皺厚度(毫米)
Insulin:兩個小時血清胰島素(μU/毫升)
BMI:身體質量指數,體重除以身高的平方
Diabets Pedigree Function: 疾病血統(tǒng)指數
是否和遺傳相關,Height:身高(厘米)
Age:年齡
Outcome:0表示不患病,1表示患病。
任務:建立機器學習模型以準確預測數據集中的患者是否患有糖尿病
二、準備工作
查閱資料得知各屬性的數據值要求,方面后期對于數據的分析與處理過程。
屬性列名稱 數據值要求
Pergnancies(懷孕次數) 符合常理即可(可為0)
Glucose(血糖濃度) 正常值為:80~120
BloodPressure(舒張壓(毫米汞柱)) 正常值為:60~80
SkinThickness(肱三頭肌皮膚褶皺厚度(毫米)) 不為0
Insulin(兩個小時血清胰島素(/毫升)) 正常值為:35~145
BMI(身體質量指數:體重除以身高的平方) 正常值為:18.5~24.9
Diabets Pedigree Function:(疾病血統(tǒng)指數:是否和遺傳相關) 無特殊值要求
Height(身高(厘米)) 不為0 符合常理即可
Age(年齡) 符合常理即可
Outcome(0表示不患病,1表示患病) 標簽值
三、實驗環(huán)境和工具
python3.5.6 + jupyter
數據處理 pandas、numpy
可視化 matplotlib、seaborn
模型構建 sklearn
四、預測分析
4.1探索性數據分析
數據描述
首先觀察基本的數據類型,以及數據是否存在缺失情況,簡要統(tǒng)計信息
all_data.shape all_data.info()
<class "pandas.core.frame.DataFrame"> RangeIndex: 768 entries, 0 to 767 Data columns (total 10 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 Height 766 non-null object 9 Outcome 768 non-null int64 dtypes: float64(2), int64(7), object(1) memory usage: 60.1+ KB
數據總量時比較少的只有768個例子,可以看到除Height外的屬性都為數值型屬性。在后續(xù)數據預處理過程需要對Height屬性進行類型轉換操作。目前沒有缺失值的出現。
# height 數值類型 為object 需要轉化為 數值型 all_data = all_data.astype({"Height":"float64"})
all_data.describe()
發(fā)現兩個問題:
1.缺失值
從其中的min值可以很直觀地觀察到,Glucose, BloodPressure, SkinTinckness, Insulin, BMI等特征存在0值的情況(當然Pregnancies根據常識判斷是可以為0的)。而根據常規(guī)范圍明顯可以判定這些0值是不合理的,所以也是一種缺失值缺失值,后續(xù)數據預處理需要對這些缺失值進行填充處理。
2.離群值/異常值
Glucose,BloodPressure,SkinTinckness,Insulin等特征的max值和75%分位點值或者min值和25%分位點值之間的差距比較大,初步判斷可能存在離群值/異常值。尤其是SkinThickness和Insulin特征(具體見圖4紅色框部分),后續(xù)可以通過可視化進一步直觀地觀察判斷。
為了方便后序對缺失值的統(tǒng)一處理,將異常值統(tǒng)一替換為np.nan。
import numpy as np #缺失值替換 經分析,除懷孕次數,其他特征的0值表示缺失值 替換為np.nan replace_list = ["Glucose", "BloodPressure", "SkinThickness", "Insulin", "BMI", "Height"] all_data.loc[:,replace_list] = all_data.loc[:,replace_list].replace({0:np.nan})
#各特征缺失數量統(tǒng)計 null_count = all_data.isnull().sum().values # 缺失值情況 plt.figure() sns.barplot(x = null_count, y = all_data.columns) for x, y in enumerate(null_count): plt.text(y, x, "%s" %y, horizontalalignment="center", verticalalignment="center") plt.show()
可以觀察到Glucose,Insulin,SkinThickness,BMI,Height等特征都存在缺失值。并且 Insulin,SkinThickness缺失值比較多,分別占到了48%,30%的比例。所以后期數據預處理也是很關鍵的。
五、可視化分析
接下來通過更多針對性的可視化,來進一步探索特征值的分布以及特征和預測變量之間的關系
# 患病和不患病情況下 箱線圖查看數據分散情況 for col in all_data.columns: plt.figure(figsize = (10,6)) if all_data[col].unique().shape[0] > 2: sns.boxplot(x="Outcome", y=col, data=all_data.dropna()) else: sns.countplot(col,hue = "Outcome",data = all_data.dropna()) plt.title(col) plt.show()
部分輸出:
觀察患病和不患病情況下 各特征值或者人數分布 label接近2:1 存在一定的分布不平衡 像insulin之類的特征離群值是比較多的,由于離群值會對模型評估產生影響,所以后續(xù)可能要做處理,剔除偏離較大的離群值
# 患病和不患病情況下 各特征的分布情況 for col in all_data.drop("Outcome",1).columns: plt.figure() sns.displot(data = all_data, x = col,hue = "Outcome",kind="kde") plt.show()
部分輸出:
1.從數據樣本本身出發(fā)研究數據分布特征,可以發(fā)現在患病和不患病兩種情況下,部分特征的密度分布比較相近,特別是height的分布圖,發(fā)現兩曲線基本相近。感覺和label之間的相關性都不是很強。
2.同時,可以發(fā)現部分特征存在右偏的現象(skewness (偏度) 描述數據分布形態(tài)的統(tǒng)計量,其描述的是某總體取值分布的對稱性),考慮到需要數據盡量服從正態(tài)分布,所以后續(xù)數據預處理需要對存在一定偏度的特征進行相關處理。
# 觀察各特征分布和患病的關系 corr = all_data.corr() plt.figure(figsize = (8,6)) sns.heatmap(corr,annot = True,cmap = "Blues") plt.show()
heatmap()函數可以直觀地將數據值的大小以定義的顏色深淺表示出來。
1.可以發(fā)現顏色相對來說都比較淺,也就是說無論是特征和特征之間還是特征和outcome標簽之間的相關性都沒有很高。
2.發(fā)現其余各特征變量中與outcome的相關度中最高的是Glucose 屬性值為0.49,最低的是Height屬性值為0.059。
3.同時觀察特征與特征之間的關系,發(fā)現Insulin與Glucose,BMI和SkinThickness之間的相關度分別為0.58,0.65屬于比較高的相關性,由于Insulin是一個確實比較嚴重的特征,而相關性可以是一種協助填充缺失值的方法。
plt.figure() sns.scatterplot(x = "Insulin", y = "Glucose", data = all_data) plt.show() sns.scatterplot(x = "Insulin", y = "BMI", data = all_data) plt.show() sns.scatterplot(x = "Insulin", y = "Age", data = all_data) plt.show() plt.figure() sns.scatterplot(x = "SkinThickness", y = "BMI", data = all_data) plt.show() sns.scatterplot(x = "SkinThickness", y = "Glucose", data = all_data) plt.show() sns.scatterplot(x = "SkinThickness", y = "BloodPressure", data = all_data) plt.show()
部分輸出:
六、構建baseline
因為決策樹幾乎不需要數據預處理。其他方法經常需要數據標準化,創(chuàng)建虛擬變量和刪除缺失值。
# 讀取數據 all_data = pd.read_csv("data.csv") # height 數值類型 為object 需要轉化為 數值型 all_data = all_data.astype({"Height":"float64"}) # all_data.dropna(inplace = True) # 特征 feature_data = all_data.drop("Outcome",1) # 標簽 label = all_data["Outcome"] base_model = DecisionTreeClassifier() base_scores = cross_validate(base_model, feature_data, label,cv=5,return_train_score=True) print(base_scores["test_score"].mean())
0.6954248366013072
七、數據預處理
綜合前面分析,先做了以下處理
# 讀取數據 all_data = pd.read_csv("data.csv") # height 數值類型 為object 需要轉化為 數值型 all_data = all_data.astype({"Height":"float64"}) # 理論缺失值0替換為np.nan replace_list = ["Glucose", "BloodPressure", "SkinThickness", "Insulin", "BMI", "Height"] all_data.loc[:,replace_list] = all_data.loc[:,replace_list].replace({0:np.nan}) # 刪除相關性低的Height all_data.drop("Height",1,inplace = True)
八、離群值處理
1.經過前面的分析發(fā)現數據是存在部分離群值的,雖然實驗本身就是關于疾病預測,異常值的存在屬于正常現象。但是對于一些可能超出人體接受范圍的值,衡量對預測的影響之后,由于數據量比較小,這里選擇刪除極端異常點。
2.極端異常點 :上限的計算公式為Q3+3(Q3-Q1) 下界的計算公式為Q1-3(Q3-Q1))。
# remove the outliers # 異常點 上須的計算公式為Q3+1.5(Q3-Q1);下須的計算公式為Q1-1.5(Q3-Q1) # 極端異常點 :上限的計算公式為Q3+3(Q3-Q1) 下界的計算公式為Q1-3(Q3-Q1) # 由于數據量比較少 所以選擇刪除極端異常值 def remove_outliers(feature,all_data): first_quartile = all_data[feature].describe()["25%"] third_quartile = all_data[feature].describe()["75%"] iqr = third_quartile - first_quartile # 異常值下標 index = all_data[(all_data[feature] < (first_quartile - 3*iqr)) | (all_data[feature] > (first_quartile + 3*iqr))].index all_data = all_data.drop(index) return all_data outlier_features = ["Insulin", "Glucose", "BloodPressure", "SkinThickness", "BMI", "DiabetesPedigreeFunction"] for feat in outlier_features: all_data = remove_outliers(feat,all_data)
處理之后的數據基本的統(tǒng)計信息
九、缺失值處理
1.直接刪除處理
def drop_method(all_data): median_fill = ["Glucose", "BloodPressure","SkinThickness", "BMI","Height"] for column in median_fill: median_val = all_data[column].median() all_data[column].fillna(median_val, inplace=True) all_data.dropna(inplace = True) return all_data
2.中值填充
def median_method(): for column in list(all_data.columns[all_data.isnull().sum() > 0]): median = all_data[column].median() all_data[column].fillna(median, inplace=True)
3.KNNImputer填充
def knn_method(): # 先將缺失值比較少的特征用中值填充 values = {"Glucose": all_data["Glucose"].median(),"BloodPressure":all_data["BloodPressure"].median(),"BMI":all_data["BMI"].median()} all_data.fillna(value=values,inplace=True) # 用KNNImputer 填充 Insulin SkinThickness corr_SkinThickness = ["BMI", "Glucose","BloodPressure", "SkinThickness"] # 權重按距離的倒數表示。在這種情況下,查詢點的近鄰比遠處的近鄰具有更大的影響力 SkinThickness_imputer = KNNImputer(weights = "distance") all_data[corr_SkinThickness] = SkinThickness_imputer.fit_transform(all_data[corr_SkinThickness]) corr_Insulin = ["Glucose", "BMI","BloodPressure", "Insulin"] Insulin_imputer = KNNImputer(weights = "distance") all_data[corr_Insulin] = Insulin_imputer.fit_transform(all_data[corr_Insulin])
4.隨機森林填充
from sklearn.ensemble import RandomForestRegressor from sklearn.impute import SimpleImputer # 用來填補缺失值 def predict_method(feature): # 復制一份數據 避免對原數據做出不必要的修改 copy_data = all_data.copy() # 缺失了的下標 predict_index = copy_data[copy_data[feature].isnull()].index # 沒缺失的下標 train_index = copy_data[feature].dropna().index # 用作預測 的訓練集標簽 train_label = copy_data.loc[train_index,feature] copy_data = copy_data.drop(feature,axis=1) # 對特征先用中值填充 imp_median = SimpleImputer(strategy="median") # 用作預測的訓練集特征 train_feature = copy_data.loc[train_index] train_feature = imp_median.fit_transform(train_feature) # 需要進行預測填充處理的缺失值 pre_feature = copy_data.loc[predict_index] pre_feature = imp_median.fit_transform(pre_feature) # 選取隨機森林模型 fill_model = RandomForestRegressor() fill_model = fill_model.fit(train_feature,train_label) # 預測 填充 pre_value = fill_model.predict(pre_feature) all_data.loc[predict_index,feature] = pre_value #用隨機森林的方法填充缺失值較多的 SkinThickness 和 Insulin 缺失值 predict_method("Insulin") predict_method("SkinThickness") # 其余值中值填充 for column in list(all_data.columns[all_data.isnull().sum() > 0]): median = all_data[column].median() all_data[column].fillna(median, inplace=True)
十、特征工程
# 特征 feture_data = all_data.drop("Outcome",1) # 標簽 label = all_data["Outcome"]
# 利用BMI和身高構造weight特征 # BMI = weight(kg) / height(m)**2 feture_data["weight"] = (0.01*feture_data["Height"])**2 * feture_data["BMI"]
十一、數據標準化
# 標準化 Std = StandardScaler() feture_data = Std.fit_transform(feture_data)
十二、模型構建與調參優(yōu)化
用到的模型
from sklearn.svm import SVC,SVR
from sklearn.tree import DecisionTreeClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier,StackingClassifier
調參方法
from sklearn.model_selection import GridSearchCV
評估指標 Accuracy roc_auc
from sklearn.metrics import make_scorer from sklearn.metrics import
accuracy_score from sklearn.metrics import roc_auc_score
def train(model, params): grid_search = GridSearchCV(estimator = model, param_grid = params,scoring=scores,refit="Accuracy") grid_search.fit(feture_data,label) print(grid_search.best_estimator_) return grid_search def paint(x,y): plt.figure() sns.lineplot(x=x,y=y) plt.show()
SVC
#調參時先嘗試一個大范圍,確定比較小的范圍,然后在小范圍里搜索 model = SVC() params = {"C":np.linspace(0.1, 2, 100)} SVC_grid_search = train(model,params) paint([x for x in range(100)],SVC_grid_search.cv_results_["mean_test_Accuracy"]) paint([x for x in range(100)],SVC_grid_search.cv_results_["mean_test_AUC"]) print("test_Accuracy : {} test_AUC : {}".format(SVC_grid_search.cv_results_["mean_test_Accuracy"].mean(),SVC_grid_search.cv_results_["mean_test_AUC"].mean()))
LogisticRegression
model = LogisticRegression() params = {"C":np.linspace(0.1,2,100)} LR_grid_search = train(model,params) paint([x for x in range(100)],LR_grid_search.cv_results_["mean_test_Accuracy"]) paint([x for x in range(100)],LR_grid_search.cv_results_["mean_test_AUC"]) print("test_Accuracy : {} test_AUC : {}".format(LR_grid_search.cv_results_["mean_test_Accuracy"].mean(),LR_grid_search.cv_results_["mean_test_AUC"].mean()))
RandomForestClassifier
model = RandomForestClassifier() params = {"n_estimators":[x for x in range(30,50,2)],"min_samples_split":[x for x in range(4,10)]} RFC_grid_search = train(model,params) print("test_Accuracy : {} test_AUC : {}".format(RFC_grid_search.cv_results_["mean_test_Accuracy"].mean(),RFC_grid_search.cv_results_["mean_test_AUC"].mean()))
StackingClassifier
estimators = [ ("SVC",SVC_grid_search.best_estimator_), ("NB", LR_grid_search.best_estimator_), ("RFC", RFC_grid_search.best_estimator_) ] model = StackingClassifier(estimators=estimators, final_estimator=SVC()) model_score = cross_validate(model,feture_data, label,scoring=scores) print("test_Accuracy : {} test_AUC : {}".format(model_score["test_Accuracy"].mean(),model_score["test_AUC"].mean()))
SVC預測結果:
1.直接刪除缺失值以及異常值刪除公式上限Q3+1.5(Q3-Q1);下限計算公式為Q1-1.5(Q3-Q1)
SVC(C=1.097979797979798)
test_Accuracy : 0.8549075391180654
test_AUC : 0.511601411290322
2.直接刪除缺失值以及異常值刪除公式上限Q3+3(Q3-Q1);下限計算公式為Q1-3(Q3-Q1)
SVC(C=1.405050505050505)
test_Accuracy : 0.7953321596244133
test_AUC : 0.7133755225726653
3.中值填充以及異常值刪除公式上限Q3+3(Q3-Q1);下限計算公式為Q1-3(Q3-Q1)
SVC(C=1.7888888888888888)
test_Accuracy : 0.7814101086443079
test_AUC : 0.7248522348166069
十三、總結
1.一些刪除數據值的處理方法導致樣本標簽的不均衡會導致對比例大的樣本造成過擬合,也就是說預測偏向樣本數較多的分類。這樣就會大大降低模型的泛化能力。表現在準確率很高,但roc_auc_score很低。上面SVC的預測結果就很好的說明了。
2.可以看出由于缺失值比較多,所以反而各種填充方法的效果比直接刪除的效果是要更差的(也有可能我沒找到合適的填充方法)
3.關于離群值的處理,主要方法有直接刪除法,替換為缺失值處理,以及中值填充法等。由于缺失值處理那里的效果不是很理想,所以就選擇了直接刪除,并且在平衡了roc_auc_score和accuracy兩個指標后,選擇只刪除極端異常點。
4.關于樣本0/1比例的問題,可以考慮上采樣或者下采樣的方法平衡樣本。本文不涉及。
到此這篇關于python數據分析之用sklearn預測糖尿病的文章就介紹到這了,更多相關用sklearn預測糖尿病內容請搜索服務器之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持服務器之家!
原文鏈接:https://blog.csdn.net/qq_43402639/article/details/115841530