Advertisement

心脏病预测-----集成学习xgboost

阅读量:

该研究使用集成学习中的XGBoost模型,结合集成学习对心脏病进行预测。数据集包含303个样本和14个特征,涵盖年龄、性别、胸痛类型、静息血压等信息。研究通过相关性分析、可视化和特征重要性评估,发现多个特征对心脏病预测具有显著影响。模型采用独热编码和标准化处理,最终在超参数优化后,XGBoost模型的AUC值达到0.97,显示较高的预测准确性。

一、问题背景

基于集成方法的xgboost模型被用来应用于心脏病预测任务。

二、数据集分析

属性 含义
age 年龄
sex 性别 1=male,0=female
cp 胸痛类型(4种) 值1:典型心绞痛,值2:非典型心绞痛,值3:非心绞痛,值4:无症状
trestbps 静息血压
chol 血清胆固醇
fbs 空腹血糖 >120mg/dl ,1=true; 0=false
restecg 静息心电图(值0,1,2)
thalach 达到的最大心率
exang 运动诱发的心绞痛(1=yes;0=no)
oldpeak 相对于休息的运动引起的ST值(ST值与心电图上的位置有关)
slope 运动高峰ST段的坡度 Value 1: upsloping向上倾斜, Value 2: flat持平, Value 3: downsloping向下倾斜
ca The number of major vessels(血管) (0-3)
thal A blood disorder called thalassemia (3 = normal; 6 = fixed defect; 7 = reversable defect) 一种叫做地中海贫血的血液疾病(3 =正常;6 =固定缺陷;7 =可逆转缺陷)
target 目标变量(数字0和1)

注:slope特征:
通过坡度方向降低ST段心电图结果

在这里插入图片描述

ST段的异常情况与正常ST段进行比较,值得注意的是,T波出现异常

在这里插入图片描述

三、代码实现

复制代码
    # 导入相关的宝
    import numpy as np 
    import pandas as pd
    import seaborn as sns
    from sklearn.neighbors import KNeighborsClassifier
    from sklearn.model_selection import GridSearchCV
    from sklearn.metrics import accuracy_score, confusion_matrix, classification_report
    from matplotlib import pyplot as plt
    from sklearn.linear_model import LogisticRegression
    from sklearn.metrics import roc_curve, roc_auc_score
    from sklearn.model_selection import train_test_split
    from sklearn.preprocessing import StandardScaler, RobustScaler, MinMaxScaler, LabelEncoder
    from sklearn.tree import DecisionTreeClassifier
    from sklearn.decomposition import PCA
    from sklearn.svm import SVC
    from sklearn.ensemble import RandomForestClassifier
    from xgboost import XGBClassifier
    import warnings
    
    # 导入数据
    warnings.simplefilter(action='ignore')
    df = pd.read_csv("C:/Users/David/Desktop/data/data/heart/heart.csv")
    df.head()
    
    # 基本的EDA和可视化
    df.describe()
    
    df.info()
    
    sns.set_context("talk")
    plt.figure(figsize=(15, 12))
    sns.heatmap(df.corr(), annot=True, fmt=".2f")
在这里插入图片描述

该图是一个矩阵,它呈现了数据集中所有列之间的相关性。对于包含相对较少特征的数据集,这种图是一种直观的可视化线性关系的方法。然而,需要注意的是,这个特定的数据集主要由分类特征组成,因此上图中的大多数相关系数都没有实际意义,特别是对于包含非二元分类特征的点。

复制代码
    df.groupby("target")[["thalach","chol","age","trestbps"]].mean()

我选择了所有的数字列,计算了它们的平均值,并按照目标列“条件”进行分组。

复制代码
    # 年龄分布直方图
    sns.set_style("darkgrid")
    sns.set_context("poster")
    plt.figure(figsize=(12, 9))
    sns.histplot(data=df["age"], alpha=0.6)
    plt.axvline(df["age"].mean(), color="red")
    plt.title("Distribution of age in the dataset")
    plt.text(33, 42, "Mean age: " + str(round(df["age"].mean(), 1)), color="indianred", weight="heavy", size="larger", bbox=dict(boxstyle="round", color="rosybrown"))
    plt.xlabel("Age")
    plt.ylabel("Count")
    plt.show()
    # 数据集中的年龄分布比较正常,平均年龄为54.5岁。
    
    # ST降压分布直方图
    sns.set_style("darkgrid")
    sns.set_context("poster")
    plt.figure(figsize=(12, 9))
    sns.histplot(data=df["oldpeak"], alpha=0.6)
    plt.title("Relative ST depression (Exercise-Rest, mm)")
    plt.xlabel("Difference (Exercise-Rest, mm)")
    plt.ylabel("Count")
    plt.show()

根据图示,大多数病例在心电图ST段压低的最底层点之间的差值通常在0至2毫米之间。在该研究中,我们采用2毫米作为中断应力测试的临界值,因此可以认为,超出这个范围的数值应被视为异常情况。

复制代码
    #创建一个新的列
    df["Heart condition"] = df["target"].replace({1:"Present", 0:"Not present"})
    
    # 关于实际标绘的部分
    sns.set_style("darkgrid")
    sns.set_context("poster")
    plt.figure(figsize=(12, 9))
    sns.scatterplot("chol", "age", hue=df["Heart condition"], data=df)
    plt.title("Cases presenting heart disease compared by their age and blood cholesterol")
    plt.xlabel("Blood cholesterol (mg/dL)")
    plt.ylabel("Age")
    plt.show()
    
    sns.set_style("darkgrid")
    sns.set_context("poster")
    plt.figure(figsize=(12, 9))
    sns.scatterplot("thalach", "age", hue=df["Heart condition"], data=df)
    plt.title("Maximum heart rate achieved by presence of heart condition and distribution of age")
    plt.xlabel("Maximum heart rate (beats per minute)")
    plt.ylabel("Age")
    plt.show()
在这里插入图片描述

对于健康个体而言,心脏功能可能轻微向右偏移(或趋向),这表明具有较高最大心率值的人群可能更有可能拥有健康心脏。值得注意的是,年轻人群体的心率水平较高,这表明年龄与最大心率之间呈现反比关系。

复制代码
    sns.set_style("darkgrid")
    sns.set_context("poster")
    plt.figure(figsize=(12, 9))
    sns.scatterplot("trestbps", "age", hue=df["Heart condition"], data=df)
    plt.title("Change of rest heart bpm by age")
    plt.xlabel("Resting heart rate (beats per minute)")
    plt.ylabel("Age")
    plt.show()
    
    plt.figure(figsize=(12, 9))
    g = sns.countplot("exang", hue="slope", data=df)
    g.set_xticklabels(["Not present", "Present"])
    plt.title("Presence of exercise induced angina by the slope's direction on peak of the ST segment")
    plt.xlabel("Presence of exercise induced angina")
    plt.ylabel("Counts")
    plt.legend(["Upsloping", "Flat", "Downsloping"])
    plt.show()

根据PubMed网站上的这篇(https://www.ncbi.nlm.nih.gov/pmc/articles/PMC1123032/)文章:“……因此,在运动过程中,正常的ST段会显著地上升……”
在努力测试中,健康个体的ST段斜率预计呈现上升趋势。

复制代码
    plt.figure(figsize=(12, 9))
    g = sns.countplot("cp", hue="sex", data=df)
    g.set_xticklabels(["Typical", "Atypical", "Non-Anginal", "Asymptomatic"])
    plt.title("Distribution of anginal pain type by gender")
    plt.xlabel("Anginal pain type")
    plt.ylabel("Count")
    plt.legend(["Female", "Male"])
    plt.show()
    
    plt.figure(figsize=(20, 9))
    g = sns.countplot(pd.cut(df["oldpeak"], 13), hue="Heart condition", data=df) #I had to bin the x values since there were too many distinct values to interprete
    plt.title("Difference ST segment depression (Exercise-Rest) by presence of heart disease")
    plt.xlabel("Difference")
    plt.ylabel("Count")
    plt.xticks(rotation=75)
    plt.legend(["Not present", "Present"])
    plt.show()

虽然这可能不是最理想的情节,但它明确表明,在大萧条期间,运动与休息的变化呈现出相似的方向。这表明,在大萧条期间,运动与休息的变化呈现出相似的方向,更有可能遇到心脏病患者。

复制代码
    #将连续的特征集合在一起,从而创建离散的分类列,可以帮助模型一般化数据和减少过拟合
    df["thalach"] = pd.cut(df["thalach"], 8, labels=range(1, 9))
    df["trestbps"] = pd.cut(df["trestbps"], 5, labels=range(8, 13))
    df["age"] = pd.cut(df["age"], 12, labels=range(12, 24))
    df["chol"] = pd.cut(df["chol"], 10, labels=range(24, 34))
    df["oldpeak"] = pd.cut(df["oldpeak"], 5, labels=range(34, 39))

通过统一的归类处理,将全部的连续数据转换为分类数据。在可选选项数量有限的情况下,该模型能够揭示特定特征的分布权重情况。

复制代码
    # 对类别变量独热编码
    a = pd.get_dummies(df, columns=["cp", "restecg", "slope", "thalach", "trestbps", "age", "chol", "thal", "oldpeak"], 
                   prefix=["cp", "restecg", "slope", "thalach", "trestbps", "age", "chol", "thal", "oldpeak"], drop_first=True)

分类特征的编码具有重要意义,因为如果不进行编码,模型可能会将类别表示的数字视为权重进行处理,这可能导致模型无法准确捕捉到特征之间的相互关系。我采用了热编码方法,并将第一个参数设置为False以实现降维效果。

复制代码
    a = a.drop("Heart condition", axis=1)

我移除了并更名了condition列,并在图表中为该列添加了适当的图例说明。

复制代码
    a.head()
    
    ## 确定特征重要性
    rf = RandomForestClassifier()
    rf.fit(a.drop("target", axis=1), a.target)
    importances = rf.feature_importances_
    features = pd.Series(importances, index=a.drop("target", axis=1).columns)
    plt.figure(figsize=(12, 16))
    features.plot(kind="barh")
    plt.show()
在这里插入图片描述

通过随机森林分类器系统性地评估特征重要性并详细地展示它们。

复制代码
    # 构建模型和超参数调优
    X = a.drop(["target", "restecg_1", "thalach_2", "thalach_8", "trestbps_12", "age_13", "age_22", "age_23", 
            "chol_29", "chol_30", "chol_31", "chol_32", "chol_33", "oldpeak_37", "oldpeak_38"], axis=1)
    y = a.target
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, stratify=y, random_state=42)
    
    #用xGBoosting运行GridSearchCV要花费很长时间,后面代码是添加了超参数的结果。
    # 查找最佳超参数
    # xgb = xgb = XGBClassifier(objective="binary:logistic", eval_metric="auc")
    # params = {"max_depth":np.arange(1, 8), "n_estimators":[100, 300, 500, 600, 900, 1000], 
    #           "learning_rate":[0.001, 0.01, 0.03, 0.05, 0.07], "colsample_bytree":[0.35, 0.5, 0.65],
    #           "subsample":[0.4, 0.5, 0.55, 0.7]
    #           }
    # grid_xgb = GridSearchCV(estimator = xgb, param_grid = params, cv=10, n_jobs=-1)
    # grid_xgb.fit(X_train, y_train)
    # xgb_pred = grid_xgb.predict(X_test)
    # print(classification_report(y_test, xgb_pred))
    
    xgb = XGBClassifier(objective="binary:logistic", eval_metric="auc", max_depth=1,
                    n_estimators=500,learning_rate=0.05,colsample_bytree=0.35,subsample=0.5)
    xgb.fit(X_train, y_train)
    xgb_pred = xgb.predict(X_test)

为了解决数据噪声和过拟合问题,我移除了目标特征列以及重要性百分比较低的特征列。具体来说,这两个特征列(如chol_31和chol_32)并未被模型用于节点划分。在数据准备阶段,我采用80%的数据进行模型训练,剩余的20%用于测试,并经过分层采样处理,确保训练集和验证集在人口健康分布上具有一致性。为了优化模型超参数,我采用了GridSearchCV方法,并在调参过程中应用了10次交叉验证。

复制代码
    # 模型评估
    print(grid_xgb.best_params_)
    
    print(grid_xgb.cv_results_["mean_test_score"].mean())
    
    print(classification_report(y_test, xgb_pred))

采用加权平均策略的模型具有90%的最高准确率,而其10折交叉验证训练与测试数据的平均准确率降低至81%左右。值得注意的是,最佳评分模型的准确性分数与采用10种不同训练/测试分割的被调优模型提供的准确性分数平均值相比,仅相差8%,这表明模型存在显著的过拟合问题。

复制代码
    sns.set_context("talk")
    plt.figure(figsize=(12, 9))
    sns.heatmap(confusion_matrix(y_test, xgb_pred), annot=True, xticklabels=["Healthy", "Sick"], yticklabels=["Healthy", "Sick"], fmt="g", cmap="icefire_r")
    plt.ylabel("Predicted")
    plt.xlabel("Actual")
    plt.title("Confusion matrix of xGBoosting")
    plt.show()
在这里插入图片描述
复制代码
    xgb_prob = xgb.predict_proba(X_test)[:, 1]
    fpr, tpr, thresholds = roc_curve(y_test, xgb_prob)
    sns.set_style("darkgrid")
    sns.set_context("poster")
    plt.figure(figsize=(15, 10))
    plt.plot([0, 1], [0, 1], 'k--')
    sns.lineplot(fpr, tpr, alpha=0.6, ci=None)
    plt.xlabel('False positive rate')
    plt.ylabel('True positive rate')
    plt.title('ROC curve')
    plt.legend(["Baseline", "xGBoosting"])
    plt.show()
    print(roc_auc_score(y_test, xgb_prob))
在这里插入图片描述

为模型提供理想的参数配置和优化的训练-测试分割方案,其AUC值达到0.97。

全部评论 (0)

还没有任何评论哟~