当前位置: 首页 > news >正文

基于统计检验与多模型对心脏病数据的分析与预测

1.项目背

随着社会健康意识的不断提升,心血管疾病的预防与早期诊断逐渐成为医学研究和公共卫生领域关注的重点,脏病作为威胁人类健康的主要慢性疾病之一,其发作的危险因素和临床特征备受关注,通过对心脏病发作相关数据的系统分析,有助于揭示不同人群的患病特征,还能为疾病的早期筛查和精准干预提供数据支持,该项目以心脏病发作数据为基础,围绕患者的基础人口学特征、生理指标及心脏生化指标,开展描述性统计、影响因素分析及多种机器学习模型的预测研究,旨在为心脏病的风险评估和临床决策提供科学依据。

2.数据说明

字段说明
Age患者年龄
Gender性别(1=男性,0=女性)
Heart rate心率(每分钟心跳次数)
Systolic blood pressure收缩压(毫米汞柱)
Diastolic blood pressure舒张压(毫米汞柱)
Blood sugar血糖水平(毫克/分升)
CK-MB肌酸激酶同工酶水平(心肌损伤标志物)
Troponin肌钙蛋白水平(心肌损伤特异性标志物)
Result诊断结果(positive=患有心脏病,negative=未患心脏病)

3.Python库导入及数据读取

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
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
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
data = pd.read_csv('D:/Desktop/商业数据分析案例/心脏病发作数据集/Medicaldataset.csv')

4.数据预览及预处理

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
print('查看数据信息:')
data.info()
查看数据信息:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1319 entries, 0 to 1318
Data columns (total 9 columns):#   Column                    Non-Null Count  Dtype  
---  ------                    --------------  -----  0   Age                       1319 non-null   int64  1   Gender                    1319 non-null   int64  2   Heart rate                1319 non-null   int64  3   Systolic blood pressure   1319 non-null   int64  4   Diastolic blood pressure  1319 non-null   int64  5   Blood sugar               1319 non-null   float646   CK-MB                     1319 non-null   float647   Troponin                  1319 non-null   float648   Result                    1319 non-null   object 
dtypes: float64(3), int64(5), object(1)
memory usage: 92.9+ KB
print(f'查看重复值:{data.duplicated().sum()}')
查看重复值:0
characteristic = ['Gender','Result']
print('数据中性别和诊断结果的唯一值情况:')
for i in characteristic:print(f'{i}:')print(f'共有:{len(data[i].unique())}条唯一值')print(data[i].unique())print('-'*50)
数据中性别和诊断结果的唯一值情况:
Gender:
共有:2条唯一值
[1 0]
--------------------------------------------------
Result:
共有:2条唯一值
['negative' 'positive']
--------------------------------------------------

positive表示阳性,也就是患有心脏病;negative表示阴性,也就是未患心脏病的。

# 为数据集特征创建映射字典
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)sns.boxplot(y=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的异常值
heart_rate_outliers = data[data['Heart rate'] > 1000]
print(f"心率 > 1000 的异常值数量: {len(heart_rate_outliers)}")
data = data[data['Heart rate'] <= 1000]# 删除收缩压小于50的异常值
systolic_outliers = data[data['Systolic blood pressure'] < 50]
print(f"收缩压 < 50 的异常值数量: {len(systolic_outliers)}")
data = data[data['Systolic blood pressure'] >= 50]# 删除舒张压大于140的异常值
diastolic_outliers = data[data['Diastolic blood pressure'] > 140]
print(f"舒张压 > 140 的异常值数量: {len(diastolic_outliers)}")
data = data[data['Diastolic blood pressure'] <= 140]print(f"处理后数据量: {len(data)}")
处理前数据量: 1319
心率 > 1000 的异常值数量: 3
收缩压 < 50 的异常值数量: 1
舒张压 > 140 的异常值数量: 1
处理后数据量: 1314

检查舒张压和收缩压的关系,看看是否存在舒张压大于收缩压的情况。

# 检查舒张压大于收缩压的情况
bp_anomalies = data[data['Diastolic blood pressure'] > data['Systolic blood pressure']]
print(f"发现舒张压大于收缩压的记录数: {len(bp_anomalies)}")
发现舒张压大于收缩压的记录数: 6

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

# 获取这些记录的索引
anomaly_indices = bp_anomalies.index# 调换这些记录的收缩压和舒张压值
data.loc[anomaly_indices, ['Systolic blood pressure', 'Diastolic blood pressure']] = data.loc[anomaly_indices, ['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)sns.boxplot(y=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()

在这里插入图片描述

处理后的数据,不存在明显异常值,有些特征存在离群点,但是考虑可能是真实值,因为心脏病或者高血压、高血糖导致的,处理后的数据有1314条。

5.描述性分析

data.describe(include='all')
AgeGenderHeart rateSystolic blood pressureDiastolic blood pressureBlood sugarCK-MBTroponinResult
count1314.0000001314.0000001314.0000001314.0000001314.0000001314.0000001314.0000001314.0000001314
uniqueNaNNaNNaNNaNNaNNaNNaNNaN2
topNaNNaNNaNNaNNaNNaNNaNNaNpositive
freqNaNNaNNaNNaNNaNNaNNaNNaN807
mean56.1986300.65981775.987823127.31659172.087519146.79048715.3228770.361174NaN
std13.6455830.47395115.28689325.88183213.79799175.01365946.4084361.156510NaN
min14.0000000.00000020.00000065.00000038.00000035.0000000.3210000.001000NaN
25%47.0000000.00000064.000000110.00000062.00000098.0000001.6600000.006000NaN
50%58.0000001.00000074.000000124.00000071.000000116.0000002.8500000.014000NaN
75%65.0000001.00000085.000000143.00000080.000000170.0000005.8175000.085000NaN
max103.0000001.000000135.000000223.000000128.000000541.000000300.00000010.300000NaN
plt.figure(figsize=(20,15))
plt.subplot(3, 3, 1)
sns.histplot(data['Age'], bins=12, kde=True)
plt.title('患者年龄分布')
plt.xlabel('年龄 (岁)')
plt.ylabel('人数')
plt.grid(axis='y')gender_counts = data['Gender'].value_counts()
plt.subplot(3, 3, 2)
plt.pie(gender_counts, labels=gender_counts.index, autopct='%1.1f%%', startangle=90, colors=['#66b3ff','#ff9999'])
plt.title('患者性别比例')
plt.axis('equal')  # 使饼图为圆形plt.subplot(3, 3, 3)
sns.histplot(data['Heart rate'], bins=12, kde=True)
plt.title('患者心率分布')
plt.xlabel('心率(次/分钟)')
plt.ylabel('人数')
plt.grid(axis='y')plt.subplot(3, 3, 4)
sns.histplot(data['Systolic blood pressure'], bins=12, kde=True)
plt.title('患者收缩压分布')
plt.xlabel('收缩压(mmHg)')
plt.ylabel('人数')
plt.grid(axis='y')plt.subplot(3, 3, 5)
sns.histplot(data['Diastolic blood pressure'], bins=12, kde=True)
plt.title('患者舒张压分布')
plt.xlabel('舒张压(mmHg)')
plt.ylabel('人数')
plt.grid(axis='y')plt.subplot(3, 3, 6)
sns.histplot(data['Blood sugar'], bins=12, kde=True)
plt.title('患者血糖水平分布')
plt.xlabel('血糖水平(mg/dl)')
plt.ylabel('人数')
plt.grid(axis='y')plt.subplot(3, 3, 7)
sns.histplot(data['CK-MB'], bins=12, kde=True)
plt.title('患者血肌酸激酶同工酶水平分布')
plt.xlabel('肌酸激酶同工酶水平')
plt.ylabel('人数')
plt.grid(axis='y')plt.subplot(3, 3, 8)
sns.histplot(data['Troponin'], bins=12, kde=True)
plt.title('患者肌钙蛋白水平分布')
plt.xlabel('肌钙蛋白水平')
plt.ylabel('人数')
plt.grid(axis='y')result_counts = data['Result'].value_counts()
plt.subplot(3, 3, 9)
plt.pie(result_counts, labels=result_counts.index, autopct='%1.1f%%', startangle=90)
plt.title('患者患心脏病比例')
plt.axis('equal')plt.tight_layout()
plt.show()

在这里插入图片描述

患者基础人口特征

  • 性别分布:男性(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例

6.心脏病影响因素分析

6.1可视化分析

# 创建一个3x3的画布
fig = plt.figure(figsize=(20, 16))
plt.subplots_adjust(wspace=0.3, hspace=0.4)# 1. 年龄与诊断结果关系
ax1 = plt.subplot(3, 3, 1)
sns.boxplot(x='Result', y='Age', data=data, hue='Result', legend=False)
ax1.set_title('年龄与心脏病诊断结果的关系', fontsize=14)
ax1.set_xlabel('诊断结果', fontsize=12)
ax1.set_ylabel('年龄', fontsize=12)# 2.性别与诊断结果关系
gender_and_result = pd.crosstab(data['Gender'], data['Result'])
gender_and_result_percent = gender_and_result.div(gender_and_result.sum(axis=1), axis=0) * 100ax2 = plt.subplot(3, 3, 2)
gender_and_result_percent['positive'].plot(kind='bar',ax=ax2)
plt.title('性别与心脏病诊断结果的关系')
plt.xlabel('性别(0=女性,1=男性)')
plt.ylabel('患心脏病比例')
plt.xticks(rotation=0)
# 添加数据标签
for p in ax2.patches:ax2.annotate(f"{p.get_height():.1f}%", (p.get_x() + p.get_width() / 2., p.get_height()),ha='center', va='center', xytext=(0, 10), textcoords='offset points')# 3. 心率与诊断结果关系
ax3 = plt.subplot(3, 3, 3)
sns.boxplot(x='Result', y='Heart rate', data=data, hue='Result', legend=False)
ax3.set_title('心率与心脏病诊断结果的关系', fontsize=14)
ax3.set_xlabel('诊断结果', fontsize=12)
ax3.set_ylabel('心率 (次/分钟)', fontsize=12)# 4. 收缩压与诊断结果关系
ax4 = plt.subplot(3, 3, 4)
sns.boxplot(x='Result', y='Systolic blood pressure', data=data, hue='Result', legend=False)
ax4.set_title('收缩压与心脏病诊断结果的关系', fontsize=14)
ax4.set_xlabel('诊断结果', fontsize=12)
ax4.set_ylabel('收缩压 (mmHg)', fontsize=12)# 5. 舒张压与诊断结果关系
ax5 = plt.subplot(3, 3, 5)
sns.boxplot(x='Result', y='Diastolic blood pressure', data=data, hue='Result', legend=False)
ax5.set_title('舒张压与心脏病诊断结果的关系', fontsize=14)
ax5.set_xlabel('诊断结果', fontsize=12)
ax5.set_ylabel('舒张压 (mmHg)', fontsize=12)# 6. 血糖与诊断结果关系
ax6 = plt.subplot(3, 3, 6)
sns.boxplot(x='Result', y='Blood sugar', data=data, hue='Result', legend=False)
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)
sns.boxplot(x='Result', y='CK-MB', data=data, hue='Result', legend=False)
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)
sns.boxplot(x='Result', y='Troponin', data=data, hue='Result', legend=False)
ax8.set_title('肌钙蛋白与心脏病诊断结果的关系', fontsize=14)
ax8.set_xlabel('诊断结果', fontsize=12)
ax8.set_ylabel('肌钙蛋白', fontsize=12)plt.tight_layout()
plt.show()

在这里插入图片描述

尴尬了,发现存在异常值点,之前没有进行分类识别,反而在研究影响因素的时候发现,如果有读者想要做期末论文的话,可以把这个图绘制作为数据预处理那部分。

# 找出异常点并显示
outlier = data[(data['Result'] == 'negative') & (data['Troponin'] > 9)]
print("找到的异常点信息:")
outlier
找到的异常点信息:
AgeGenderHeart rateSystolic blood pressureDiastolic blood pressureBlood sugarCK-MBTroponinResult
296316613555166.00.49310.0negative
# 删除异常点
data = data.drop(outlier.index)
# 创建一个3x3的画布
fig = plt.figure(figsize=(20, 16))
plt.subplots_adjust(wspace=0.3, hspace=0.4)# 1. 年龄与诊断结果关系
ax1 = plt.subplot(3, 3, 1)
sns.boxplot(x='Result', y='Age', data=data, hue='Result', legend=False)
ax1.set_title('年龄与心脏病诊断结果的关系', fontsize=14)
ax1.set_xlabel('诊断结果', fontsize=12)
ax1.set_ylabel('年龄', fontsize=12)# 2.性别与诊断结果关系
gender_and_result = pd.crosstab(data['Gender'], data['Result'])
gender_and_result_percent = gender_and_result.div(gender_and_result.sum(axis=1), axis=0) * 100ax2 = plt.subplot(3, 3, 2)
gender_and_result_percent['positive'].plot(kind='bar',ax=ax2)
plt.title('性别与心脏病诊断结果的关系')
plt.xlabel('性别(0=女性,1=男性)')
plt.ylabel('患心脏病比例')
plt.xticks(rotation=0)
# 添加数据标签
for p in ax2.patches:ax2.annotate(f"{p.get_height():.1f}%", (p.get_x() + p.get_width() / 2., p.get_height()),ha='center', va='center', xytext=(0, 10), textcoords='offset points')# 3. 心率与诊断结果关系
ax3 = plt.subplot(3, 3, 3)
sns.boxplot(x='Result', y='Heart rate', data=data, hue='Result', legend=False)
ax3.set_title('心率与心脏病诊断结果的关系', fontsize=14)
ax3.set_xlabel('诊断结果', fontsize=12)
ax3.set_ylabel('心率 (次/分钟)', fontsize=12)# 4. 收缩压与诊断结果关系
ax4 = plt.subplot(3, 3, 4)
sns.boxplot(x='Result', y='Systolic blood pressure', data=data, hue='Result', legend=False)
ax4.set_title('收缩压与心脏病诊断结果的关系', fontsize=14)
ax4.set_xlabel('诊断结果', fontsize=12)
ax4.set_ylabel('收缩压 (mmHg)', fontsize=12)# 5. 舒张压与诊断结果关系
ax5 = plt.subplot(3, 3, 5)
sns.boxplot(x='Result', y='Diastolic blood pressure', data=data, hue='Result', legend=False)
ax5.set_title('舒张压与心脏病诊断结果的关系', fontsize=14)
ax5.set_xlabel('诊断结果', fontsize=12)
ax5.set_ylabel('舒张压 (mmHg)', fontsize=12)# 6. 血糖与诊断结果关系
ax6 = plt.subplot(3, 3, 6)
sns.boxplot(x='Result', y='Blood sugar', data=data, hue='Result', legend=False)
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)
sns.boxplot(x='Result', y='CK-MB', data=data, hue='Result', legend=False)
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)
sns.boxplot(x='Result', y='Troponin', data=data, hue='Result', legend=False)
ax8.set_title('肌钙蛋白与心脏病诊断结果的关系', fontsize=14)
ax8.set_xlabel('诊断结果', fontsize=12)
ax8.set_ylabel('肌钙蛋白', fontsize=12)plt.tight_layout()
plt.show()

在这里插入图片描述

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

6.2统计检验

针对连续变量打算使用t检验,但是要先检查数据是否符合正态分布,使用Shapiro-Wilk检验分组后的数据是否符合正态分布。

# 定义连续变量
continuous_vars = ['Age', 'Heart rate', 'Systolic blood pressure', 'Diastolic blood pressure', 'Blood sugar', 'CK-MB', 'Troponin']normality_results = []# 对每个连续变量执行正态性检验
for var in continuous_vars:# 分组pos_data = data[data['Result'] == 'positive'][var]neg_data = data[data['Result'] == 'negative'][var]# Shapiro-Wilk检验_, p_pos = stats.shapiro(pos_data)_, p_neg = stats.shapiro(neg_data)# 存储结果normality_results.append({'变量': var,'阳性组p值': p_pos,'阳性组是否正态': p_pos > 0.05,'阴性组p值': p_neg,'阴性组是否正态': p_neg > 0.05})normality_df = pd.DataFrame(normality_results)
normality_df
变量阳性组p值阳性组是否正态阴性组p值阴性组是否正态
0Age3.917489e-06False1.488196e-02False
1Heart rate3.347959e-16False6.574077e-10False
2Systolic blood pressure2.096096e-15False7.600377e-06False
3Diastolic blood pressure1.480756e-05False8.716979e-05False
4Blood sugar3.739613e-31False2.309155e-25False
5CK-MB4.095379e-45False1.400158e-12False
6Troponin2.605650e-44False4.826355e-19False

所有的p值均小于0.05,因此认为都不符合正态分布,所以使用非参数检验的方法——Mann-Whitney U检验。

results = []# 对每个连续变量执行Mann-Whitney U检验
for var in continuous_vars:pos_data = data[data['Result'] == 'positive'][var]neg_data = data[data['Result'] == 'negative'][var]# Mann-Whitney U检验stat, p_value = stats.mannwhitneyu(pos_data, neg_data, alternative='two-sided')# 计算效应值 - Rank-biserial correlation (非参数效应值)n1, n2 = len(pos_data), len(neg_data)effect_size = 1 - (2 * stat) / (n1 * n2)# 获取描述性统计量pos_median = pos_data.median()neg_median = neg_data.median()# 存储结果results.append({'变量': var,'阳性组中位数': round(pos_median, 4),'阴性组中位数': round(neg_median, 4),'差值(阳性-阴性)': round(pos_median - neg_median, 4),'U统计量': stat,'p值': p_value,'效应值': round(effect_size, 4),'是否显著': '是' if p_value < 0.05 else '否','显著性': '***' if p_value < 0.001 else ('**' if p_value < 0.01 else ('*' if p_value < 0.05 else 'ns'))})mw_results_df = pd.DataFrame(results)
mw_results_df
变量阳性组中位数阴性组中位数差值(阳性-阴性)U统计量p值效应值是否显著显著性
0Age60.00052.0008.000261119.01.568374e-17-0.2789***
1Heart rate74.00075.000-1.000204076.09.887191e-010.0005ns
2Systolic blood pressure122.000125.000-3.000195096.01.746887e-010.0444ns
3Diastolic blood pressure71.00072.000-1.000202784.58.357336e-010.0068ns
4Blood sugar116.000117.000-1.000199686.05.024113e-010.0220ns
5CK-MB3.7702.3101.460277479.55.747543e-28-0.3591***
6Troponin0.0440.0060.038365936.59.429489e-130-0.7923***

解释一下效应值,效应值是量化两组间差异大小的统计指标,与p值不同,它不受样本量影响,能更客观地评估差异的实际意义,范围:-1至1之间,绝对值越大,说明影响越大,正负表示差异方向。

肌钙蛋白水平、肌酸激酶同工酶水平、年龄都是影响心脏病的重要因素,其中肌钙蛋白水平效应值的绝对值最大,是区分患者是否患心脏病的最强指标,心率收缩压舒张压血糖在两组间无统计学显著差异,表明这些生理指标在本研究中与心脏病诊断结果关联较弱。

# 对性别变量进行卡方检验
contingency_table = pd.crosstab(data['Gender'], data['Result'])print("性别与心脏病结果的列联表:")
contingency_table
性别与心脏病结果的列联表:
Resultnegativepositive
Gender
0201246
1305561
chi2_results = []
# 进行卡方检验
chi2, p_value, dof, expected = chi2_contingency(contingency_table)# 计算Cramer's V效应值
n = contingency_table.sum().sum()
phi2 = chi2/n
r, k = contingency_table.shape
cramer_v = np.sqrt(phi2 / min(k-1, r-1))# 计算各组百分比
total_by_gender = contingency_table.sum(axis=1)
positive_percent = (contingency_table['positive'] / total_by_gender * 100).round(2)
negative_percent = (contingency_table['negative'] / total_by_gender * 100).round(2)chi2_results.append({'变量': 'Gender','卡方值': round(chi2, 4),'p值': p_value,'自由度': dof,"Cramer's V效应值": round(cramer_v, 4),'男性阳性比例%': positive_percent[1],'女性阳性比例%': positive_percent[0],'差值(男性-女性)%': round(positive_percent[1] - positive_percent[0], 2),'是否显著': '是' if p_value < 0.05 else '否','显著性': '***' if p_value < 0.001 else ('**' if p_value < 0.01 else ('*' if p_value < 0.05 else 'ns'))
})chi2_df = pd.DataFrame(chi2_results)
print("卡方检验结果:")
chi2_df
卡方检验结果:
变量卡方值p值自由度Cramer's V效应值男性阳性比例%女性阳性比例%差值(男性-女性)%是否显著显著性
0Gender11.41740.00072810.093364.7855.039.75***

男性患心脏病风险更高,男性患者中有64.78%被诊断为阳性,而女性为55.03%,男性高出9.75个百分点,但是效应值比较小,属于小效应大小,这表明虽然性别差异具有统计显著性,但实际影响强度较小。

7.模型构建与预测

7.1数据预处理

将目标变量转为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)

7.2逻辑回归模型

# 创建一个函数来评估模型
def evaluate_model(model, model_name, X_train, X_test, y_train, y_test):# 训练模型model.fit(X_train, y_train)# 预测y_pred = model.predict(X_test)# 打印分类报告print(f"=== {model_name} 模型评估 ===")print(classification_report(y_test, y_pred))# 绘制混淆矩阵cm = confusion_matrix(y_test, y_pred)plt.figure(figsize=(8, 6))sns.heatmap(cm, annot=True, fmt='g', cmap='Blues', xticklabels=['预测值 0', '预测值 1'], yticklabels=['真实值 0', '真实值 1'])plt.title(f'{model_name}模型混淆矩阵')plt.show()# 绘制ROC曲线if hasattr(model, "predict_proba"):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_predfpr, tpr, _ = roc_curve(y_test, y_prob)roc_auc = auc(fpr, tpr)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   support0       0.71      0.90      0.79       1011       0.93      0.77      0.84       162accuracy                           0.82       263macro avg       0.82      0.84      0.82       263
weighted avg       0.84      0.82      0.82       263

在这里插入图片描述

在这里插入图片描述

7.3决策树模型

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   support0       0.97      0.98      0.98       1011       0.99      0.98      0.98       162accuracy                           0.98       263macro avg       0.98      0.98      0.98       263
weighted avg       0.98      0.98      0.98       263

在这里插入图片描述
在这里插入图片描述

7.4随机森林模型

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   support0       0.94      1.00      0.97       1011       1.00      0.96      0.98       162accuracy                           0.97       263macro avg       0.97      0.98      0.97       263
weighted avg       0.98      0.97      0.97       263

在这里插入图片描述
在这里插入图片描述

7.5XGBoost模型

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)
=== XGBoost 模型评估 ===precision    recall  f1-score   support0       0.94      1.00      0.97       1011       1.00      0.96      0.98       162accuracy                           0.98       263macro avg       0.97      0.98      0.98       263
weighted avg       0.98      0.98      0.98       263

在这里插入图片描述

在这里插入图片描述

7.6SVM模型

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   support0       0.66      0.95      0.78       1011       0.96      0.69      0.80       162accuracy                           0.79       263macro avg       0.81      0.82      0.79       263
weighted avg       0.84      0.79      0.79       263

在这里插入图片描述

在这里插入图片描述

每个模型预测效果都不错,但是树模型(决策树模型、随机森林模型、XGBoost模型)明显优于逻辑回归模型和SVM模型。

8. 结论

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

  1. 人群特征:患者年龄分布广泛,平均年龄56.2岁,男性比例高于女性,且男性患心脏病的风险显著高于女性。
  2. 生理与生化指标:心脏病患者的肌酸激酶同工酶(CK-MB)和肌钙蛋白(Troponin)水平均显著高于未患病者,年龄也是影响心脏病的重要因素。心率、血压、血糖等常规生理指标在两组间差异不显著。
  3. 统计检验结果:通过Mann-Whitney U检验和卡方检验,证实肌钙蛋白、肌酸激酶同工酶和年龄为主要影响因素,性别差异虽有统计学意义但效应值较小。
  4. 模型预测:多种机器学习模型均能较好地区分心脏病患者与非患者,其中树模型(决策树、随机森林、XGBoost)表现优于逻辑回归和SVM,具有较高的预测准确性。
  5. 实际意义:本研究结果为心脏病的早期筛查和风险评估提供了数据支持,提示临床在关注传统生理指标的同时,应重视心脏生化指标的检测与分析。
http://www.xdnf.cn/news/543385.html

相关文章:

  • Oracle 11g post PSU Oct18 设置ssl连接(使用wallets)
  • 企业级网络安全护盾:剖析高防IP原理与防护策略
  • 编程学习论坛测试报告
  • 隐形安全感
  • Linux Bash 中 $? 的详细用法
  • 【算法】定长滑动窗口5.20
  • 畅游Diffusion数字人(30):情绪化数字人视频生成
  • MVDR源码(可直接运行)
  • HarmonyOS NEXT~鸿蒙系统与mPaaS三方框架集成指南
  • 单端传输通道也会有奇偶模现象喔
  • PIL库的图像增强函数
  • 从ISO17025合规到信创适配 解密质检lims系统实验室的 AI 质检全链路实践
  • 【C++】C++的拷贝构造函数介绍使用
  • 【RK3588嵌入式图形编程】-Cairo-形状与填充
  • C++从入门到实战(十六)String(中)String的常用接口(构造接口,析构接口,迭代器,遍历修改,容量管理与数据访问)
  • 零基础设计模式——创建型模式 - 单例模式
  • .NET 10 - 尝试一下Minimal Api的Validation新特性
  • 开源一个记账软件,支持docker一键部署
  • APPtrace 智能参数系统:重构 App 用户增长与运营逻辑
  • C++中String类
  • 《经济日报》深度聚焦|珈和科技携手万果博览荟共筑智慧农业新示范高地 全链赋能蒲江茶果产业数字化转型升级
  • 榕壹云上门家政系统:基于Spring Boot+MySQL+UniApp的全能解决方案
  • 深度剖析ZooKeeper
  • 基于大模型与人工智能体的机械臂对话式交互系统RobotAgent
  • 阿里云CDN刷新预热--刷新URL
  • 【AI 大模型】盘古大模型简介 ( 创建空间 | 体验模型 | 部署模型 )
  • 【华为OD-B卷-打印文件 100分(python、java、c++、js、c)】
  • 面试算法刷题3(核心+acm)
  • LVS原理详解及LVS负载均衡工作模式
  • Java的线程池相关的几个问题