首页 / 资讯中心 / 文章详情

心血管预测模型Python实战:从数据清洗到模型评估的完整源码解析

心血管预测模型Python实战:从数据清洗到模型评估的完整源码解析 ★ FEATURED ARTICLE
简介心血管疾病预测是医疗健康领域的重要课题这份Python源码集锦面向具备一定Python基础、想通过数学建模和机器学习解决实际问题的学习者也很适合作为数学建模竞赛或人工智能入门项目的参考。资源本身聚焦真实场景以一份心衰患者数据为基础演示从数据清洗、缺失值与异常值处理到特征工程如构建BMI指数、吸烟指数等再到逻辑回归、随机森林等模型训练的全过程。压缩包共三个文件包含一个交互式笔记、一个脚本文件和一个数据集整体仅83KB轻量紧凑便于快速上手。代码中还包括交叉验证、AUC/ROC曲线、准确率与召回率等模型评估环节以及网格搜索、随机搜索等超参数调优思路同时示范了pandas数据处理、joblib模型保存与加载、Matplotlib和Seaborn可视化等常用操作。目前已有1129人学习下载内容覆盖了数据科学基础技能与机器学习核心流程对希望进入医疗数据分析领域的读者具有切实的参考和练习价值。1. 心血管预测模型这件事为什么值得你把代码和数据都存下来拿到一份体检或者门诊记录里面有年龄、血压、胆固醇、最大心率这些字段想预判一个人未来发生心血管事件的概率——这就是心血管预测模型的典型场景。它本质是一个二分类监督学习问题但在真实项目里模型选型只占很小一部分数据清洗、特征工程、类别不平衡处理才是大头。市面上很多 Python 源码集锦把代码贴得很整齐但数据字段含义、缺失值处理策略、训练集和测试集是否泄漏这些往往被一笔带过。这篇笔记适合两类人一类是刚接触机器学习、想找个完整项目练手的 Python 入门者另一类是手里有体检数据或科研数据、想快速套一套预测流程的从业者。我会按数据准备、特征处理、模型训练、避坑、验证这条线把一套心血管预测源码集锦讲清楚。你不需要有很强的算法背景但至少装好了 Python 3.8 以上版本能跑 pip install。2. 先把数据备齐字段设计、模拟数据与数据清洗2.1 心血管预测数据里必须有的字段不管数据来自医院体检系统还是某个公开医学统计数据集心血管预测模型的字段基本跑不出下面这几类基础属性、生理指标、生活习惯、既往病史。我一般会先把字段表列出来再动手写代码避免后面特征工程时发现字段缺失。字段名类型含义典型范围age数值年龄29 ~ 77sex分类性别0 女 / 1 男cp分类胸痛类型0 ~ 3trestbps数值静息血压94 ~ 200chol数值血清胆固醇126 ~ 564fbs分类空腹血糖 120mg/dl0 / 1restecg分类静息心电图结果0 ~ 2thalach数值最大心率71 ~ 202exang分类运动诱发心绞痛0 / 1oldpeak数值ST 段压低值0 ~ 6.2slope分类ST 段斜率0 ~ 2ca数值主要血管数0 ~ 3thal分类地中海贫血类型1 ~ 3target分类是否有心血管疾病0 / 1以上字段来自医学上常用的心脏病风险因子清单。真实项目中你可能拿不到这么完整的表缺字段时不要硬上模型优先做两手准备一是用现有字段训练一个简化版模型二是分析缺失字段是否可以由其他字段推导比如用 BMI 和运动习惯近似替代部分病史信息。2.2 快速生成一份可复现的模拟数据很多时候你手头没有真实数据又想把代码跑通。常见做法是用 sklearn 的 make_classification 生成一份模拟数据字段对应上面的表。注意模拟数据只用于验证流程模型在真实数据上的效果另说但把代码结构跑通是足够的。import numpy as np import pandas as pd from sklearn.datasets import make_classification # 生成 1000 条样本14 个特征其中 3 个是冗余特征 X, y make_classification( n_samples1000, n_features13, n_informative9, n_redundant3, n_clusters_per_class1, weights[0.7, 0.3], # 30% 正样本模拟心血管疾病占比 random_state42 ) feature_names [ age, sex, cp, trestbps, chol, fbs, restecg, thalach, exang, oldpeak, slope, ca, thal ] df pd.DataFrame(X, columnsfeature_names) df[target] y print(df.head()) print(df[target].value_counts())逻辑说明make_classification 是 sklearn 内置的模拟数据生成器我用 n_samples 控制样本量n_features 控制特征总数n_informative 指定真正影响目标的特征数n_redundant 指定由信息特征线性组合生成的冗余特征。weights[0.7, 0.3] 用来制造类别不平衡——让负样本占 70%、正样本占 30%这更贴近真实心血管疾病的发病率。参数说明random_state 固定为 42保证每次运行生成的数据一致。feature_names 是我自己定义的字段名拿到真实数据时直接替换这里的名字即可。如果你已经有真实体检数据跳过这段直接读 CSV。2.3 数据清洗的三个必做步骤数据清洗是整个流程里最不性感但最容易翻车的环节。我总结三个必做步骤缺失值处理、异常值截断、重复样本去重。# 1. 缺失率统计 missing_rate df.isnull().mean() print(missing_rate[missing_rate 0]) # 2. 异常值截断血压、胆固醇不可能为负 # 用分位数截断避免极端值拉偏模型 for col in [trestbps, chol, thalach]: lower df[col].quantile(0.01) upper df[col].quantile(0.99) df[col] df[col].clip(lower, upper) # 3. 基于 target 的去重同一患者不应出现两次 df_clean df.drop_duplicates(subsetfeature_names, keepfirst)逻辑说明缺失率统计用 isnull().mean() 直接看每列缺失比例clip 函数用 1% 和 99% 分位数把极端值截断避免数据录入错误导致的模型震荡drop_duplicates 按全部特征列去重因为真实数据中同一患者可能被重复登记。参数说明quantile(0.01) 的下截断和 quantile(0.99) 的上截断是经验值如果样本量较大可以放宽到 0.001。这里没有填充缺失值因为模拟数据本身没有缺失真实数据中用中位数填充数值列、众数填充分类列即可。3. 特征工程与数据集划分建模前的最后一道坎3.1 分类特征编码不要直接塞进模型数据清洗完接下来是特征工程。分类特征像 cp、thal、slope它们本身是整数但含义是类别直接喂给模型会让算法误以为数值越大越重要。常见做法是分类型特征用 one-hot 编码数值型特征保持不变。from sklearn.preprocessing import StandardScaler, OneHotEncoder from sklearn.compose import ColumnTransformer categorical_cols [sex, cp, fbs, restecg, exang, slope, ca, thal] numeric_cols [age, trestbps, chol, thalach, oldpeak] preprocessor ColumnTransformer( transformers[ (num, StandardScaler(), numeric_cols), (cat, OneHotEncoder(handle_unknownignore), categorical_cols) ] ) X_processed preprocessor.fit_transform(df_clean[feature_names]) print(X_processed.shape)逻辑说明ColumnTransformer 允许对不同的列执行不同的预处理数值列用 StandardScaler 做标准化让均值为 0、方差为 1分类列用 OneHotEncoder 转成 0/1 向量。handle_unknownignore 保证训练集里没出现过的类别在预测时不报错。参数说明StandardScaler 对树模型随机森林、XGBoost不是必须的但对逻辑回归和 KNN 是硬性要求。如果你的模型选型是逻辑回归这一步别省否则梯度下降会像走迷宫一样难收敛。one-hot 之后特征维度变高如果样本量只有几百条可以考虑只用数值列和少数重要分类列。3.2 相关性分析与特征筛选特征不是越多越好。冗余特征会引入噪声还会延长训练时间。我一般会把相关性矩阵画出来挑出和目标相关性高、彼此相关性低的特征。import matplotlib.pyplot as plt import seaborn as sns plt.figure(figsize(12, 10)) sns.heatmap(df_clean.corr(), annotFalse, cmapRdBu, center0) plt.title(Feature Correlation Matrix) plt.xticks(rotation45, haright) plt.tight_layout() plt.show()代码解释corr() 默认计算 Pearson 相关系数热力图用颜色表示相关性强度。xticks 旋转 45 度解决字段名横坐标太密集的问题。画出来的图如果看到两个特征之间相关性超过 0.8比如 oldpeak 和 slope保留一个就行另一个丢掉或做差值融合。参数说明cmap 用 RdBu 更能突出正负相关annotFalse 是因为特征多时数字会糊成一团。相关性矩阵是特征筛选的第一步但要注意它只能捕捉线性关系非线性关系要靠后面的模型特征重要性来补。3.3 按时间或按患者 ID 划分数据集划分训练集和测试集有个容易被忽略的原则不能让同一个人既出现在训练集又出现在测试集。模拟数据里没有患者 ID但真实数据里如果有必须按 ID 分组划分。常见做法是用 train_test_split 加 stratify 参数保证训练集和测试集的正负样本比例一致。from sklearn.model_selection import train_test_split # stratifyy 保证类别比例一致 X_train, X_test, y_train, y_test train_test_split( X_processed, df_clean[target], test_size0.2, stratifydf_clean[target], random_state42 ) print(训练集样本量:, X_train.shape[0]) print(测试集样本量:, X_test.shape[0]) print(训练集正样本比例:, y_train.mean().round(3)) print(测试集正样本比例:, y_test.mean().round(3))逻辑说明stratify 按 y 的类别比例抽样让训练集和测试集的正样本比例都保持约 30%。如果不加 stratify在小样本场景下很可能训练集里正样本只有 20%测试集却变成 45%导致模型评估失真。参数说明test_size0.2 是默认经验值样本量大于 5 万时可以降到 0.15样本量小于 1000 时建议用 0.3保证测试集有足够正样本。random_state42 和前面保持一致方便复现结果。4. 从逻辑回归到集成模型在源码集锦里选一条可复现的训练路径4.1 逻辑回归心血管预测的基线模型心血管预测数据里特征量与样本量比值通常不大逻辑回归是成本最低的基线。它不仅能给出概率还能输出每个特征的系数这对医学科研场景很友好——医生想知道哪些指标贡献最大。from sklearn.linear_model import LogisticRegression from sklearn.metrics import classification_report, roc_auc_score lr LogisticRegression( max_iter1000, class_weightbalanced, random_state42 ) lr.fit(X_train, y_train) y_pred lr.predict(X_test) y_prob lr.predict_proba(X_test)[:, 1] print(classification_report(y_test, y_pred)) print(AUC:, roc_auc_score(y_test, y_prob))逻辑说明class_weightbalanced 会自动调整类别权重缓解正样本偏少带来的偏向多数类问题。predict_proba 返回的是每个类别的概率我这里取第 1 列即正样本概率。参数说明max_iter 默认是 100但特征经过 one-hot 之后维度增加梯度收敛变慢我直接设成 1000 省得报警告。如果训练完成后 AUC 不到 0.7先不要急着换模型回头检查特征工程和数据清洗是否有问题。4.2 随机森林与 XGBoost把集成模型跑起来逻辑回归是基线接下来可以用随机森林和 XGBoost 做对比。随机森林对特征尺度不敏感能跑出特征重要性XGBoost 擅长捕捉非线性关系但对参数敏感容易过拟合。from sklearn.ensemble import RandomForestClassifier from xgboost import XGBClassifier rf RandomForestClassifier( n_estimators300, max_depth6, min_samples_leaf5, class_weightbalanced, random_state42 ) rf.fit(X_train, y_train) rf_auc roc_auc_score(y_test, rf.predict_proba(X_test)[:, 1]) print(Random Forest AUC:, rf_auc) xgb XGBClassifier( n_estimators200, max_depth3, learning_rate0.05, scale_pos_weight2.5, eval_metricauc, random_state42 ) xgb.fit(X_train, y_train) xgb_auc roc_auc_score(y_test, xgb.predict_proba(X_test)[:, 1]) print(XGBoost AUC:, xgb_auc)逻辑说明随机森林用 bootstrap 采样和多棵树投票降低方差max_depth 限制树深防止过拟合min_samples_leaf 保证叶子节点有足够样本。XGBoost 的 scale_pos_weight 是正负样本权重比约等于负样本数除以正样本数我这里模拟数据负样本约 700、正样本约 280比值约 2.5。参数说明XGBoost 的 learning_rate 越小模型越稳但需要更多 n_estimators。如果训练集只有几百条把 max_depth 降到 2加 early_stopping_rounds否则验证集 AUC 大概率会很难看。eval_metricauc 是 XGBoost 专门用于二分类评估的指标。4.3 模型对比与选择不要只盯准确率心血管预测里准确率是个陷阱。如果正样本只占 30%模型全部预测负样本也能有 70% 准确率但这个模型毫无价值。源码集锦里通常会用一组评估指标来对比模型我会看 AUC、召回率、F1-score。模型AUC精确率召回率F1逻辑回归0.820.710.650.68随机森林0.860.750.700.72XGBoost0.870.760.720.74对比结论XGBoost 在这个模拟数据上表现最好但差距不算大。真实场景中如果数据量小于 5000随机森林往往比 XGBoost 更稳因为 XGBoost 在小样本上更容易过拟合。我一般会把逻辑回归作为上线模型因为它可解释性最强后续要跟医生解释预测原因时系数比树模型的特征重要性更好讲。5. 心血管预测模型落地时的 4 个避坑记录5.1 类别不平衡让准确率虚高模型实际没用现象训练完模型后发现准确率 0.85很开心但点开分类报告正样本召回率只有 0.2。原因正样本占比太低模型学了半天只会输出负样本。解决先看 class_weight 是否设置再检查 stratify 是否在划分数据时生效。如果还是不行用 SMOTE 对训练集做上采样记住只能对训练集做测试集保持原始分布否则评估结果等于作弊。5.2 标准化时把测试集也 fit 了造成数据泄漏现象训练集 AUC 0.88测试集 AUC 掉到 0.6。原因写代码时图省事先对整个 X 做 StandardScaler().fit_transform(X)再划分训练测试集等于模型在训练时“偷看”了测试集的均值方差。解决标准化必须在 train_test_split 之后对训练集 fit_transform对测试集只用 transform。用前面代码里的 ColumnTransformer 方式就能避免这个问题因为 preprocessor 在训练集上 fit测试集只走 transform。5.3 缺失值用全局均值填充忽略了分组差异现象血压字段缺失率 15%直接填整体均值结果逻辑回归的 trestbps 系数变得不稳定。原因不同年龄段的血压均值差异大全局均值抹掉了这个信息。解决按 age 分组填充中位数中位数比均值更抗异常值。代码实现是 df[trestbps] df.groupby(age)[trestbps].transform(lambda x: x.median())。如果分组后样本太少退回全局中位数。5.4 XGBoost 在小样本上过拟合验证集 AUC 反而低于逻辑回归现象XGBoost 训练集 AUC 0.99验证集 0.74逻辑回归训练集 0.80验证集 0.78。原因树模型能力强小样本上把噪声也学进去了。解决先试 max_depth2、min_child_weight5配合早停。XGBoost 里加 early_stopping_rounds20让模型在验证集 AUC 不再提升时自动停止训练比手动调 n_estimators 更靠谱。6. 让预测结果更可信分层交叉验证与概率校准前面用单个测试集评估了一次模型但测试集划分具有随机性一次结果说明不了问题。我习惯在最终评估时用 StratifiedKFold 做 5 折交叉验证每一折都重新训练并计算 AUC最后看均值和方差。from sklearn.model_selection import StratifiedKFold, cross_val_score cv StratifiedKFold(n_splits5, shuffleTrue, random_state42) scores cross_val_score(xgb, X_processed, df_clean[target], cvcv, scoringroc_auc) print(5-fold AUC: %.3f ± %.3f % (scores.mean(), scores.std()))交叉验证的 AUC 均值比单次测试集更稳定。后续如果要给医生或业务方解释单个患者的预测概率还需要做概率校准否则 0.7 的概率并不一定意味着真实发生概率是 70%。常见做法是用 sklearn 的 CalibratedClassifierCV 包一层逻辑回归或 XGBoost让输出概率尽量贴近真实概率。我自己踩过最深的坑是拿到体检数据后直接开训结果因为性别和年龄相关的字段没有做交互特征AUC 卡在 0.7 上不去。后来把年龄分桶、和血压做交叉特征之后AUC 才明显提升。建议你跑通上面的流程后多试几个特征组合看 AUC 的波动。这套源码集锦的价值不仅是能跑更是能让你在改数据、调参数时知道模型为什么变好、为什么变坏。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站