kaggle实战:机器学习建模预测肾脏疾病
本文是针对kaggle上面一份肾脏疾病数据的建模,包含:
- 数据预处理
- 特征工程
- 缺失值填充
- 分类模型的建立
- 模型结果对比
- shap模型可解释性
原数据集地址:
https://www.kaggle.com/datasets/mansoordaku/ckdisease?datasetId=1111&sortBy=voteCount
结果
先看看最终的结果对比:
- KNN是分数最低的;LGBM第一。一般在kaggle,分类问题LGBM高频使用,且效果一般都比较好
- 树模型中,以决策树为基础,效果都有所提升。
导入库
笔记1📒:一般在建模中,导入库包含:
- 数据处理pandas为主
- 可视化库:笔者一般用的Plotly结合seaborn;偶尔用原生的matplotlib和pyecharts
- 各种回归和分类模型 + 评价指标
- 其他:切分数据、降维、采样、标准化等
1 | import numpy as np |
数据基本信息
很明显:上面数据中id字段是对建模无用的,直接drop函数删除:
In [3]:
1 | df.drop("id",axis=1,inplace=True) |
查看数据量大小:行数和字段属性数量
In [4]:
1 | df.shape |
Out[4]:
1 | (400, 25) |
总共是400条数据,25个字段
不同的字段类型统计:
In [5]:
1 | df.dtypes |
Out[5]:
1 | age float64 |
In [6]:
1 | pd.value_counts(df.dtypes) |
Out[6]:
只包含两个类型的字段
1 | object 14 |
查看缺失值情况:
In [7]:
1 | df.isnull().sum().sort_values(ascending=False) |
Out[7]:
1 | rbc 152 |
数值型字段的描述统计信息,通常是查看这些字段的统计值信息:总统计量、最值、四分位数等:
In [8]:
1 | df.describe().style.background_gradient(cmap="ocean_r") # 描述统计信息 |
数据基本信息:
In [9]:
1 | df.info() |
字段解释
针对每个字段的中文含义解释:
In [10]:
1 | columns = df.columns |
Out[10]:
1 | Index(['age', 'bp', 'sg', 'al', 'su', 'rbc', 'pc', 'pcc', 'ba', 'bgr', 'bu', |
- age:年龄
- bp:blood_pressure,血压
- sg:specific_gravity,比重值;肾脏疾病通常是检测尿比重
- al:albumin,白蛋白
- su:sugar,葡萄糖
- rbc:red_blood_cells,【红血细胞】是否正常?
- pc:pus_cell,【脓细胞】含量是否正常?
- pcc:pus_cell_clumps,【脓细胞群】是否正常
- ba:bacteria,是否【细菌】感染?
- bgr:blood_glucose_random,随机血糖量
- bu:blood_urea,血尿素
- sc:serum_creatinine,血清肌酐
- sod:sodium,钠
- pot:potassium,钾
- hemo:haemoglobin,血红蛋白
- pcv:packed_cell_volume(PCV),血细胞压积,红细胞在血液中所占容积比
- wc:white_blood_cell_count,白血细胞计数
- rc:red_blood_cell_count,红血细胞计数
- htn:hypertension,是否有【高血压】?
- dm:diabetes_mellitus,是否有【糖尿病】?
- cad:coronary_artery_disease,是否有【冠状动脉疾病】?
- appet:appetite,是否有【食欲】?
- pe:peda_edema,足部是否【水肿】?
- ane:aanemia,是否【贫血】?
- classification:分类结果,是否患病
字段预处理
下面我们对部分字段进行处理
字段classification
最终分类结果的处理
In [11]:
1 | df["classification"].value_counts() # 修改前 |
Out[11]:
1 | ckd 248 |
可以看到有2个记录是异常的,这种情况就是属于数据异常,需要手动定位发现统一改成ckd:
In [12]:
1 | df["classification"] = df["classification"].apply(lambda x: x if x == "notckd" else "ckd") |
In [13]:
1 | df["classification"].value_counts() # 修改后 |
Out[13]:
1 | ckd 250 |
年龄age
In [14]:
1 | px.violin(df,y="age",color="classification") |
pcv:packed_cell_volume(PCV)
PCV-血细胞压积,红细胞在血液中所占容积比
In [15]:
1 | df["pcv"].value_counts() # 修改前 |
可以看到这个字段存在不规范的记录,也需要处理:
In [16]:
1 | df["pcv"] = pd.to_numeric(df["pcv"], errors="coerce") |
In [17]:
1 | df["pcv"].value_counts() # 修改后 |
wc:white_blood_cell_count
白血细胞计数
In [18]:
1 | df["wc"].value_counts() # 修改后 |
Out[18]:
1 | 9800 11 |
In [19]:
1 | df["wc"] = pd.to_numeric(df["wc"], errors="coerce") |
rc:red_blood_cell_count
红血细胞计数
In [20]:
1 | df["rc"].value_counts() # 修改前 |
也需要进行转化:
In [21]:
1 | df["rc"] = pd.to_numeric(df["rc"], errors="coerce") |
In [22]:
1 | # 不同字段类型统计 |
Out[22]:
1 | float64 14 |
dm:diabetes_mellitus
是否有【糖尿病】?
In [23]:
1 | df["dm"].value_counts() |
Out[23]:
1 | no 258 |
dm字段存在异常,一般是空格和换行符引起的;我们将取值统一成no和yes
In [24]:
1 | df["dm"] = df["dm"].str.strip() |
In [25]:
1 | df["dm"].value_counts() |
Out[25]:
1 | no 261 |
cad:coronary_artery_disease
是否有【冠状动脉疾病】?
In [26]:
1 | df["cad"].value_counts() |
Out[26]:
1 | no 362 |
In [27]:
1 | df["cad"] = df["cad"].str.strip() |
查看处理后df的信息:
In [28]:
1 | df.info() |
不同特征分布
In [29]:
1 | # 分类型 |
分类型变量取值
下面查看分类型变量的不同取值情况:
In [30]:
1 | for col in cat_cols: |
In [31]:
1 | # 分类型变量统计 |
Out[31]:
1 | 11 |
In [32]:
1 | plt.figure(figsize = (20, 16)) |
连续型变量分布
In [33]:
1 | len(num_cols) # 总共是14个连续型数值变量 |
Out[33]:
1 | 14 |
In [34]:
1 | plt.figure(figsize = (20, 16)) |
小结:可以看到多个特征的分布存在一定的偏度skewness(更多的是左偏)
不同特征分布
定义3个不同绘图函数
In [35]:
1 | # 1、小提琴图:查看数据分布情况 |
In [36]:
1 | violin("rc") |
1 | kde("rc") |
两两变量关系
两个连续型变量之间的关系
缺失值处理
整体缺失情况
In [56]:
1 | # 全部字段的缺失值情况 |
Out[56]:
1 | rbc 152 |
In [57]:
1 | # 连续型变量缺失 |
Out[57]:
1 | age 9 |
In [58]:
1 | # 分类型变量缺失 |
Out[58]:
1 | rbc 152 |
两种填充方式
- 随机采样填充:在字段现有值的数据中随机采样进行填充,针对的缺失值较多的字段
- 均值或众数填充:针对缺失值较少的字段,用该字段现有数据的均值或者众数填充
In [59]:
1 | df["rbc"].isna().sum() # 表示某个字段的缺失量 |
Out[59]:
1 | 152 |
In [60]:
1 | df["dm"].mode()[0] # 某个字段的众数 |
Out[60]:
1 | 'no' |
In [61]:
1 | def random_value_imputate(col): |
1、连续型变量使用随机填充方法:
In [62]:
1 | for col in num_cols: |
2、分类型变量,针对字段不同方法不同:
In [63]:
1 | # 随机填充 |
In [64]:
1 | # 其他字段是众数填充 |
填充完成后数据就没有缺失值:
In [65]:
1 | df.isnull().sum() |
Out[65]:
1 | age 0 |
相关性分析
In [67]:
1 | df["classification"] = df["classification"].map({"ckd":0, "notckd":1}) |
可以看到和classification强相关的特征主要是:sg(尿比重)、hemo(血红蛋白)、pcv(血细胞压积,红细胞在血液中所占容积比)、rc(红细胞数量)
特征编码
针对分类型变量编码:
In [68]:
1 | for col in cat_cols: |
所有的分类型变量都是两种取值情况,我们直接使用类型编码,变成0-1即可:
In [69]:
1 | from sklearn.preprocessing import LabelEncoder |
为了分析的方便,也对classification字段进行编码:
In [70]:
1 | df["classification"].value_counts() |
Out[70]:
1 | 0 250 |
建模
特征和目标
In [71]:
1 | X = df.drop("classification",axis=1) |
训练集和测试集
In [72]:
1 | # 随机打乱数据 |
In [73]:
1 | # from sklearn.model_selection import train_test_split |
定义建模函数
In [75]:
1 | def create_model(model): |
8种模型
KNN
In [76]:
1 | from sklearn.neighbors import KNeighborsClassifier |
决策树
In [77]:
1 | from sklearn.tree import DecisionTreeClassifier |
随机森林Random Forest Classifier
In [78]:
1 | from sklearn.ensemble import RandomForestClassifier |
Ada Boost Classifier
In [80]:
1 | from sklearn.ensemble import AdaBoostClassifier |
Gradient Boosting Classifier
In [81]:
1 | from sklearn.ensemble import GradientBoostingClassifier |
XgBoost
In [82]:
1 | from xgboost import XGBClassifier |
Cat Boost Classifier
In [83]:
1 | from catboost import CatBoostClassifier |
Extra Trees Classifier
In [84]:
1 | from sklearn.ensemble import ExtraTreesClassifier |
LGBM
In [85]:
1 | from lightgbm import LGBMClassifier |
模型对比
In [86]:
1 | models = pd.DataFrame({"model":["KNN","Decision Tree","Random Forest","Ada Boost ", |
Out[86]:
In [87]:
1 | models = models.sort_values("acc",ascending=True) # 升序排列 |
Out[87]:
In [88]:
1 | px.bar(models, |
模型可解释性
我们在这里选择随机森林模型(rd_clf)同时使用shap库来进行解释
shap值计算
In [89]:
1 | explainer = shap.TreeExplainer(rd_clf) |
Out[89]:
1 | [array([[ 0.00082722, -0.00174422, 0.08114011, ..., -0.00147523, |
Feature Importance
In [90]:
1 | shap.summary_plot(shap_values[1], X_test, plot_type="bar") |
从结果来看,sg(尿比重)、sc(血清肌酐)、hemo(血红蛋白)是重点影响特征。
1 | shap.summary_plot(shap_values[1], X_test) |
summary plot
为每个样本绘制其每个特征的SHAP值;一个点代表一个样本,颜色表示特征值的高低(红色高,蓝色低)
个体差异
查看单个病人的不同特征属性对其结果的影响:
从选择3个病人的结果来看,即使同样是患病者shap值的个体差异仍然很大。