基于统计检验与多模型对心脏病数据的分析与预测
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()
Age | Gender | Heart rate | Systolic blood pressure | Diastolic blood pressure | Blood sugar | CK-MB | Troponin | Result | |
---|---|---|---|---|---|---|---|---|---|
0 | 64 | 1 | 66 | 160 | 83 | 160.0 | 1.80 | 0.012 | negative |
1 | 21 | 1 | 94 | 98 | 46 | 296.0 | 6.75 | 1.060 | positive |
2 | 55 | 1 | 64 | 160 | 77 | 270.0 | 1.99 | 0.003 | negative |
3 | 64 | 1 | 70 | 120 | 55 | 270.0 | 13.87 | 0.122 | positive |
4 | 55 | 1 | 64 | 112 | 65 | 300.0 | 1.08 | 0.003 | negative |
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')
Age | Gender | Heart rate | Systolic blood pressure | Diastolic blood pressure | Blood sugar | CK-MB | Troponin | Result | |
---|---|---|---|---|---|---|---|---|---|
count | 1314.000000 | 1314.000000 | 1314.000000 | 1314.000000 | 1314.000000 | 1314.000000 | 1314.000000 | 1314.000000 | 1314 |
unique | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 2 |
top | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | positive |
freq | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 807 |
mean | 56.198630 | 0.659817 | 75.987823 | 127.316591 | 72.087519 | 146.790487 | 15.322877 | 0.361174 | NaN |
std | 13.645583 | 0.473951 | 15.286893 | 25.881832 | 13.797991 | 75.013659 | 46.408436 | 1.156510 | NaN |
min | 14.000000 | 0.000000 | 20.000000 | 65.000000 | 38.000000 | 35.000000 | 0.321000 | 0.001000 | NaN |
25% | 47.000000 | 0.000000 | 64.000000 | 110.000000 | 62.000000 | 98.000000 | 1.660000 | 0.006000 | NaN |
50% | 58.000000 | 1.000000 | 74.000000 | 124.000000 | 71.000000 | 116.000000 | 2.850000 | 0.014000 | NaN |
75% | 65.000000 | 1.000000 | 85.000000 | 143.000000 | 80.000000 | 170.000000 | 5.817500 | 0.085000 | NaN |
max | 103.000000 | 1.000000 | 135.000000 | 223.000000 | 128.000000 | 541.000000 | 300.000000 | 10.300000 | NaN |
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
找到的异常点信息:
Age | Gender | Heart rate | Systolic blood pressure | Diastolic blood pressure | Blood sugar | CK-MB | Troponin | Result | |
---|---|---|---|---|---|---|---|---|---|
29 | 63 | 1 | 66 | 135 | 55 | 166.0 | 0.493 | 10.0 | negative |
# 删除异常点
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值 | 阴性组是否正态 | |
---|---|---|---|---|---|
0 | Age | 3.917489e-06 | False | 1.488196e-02 | False |
1 | Heart rate | 3.347959e-16 | False | 6.574077e-10 | False |
2 | Systolic blood pressure | 2.096096e-15 | False | 7.600377e-06 | False |
3 | Diastolic blood pressure | 1.480756e-05 | False | 8.716979e-05 | False |
4 | Blood sugar | 3.739613e-31 | False | 2.309155e-25 | False |
5 | CK-MB | 4.095379e-45 | False | 1.400158e-12 | False |
6 | Troponin | 2.605650e-44 | False | 4.826355e-19 | False |
所有的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值 | 效应值 | 是否显著 | 显著性 | |
---|---|---|---|---|---|---|---|---|---|
0 | Age | 60.000 | 52.000 | 8.000 | 261119.0 | 1.568374e-17 | -0.2789 | 是 | *** |
1 | Heart rate | 74.000 | 75.000 | -1.000 | 204076.0 | 9.887191e-01 | 0.0005 | 否 | ns |
2 | Systolic blood pressure | 122.000 | 125.000 | -3.000 | 195096.0 | 1.746887e-01 | 0.0444 | 否 | ns |
3 | Diastolic blood pressure | 71.000 | 72.000 | -1.000 | 202784.5 | 8.357336e-01 | 0.0068 | 否 | ns |
4 | Blood sugar | 116.000 | 117.000 | -1.000 | 199686.0 | 5.024113e-01 | 0.0220 | 否 | ns |
5 | CK-MB | 3.770 | 2.310 | 1.460 | 277479.5 | 5.747543e-28 | -0.3591 | 是 | *** |
6 | Troponin | 0.044 | 0.006 | 0.038 | 365936.5 | 9.429489e-130 | -0.7923 | 是 | *** |
解释一下效应值,效应值是量化两组间差异大小的统计指标,与p值不同,它不受样本量影响,能更客观地评估差异的实际意义,范围:-1至1之间,绝对值越大,说明影响越大,正负表示差异方向。
肌钙蛋白水平、肌酸激酶同工酶水平、年龄都是影响心脏病的重要因素,其中肌钙蛋白水平效应值的绝对值最大,是区分患者是否患心脏病的最强指标,心率、收缩压、舒张压和血糖在两组间无统计学显著差异,表明这些生理指标在本研究中与心脏病诊断结果关联较弱。
# 对性别变量进行卡方检验
contingency_table = pd.crosstab(data['Gender'], data['Result'])print("性别与心脏病结果的列联表:")
contingency_table
性别与心脏病结果的列联表:
Result | negative | positive |
---|---|---|
Gender | ||
0 | 201 | 246 |
1 | 305 | 561 |
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效应值 | 男性阳性比例% | 女性阳性比例% | 差值(男性-女性)% | 是否显著 | 显著性 | |
---|---|---|---|---|---|---|---|---|---|---|
0 | Gender | 11.4174 | 0.000728 | 1 | 0.0933 | 64.78 | 55.03 | 9.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. 结论
本项目基于对心脏病发作数据集的系统分析,得出以下主要结论:
- 人群特征:患者年龄分布广泛,平均年龄56.2岁,男性比例高于女性,且男性患心脏病的风险显著高于女性。
- 生理与生化指标:心脏病患者的肌酸激酶同工酶(CK-MB)和肌钙蛋白(Troponin)水平均显著高于未患病者,年龄也是影响心脏病的重要因素。心率、血压、血糖等常规生理指标在两组间差异不显著。
- 统计检验结果:通过Mann-Whitney U检验和卡方检验,证实肌钙蛋白、肌酸激酶同工酶和年龄为主要影响因素,性别差异虽有统计学意义但效应值较小。
- 模型预测:多种机器学习模型均能较好地区分心脏病患者与非患者,其中树模型(决策树、随机森林、XGBoost)表现优于逻辑回归和SVM,具有较高的预测准确性。
- 实际意义:本研究结果为心脏病的早期筛查和风险评估提供了数据支持,提示临床在关注传统生理指标的同时,应重视心脏生化指标的检测与分析。