在当今快节奏的生活中,心脏病已成为全球范围内的主要健康问题之一。早期诊断和预防对于降低心脏病的发病率和死亡率至关重要。随着机器学习技术的不断发展,我们可以通过分析大量的医疗数据来预测心脏病的发生风险,从而为医疗专业人士提供有力的决策支持。本文将介绍如何使用机器学习模型对心脏病进行预测分析,并探讨不同模型的性能和实际应用价值。

数据集介绍

本次分析使用了一个包含1319条记录的心脏病数据集,数据集涵盖了患者的年龄、性别、心率、血压、血糖、肌酸激酶同工酶(CK-MB)和肌钙蛋白等关键生理和生化指标。此外,数据集中还包含了患者的诊断结果(心脏病阳性或阴性)。通过对这些数据的分析,我们可以探索不同特征与心脏病之间的关系,并构建预测模型。

库导入及其数据读取

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt #画图
import matplotlib as mpl
import scipy.stats as stats
from scipy.stats import chi2_contingency
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.svm import SVC   #支持向量积
#from xgboost import XGBClassifier   
from sklearn.metrics import classification_report, confusion_matrix, roc_curve, auc  #评价指标
import warnings
warnings.filterwarnings('ignore') #忽略警告信息
mpl.rcParams['font.family'] = 'SimHei'
plt.rcParams['axes.unicode_minus'] = False   # 步骤二(解决坐标轴负数的负号显示问题)
data = pd.read_csv('Medicaldataset.csv')
data.head()
AgeGenderHeart rateSystolic blood pressureDiastolic blood pressureBlood sugarCK-MBTroponinResult
06416616083160.01.800.012negative
1211949846296.06.751.060positive
25516416077270.01.990.003negative
36417012055270.013.870.122positive
45516411265300.01.080.003negative
## 数据预处理 在进行模型训练之前,数据预处理是必不可少的步骤。首先,我们对数据进行了初步检查,发现数据集中不存在重复值,但存在一些极端异常值。例如,心率大于1000、收缩压小于50和舒张压大于140的记录被认为是异常的,这些记录可能由于数据录入错误或测量误差导致。我们对这些异常值进行了删除或修正处理。 此外,我们还对数据中的性别和诊断结果进行了编码,将性别从“男/女”转换为“1/0”,并将诊断结果从“positive/negative”转换为“1/0”。为了使模型更好地处理连续变量,我们对年龄、CK-MB和肌钙蛋白等特征进行了标准化处理。
#查看数据信息
data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1319 entries, 0 to 1318
Data columns (total 9 columns):
Age                         1319 non-null int64
Gender                      1319 non-null int64
Heart rate                  1319 non-null int64
Systolic blood pressure     1319 non-null int64
Diastolic blood pressure    1319 non-null int64
Blood sugar                 1319 non-null float64
CK-MB                       1319 non-null float64
Troponin                    1319 non-null float64
Result                      1319 non-null object
dtypes: float64(3), int64(5), object(1)
memory usage: 92.8+ KB
#删除重复值
data.duplicated().sum()
0
# 为数据集特征创建映射字典,中文便于理解
feature_map = {
    'Age': '年龄',
    'Heart rate': '心率',
    'Systolic blood pressure': '收缩压',
    'Diastolic blood pressure': '舒张压',
    'Blood sugar': '血糖',
    'CK-MB': '肌酸激酶同工酶',
    'Troponin': '肌钙蛋白'
}

#绘制每个特征的统计分布图,查找是否存在离群点(异常值)
plt.figure(figsize=(20, 10))
for i, (col, col_name) in enumerate(feature_map.items(), 1):
    plt.subplot(2, 4, i) #创建子图
    plt.boxplot(data[col]) #画箱形图
    plt.title(f'{col_name}的箱线图', fontsize=14)
    plt.ylabel('数值', fontsize=12)
    plt.grid(axis='y', linestyle='--', alpha=0.7)

plt.tight_layout()
plt.show()

在这里插入图片描述

通过观察发现:心率、收缩压、舒张压存在极端异常值,虽然年龄、血糖、肌酸激酶同工酶、肌钙蛋白也存在离群点,但是这些离群点可能是真实反映,故不处理(急性心肌梗死情况下,肌酸激酶同工酶和肌钙蛋白会特别高),只处理那些极端异常值,删除心率大于1000的那个异常值点,删除收缩压小于50的那个点,删除舒张压大于140的那个点。

# 处理前的数据量
print(f"处理前数据量: {len(data)}")

# 删除心率大于1000的异常值
data = data[data['Heart rate'] <= 1000]

# 删除收缩压小于50的异常值
data = data[data['Systolic blood pressure'] >= 50]

# 删除舒张压大于140的异常值
data = data[data['Diastolic blood pressure'] <= 140]

print(f"处理后数据量: {len(data)}")
处理前数据量: 1319
处理后数据量: 1314
#检查舒张压和收缩压的关系,看看是否存在舒张压大于收缩压的情况。
wrong_bloodpressure = data[data['Diastolic blood pressure'] > data['Systolic blood pressure']]
print(f"发现舒张压大于收缩压的记录数: {len(wrong_bloodpressure)}")
发现舒张压大于收缩压的记录数: 6

考虑到可能是录入时候这6条数据录入反了,所以进行调换处理。

# 获取这些记录的索引
wrong_bloodpressure_index = wrong_bloodpressure.index
# 调换这些记录的收缩压和舒张压值
data.loc[wrong_bloodpressure_index, ['Systolic blood pressure', 'Diastolic blood pressure']] = data.loc[wrong_bloodpressure_index, ['Diastolic blood pressure', 'Systolic blood pressure']].values
#再次画图检查一下分布情况

plt.figure(figsize=(20, 10))
for i, (col, col_name) in enumerate(feature_map.items(), 1):
    plt.subplot(2, 4, i)
    plt.boxplot(data[col])
    plt.title(f'处理后{col_name}的箱线图', fontsize=14)
    plt.ylabel('数值', fontsize=12)
    plt.grid(axis='y', linestyle='--', alpha=0.7)

plt.tight_layout()
plt.show()

在这里插入图片描述

数据分析

通过数据可视化,我们发现了一些有趣的趋势。例如,男性患心脏病的比例高于女性,且年龄较大的患者更容易患心脏病。此外,心脏病患者的肌酸激酶同工酶和肌钙蛋白水平显著高于未患病者。这些发现为后续的模型构建提供了重要的背景信息。

data.describe().T
countmeanstdmin25%50%75%max
Age1314.056.19863013.64558314.00047.00058.00065.0000103.0
Gender1314.00.6598170.4739510.0000.0001.0001.00001.0
Heart rate1314.075.98782315.28689320.00064.00074.00085.0000135.0
Systolic blood pressure1314.0127.24277026.01987665.000110.000124.000143.0000223.0
Diastolic blood pressure1314.072.16133913.83362738.00062.00072.00081.0000128.0
Blood sugar1314.0146.79048775.01365935.00098.000116.000170.0000541.0
CK-MB1314.015.32287746.4084360.3211.6602.8505.8175300.0
Troponin1314.00.3611741.1565100.0010.0060.0140.085010.3
#可视化分析

#1.患者年龄分布
plt.figure(figsize=(20,15))
plt.subplot(3,3,1)
data['Age'].plot(kind='kde',color='red',label='核密度图')#绘制核密度图
data['Age'].plot(kind='hist',bins=12,secondary_y=True)#绘制直方图,,secondary_y=True添加第二个y轴显示人数
#plt.hist(data['Age'],bins=12)
plt.title('患者年龄分布')
plt.xlabel('年龄(岁)')
plt.ylabel('人数')
plt.grid(axis='y',linestyle='--')

#2.患者性别分布(饼图)
gender_counts=data['Gender'].value_counts() #数据统计
plt.subplot(3,3,2)
plt.pie(gender_counts,labels=['男','女'],autopct="%1.1f%%",startangle=75,colors=['#66b3ff','#ff9999'])
plt.title('患者性别分布')
plt.axis('equal') #使得饼图为圆形

#3.绘制患者心率分布
plt.subplot(3,3,3)
data['Heart rate'].plot(kind='kde',color='red',label='核密度图')#绘制核密度图
data['Heart rate'].plot(kind='hist',bins=12,secondary_y=True)#绘制直方图
#plt.hist(data['Heart rate'],bins=12)
plt.title('患者心率分布')
plt.xlabel('心率(次/分钟)')
plt.ylabel('人数')
plt.grid(axis='y',linestyle='--')

#4.绘制患者收缩压分布
plt.subplot(3,3,4)
data['Systolic blood pressure'].plot(kind='kde',color='red',label='核密度图')#绘制核密度图
data['Systolic blood pressure'].plot(kind='hist',bins=12,secondary_y=True)#绘制直方图
#plt.hist(data['Systolic blood pressure'],bins=12)
plt.title('患者收缩压分布')
plt.xlabel('收缩压(mmHg)')
plt.ylabel('人数')
plt.grid(axis='y',linestyle='--')

#5.绘制患者舒张压分布
plt.subplot(3,3,5)
data['Diastolic blood pressure'].plot(kind='kde',color='red',label='核密度图')#绘制核密度图
data['Diastolic blood pressure'].plot(kind='hist',bins=12,secondary_y=True)#绘制直方图
#plt.hist(data['Diastolic blood pressure'],bins=12)
plt.title('患者舒张压分布')
plt.xlabel('舒张压(mmHg)')
plt.ylabel('人数')
plt.grid(axis='y',linestyle='--')

#6.患者血糖分布
plt.subplot(3,3,6)
data['Blood sugar'].plot(kind='hist',bins=12,secondary_y=True)#绘制直方图
data['Blood sugar'].plot(kind='kde',color='red',label='核密度图')#绘制核密度图
#plt.hist(data['Blood sugar'],bins=12,density=True)
plt.title('患者血糖分布')
plt.xlabel('血糖(mg/dl)')
plt.ylabel('人数')
plt.grid(axis='y',linestyle='--')

#7.患者肌酸激酶同工酶水平
plt.subplot(3,3,7)
data['CK-MB'].plot(kind='kde',color='red',label='核密度图')#绘制核密度图
data['CK-MB'].plot(kind='hist',bins=12,secondary_y=True)#绘制直方图
#plt.hist(data['Systolic blood pressure'],bins=12)
plt.title('患者肌酸激酶同工酶水平')
plt.xlabel('肌酸激酶同工酶')
plt.ylabel('人数')
plt.grid(axis='y',linestyle='--')

#8.患者肌钙蛋白水平
plt.subplot(3,3,8)
data['Troponin'].plot(kind='kde',color='red',label='核密度图')#绘制核密度图
data['Troponin'].plot(kind='hist',bins=12,secondary_y=True)#绘制直方图
plt.title('患者肌钙蛋白水平')
plt.xlabel('肌钙蛋白')
plt.ylabel('人数')
plt.grid(axis='y',linestyle='--')

#9.患者患心脏病比例(饼图)
Result_counts=data['Result'].value_counts() #数据统计
plt.subplot(3,3,9)
plt.pie(Result_counts,labels=['患病','未患病'],autopct="%1.1f%%",startangle=75,colors=['#66b3ff','#ff9999'])
plt.title('患者患心脏病比例')
plt.axis('equal') #使得饼图为圆形
(-1.1122316573963205,
 1.1162921653423643,
 -1.1047385236129639,
 1.1118761098947136)

在这里插入图片描述

患者基础人口特征:\ 性别分布:男性(1)共870人,女性(0)共449人\ 年龄分布:年龄范围14-103岁,平均56.2岁,集中在55-65岁区间

患者生理指标:\ 心率:平均76次/分钟,主要分布在65-85次/分钟\ 血压:收缩压平均127.3mmHg,舒张压平均72.1mmHg\ 血糖:平均146.8mg/dl,呈右偏分布\

患者心脏指标:\ CK-MB(肌酸激酶同工酶):平均15.3,大多数患者值较低\ 肌钙蛋白:平均0.36,大部分患者值集中在较低区间\

诊断结果:\ 阳性(positive)患者807例,阴性(negative)患者507例

心脏病影响因素分析

#可视化分析
fig=plt.figure(figsize=(20,16))
plt.subplots_adjust(wspace=0.3,hspace=0.4)#调整子图的间隔

#1.年龄与诊断结果关系
ax1=plt.subplot(3,3,1)
positive_ages = data[data['Result'] == 'positive']['Age']
negative_ages = data[data['Result'] == 'negative']['Age']
plt.boxplot([positive_ages, negative_ages], labels=['Positive', 'Negative'])
#data.boxplot(column='Age',by='Result',ax=ax1)#这种API需要data为DF格式,通过指定列column和by分组
plt.title('年龄与心脏病诊断结果的关系')
plt.xlabel('诊断结果')
plt.ylabel('年龄')

# 2.性别与诊断结果关系
positive_data=data.loc[data['Result']=='positive']
gender_positive_counts=positive_data['Gender'].value_counts()

plt.subplot(3, 3, 2)
plt.pie(gender_positive_counts,labels=['男','女'],autopct="%1.1f%%",startangle=75,colors=['#66b3ff','#ff9999'])
plt.title('性别与心脏病诊断结果的关系')


# 3. 心率与诊断结果关系
ax3 = plt.subplot(3, 3, 3)
positive_Heartrate = data[data['Result'] == 'positive']['Heart rate']
negative_Heartrate = data[data['Result'] == 'negative']['Heart rate']
plt.boxplot([positive_Heartrate, negative_Heartrate], labels=['Positive', 'Negative'])
ax3.set_title('心率与心脏病诊断结果的关系', fontsize=14)
ax3.set_xlabel('诊断结果', fontsize=12)
ax3.set_ylabel('心率 (次/分钟)', fontsize=12)

# 4. 收缩压与诊断结果关系
ax4 = plt.subplot(3, 3, 4)
data.boxplot(column='Systolic blood pressure',by='Result',ax=ax4)
ax4.set_title('收缩压与心脏病诊断结果的关系', fontsize=14)
ax4.set_xlabel('诊断结果', fontsize=12)
ax4.set_ylabel('收缩压 (mmHg)', fontsize=12)

# 5. 舒张压与诊断结果关系
ax5 = plt.subplot(3, 3, 5)
data.boxplot(column='Diastolic blood pressure',by='Result',ax=ax5)
ax5.set_title('舒张压与心脏病诊断结果的关系', fontsize=14)
ax5.set_xlabel('诊断结果', fontsize=12)
ax5.set_ylabel('舒张压 (mmHg)', fontsize=12)

# 6. 血糖与诊断结果关系
ax6 = plt.subplot(3, 3, 6)
data.boxplot(column='Blood sugar',by='Result',ax=ax6)
ax6.set_title('血糖与心脏病诊断结果的关系', fontsize=14)
ax6.set_xlabel('诊断结果', fontsize=12)
ax6.set_ylabel('血糖 (mg/dl)', fontsize=12)

# 7. CK-MB与诊断结果关系
ax7 = plt.subplot(3, 3, 7)
positive_CKMB = data[data['Result'] == 'positive']['CK-MB']
negative_CKMB = data[data['Result'] == 'negative']['CK-MB']
plt.boxplot([positive_CKMB, negative_CKMB], labels=['Positive', 'Negative'])
#data.boxplot(column='CK-MB',by='Result',ax=ax7)
ax7.set_title('CK-MB与心脏病诊断结果的关系', fontsize=14)
ax7.set_xlabel('诊断结果', fontsize=12)
ax7.set_ylabel('CK-MB', fontsize=12)

# 8. 肌钙蛋白与诊断结果关系
ax8 = plt.subplot(3, 3, 8)
data.boxplot(column='Troponin',by='Result',ax=ax8)
ax8.set_title('肌钙蛋白与心脏病诊断结果的关系', fontsize=14)
ax8.set_xlabel('诊断结果', fontsize=12)
ax8.set_ylabel('肌钙蛋白', fontsize=12)

plt.tight_layout()
plt.show()

在这里插入图片描述

心脏病患者的肌酸激酶同工酶和肌钙蛋白显著高于未患心脏病的人,而且高龄可能也会导致心脏病,男性患心脏病的比例高于女性,其他特征差异不是非常明显。

分类模型构建与预测

为了预测心脏病的发生,我们构建了多种机器学习模型,包括逻辑回归、决策树、随机森林、XGBoost和支持向量机(SVM)。我们使用了标准化后的数据进行训练,并通过划分训练集和测试集来评估模型的性能。

数据预处理

将目标变量转为01变量,并且把连续变量进行标准化处理,由于目标变量的不平衡程度相对温和(阳性61.4%,阴性38.6%),未达到需要强制平衡的临界水平(差异在60%以上才考虑平衡样本),将处理后的数据进行划分。

# 将"positive"/"negative"转换为1/0
data['Result_Binary'] = (data['Result'] == 'positive').astype(int)
features = ['Gender','Age', 'CK-MB', 'Troponin']
# 定义特征和目标变量
x = data[features].copy()
y = data['Result_Binary']

# 只对连续变量标准化
continuous_vars = ['Age', 'CK-MB', 'Troponin']
# 先把连续变量列转为float类型
x[continuous_vars] = x[continuous_vars].astype(float)
scaler = StandardScaler()#创建标准化模型
x.loc[:, continuous_vars] = scaler.fit_transform(x[continuous_vars])#进行标准化处理
# 划分训练集和测试集(80%/20%)
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=15, stratify=y)

逻辑回归模型

逻辑回归是一种广泛应用于二分类问题的线性模型。在本次分析中,逻辑回归模型的平均准确率为83%,召回率为83%,F1分数为83%。虽然逻辑回归模型的性能相对较好,但在处理复杂的非线性关系时可能不够灵活。

模型预测基本步骤:\ 1.创建模型:模型= 模型函数(参数配置)\ 2.训练模型:模型.fit(x训练集,y训练集)\ 3.模型预测:模型.predict(x测试集)\ 4.模型评估:\ 模型报告:classification_report(y_test, y_pred)\ 混淆矩阵:confusion_matrix(y_test, y_pred))\ ROC曲线:roc_curve(y_test, y_prob)\

由于所有模型的训练、预测、评估都是一样的,古可以定义成一个函数

def evaluate_model(model, model_name, X_train, X_test, y_train, y_test):  #模型对象、模型名称,x训练集,y训练集,x测试集
    # 训练模型
    model.fit(X_train, y_train)
    
    # 预测
    y_pred = model.predict(X_test)
    
    # 打印分类报告
    print(f"=== {model_name} 模型评估 ===")
    print(classification_report(y_test, y_pred))#包含准确率、召回率、f1测度、支持率
    
    # 绘制混淆矩阵
    cm = confusion_matrix(y_test, y_pred) #返回混淆矩阵
    plt.figure(figsize=(8, 6))
    plt.imshow(cm,cmap='hot',interpolation='nearest')#热力图
    plt.colorbar()#视觉映射
    #添加标签
    for i in range(2):
        for j in range(2):
            plt.text(j, i, f'{cm[i, j]:.2f}', ha='center', va='center', color='black')
    plt.title(f'{model_name}模型混淆矩阵')
    plt.show()
    
    # 绘制ROC曲线
    if hasattr(model, "predict_proba"):#hasattr用于检查对象是否具有指定的属性
        y_prob = model.predict_proba(X_test)[:, 1]
    else:  # 对于SVM等没有predict_proba方法的模型
        y_prob = model.decision_function(X_test) if hasattr(model, "decision_function") else y_pred
    
    fpr, tpr, _ = roc_curve(y_test, y_prob)  #roc曲线
    roc_auc = auc(fpr, tpr) #auc面积
    
    plt.figure(figsize=(8, 6))
    plt.plot(fpr, tpr, color='darkorange', lw=2, label='ROC曲线(面积 = %0.2f)' % roc_auc)
    plt.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--')
    plt.xlabel('假阳率')
    plt.ylabel('真阳率')
    plt.title(f'{model_name}模型ROC曲线')
    plt.legend(loc="lower right")
    plt.show()
    
    return model, roc_auc
#逻辑回归模型
lr_model = LogisticRegression(random_state=15, class_weight='balanced')
lr_model, lr_auc = evaluate_model(lr_model, "逻辑回归", x_train, x_test, y_train, y_test)
=== 逻辑回归 模型评估 ===
             precision    recall  f1-score   support

          0       0.70      0.97      0.81       101
          1       0.98      0.73      0.84       162

avg / total       0.87      0.83      0.83       263

在这里插入图片描述

在这里插入图片描述

决策树模型

决策树模型通过构建树状结构来预测目标变量。在本次分析中,决策树模型的平均准确率为98%,召回率为98%,F1分数为98%。决策树模型的优点是易于理解和解释,但可能会出现过拟合的问题。

dt_model = DecisionTreeClassifier(random_state=15, class_weight='balanced')
dt_model, dt_auc = evaluate_model(dt_model, "决策树", x_train, x_test, y_train, y_test)

=== 决策树 模型评估 ===
             precision    recall  f1-score   support

          0       0.98      0.98      0.98       101
          1       0.99      0.99      0.99       162

avg / total       0.98      0.98      0.98       263

在这里插入图片描述

在这里插入图片描述

随机森林模型

随机森林是一种基于决策树的集成学习方法,通过构建多个决策树并结合它们的预测结果来提高模型的性能。在本次分析中,随机森林模型的平均准确率为97%,召回率为97%,F1分数为97%。随机森林模型在处理复杂的非线性关系时表现优异,且具有较强的抗过拟合能力。

rf_model = RandomForestClassifier(random_state=15, class_weight='balanced')
rf_model, rf_auc = evaluate_model(rf_model, "随机森林", x_train, x_test, y_train, y_test)
=== 随机森林 模型评估 ===
             precision    recall  f1-score   support

          0       0.95      0.98      0.97       101
          1       0.99      0.97      0.98       162

avg / total       0.97      0.97      0.97       263

在这里插入图片描述

在这里插入图片描述

XGBoost 模型

XGBoost是一种高效的梯度提升算法,广泛应用于各种机器学习竞赛中。在本次分析中,XGBoost模型的平均准确率为97%,召回率为97%,F1分数为97%。XGBoost模型在处理大规模数据集时表现出色,且具有自动处理缺失值和特征选择的能力。

xgb_model = XGBClassifier(random_state=15, scale_pos_weight=sum(y_train==0)/sum(y_train==1))
xgb_model, xgb_auc = evaluate_model(xgb_model, "XGBoost", x_train, x_test, y_train, y_test)

支持向量机SVM模型

支持向量机是一种基于最大间隔原理的分类模型,适用于高维数据集。在本次分析中,SVM模型的平均准确率为77%,召回率为76%,F1分数为77%。SVM模型在处理线性可分数据时表现良好,但在处理非线性数据时可能需要使用核技巧。

svm_model = SVC(random_state=15, class_weight='balanced', probability=True)
svm_model, svm_auc = evaluate_model(svm_model, "支持向量机", x_train, x_test, y_train, y_test)
=== 支持向量机 模型评估 ===
             precision    recall  f1-score   support

          0       0.63      0.93      0.75       101
          1       0.94      0.66      0.78       162

avg / total       0.82      0.76      0.77       263

在这里插入图片描述

在这里插入图片描述

树模型(决策树模型、随机森林模型、XGBoost模型)明显优于逻辑回归模型和SVM模型。

结论

本项目基于对心脏病发作数据集的系统分析,得出以下主要结论:

人群特征:患者年龄分布广泛,平均年龄56.2岁,男性比例高于女性,且男性患心脏病的风险显著高于女性。\ 生理与生化指标:心脏病患者的肌酸激酶同工酶(CK-MB)和肌钙蛋白(Troponin)水平均显著高于未患病者,年龄也是影响心脏病的重要因素。心率、血压、血糖等常规生理指标在两组间差异不显著。

模型预测:多种机器学习模型均能较好地区分心脏病患者与非患者,其中树模型(决策树、随机森林、XGBoost)表现优于逻辑回归和SVM,具有较高的预测准确性。\ 实际意义:本研究结果为心脏病的早期筛查和风险评估提供了数据支持,提示临床在关注传统生理指标的同时,应重视心脏生化指标的检测与分析。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐