Framingham数据集与心血管风险预测建模全流程实战解析

发布时间:2026/8/27 7:14:32
Framingham数据集与心血管风险预测建模全流程实战解析 简介在医学数据挖掘领域心血管风险预测是一项经典监督学习任务其核心在于从结构化体检数据中识别未来冠心病事件的高危人群。Framingham心脏病数据集源于持续数十年的流行病学队列研究包含年龄、血压、胆固醇、吸烟史等15项风险因素及十年冠心病结局标签是入门分类建模与特征工程的理想样本。然而真实数据往往存在缺失值比例偏高、类别分布不平衡正样本仅约15%以及字段间多重共线性等典型问题直接套用默认流程极易导致模型评估失真。从数据质量探查、分组插补、标准化处理到逻辑回归、随机森林与XGBoost的对比实验再到ROC-AUC、PR曲线与概率校准的审慎解读完整建模链条需兼顾业务可解释性与统计严谨性。基于该数据集的风险预测实践不仅可掌握医学数据挖掘的通用方法论更能为临床风险分层与决策支持提供可靠依据。 写这份博文之前我先说下背景。我手头有个需求用经典公共数据集做一次完整的心血管风险预测建模第一反应就是Framingham心脏病数据集。这个数据集在医学数据挖掘领域太有名了但真正动手用起来和网上教程里写得不太一样。数据字段的医学含义、缺失值比例、类别不平衡程度、模型评估的坑都是教程里不细说、但实操里绕不过去的点。这篇文章就围绕我完整跑通这个项目的全过程讲讲数据怎么理解、怎么清洗、怎么建模型以及最后我踩过的那些坑。1. 数据集出身一场持续70年的流行病学追踪为什么会变成机器学习界的常青树Framingham心脏病数据集来源于美国的Framingham Heart StudyFramingham心脏研究这应该是流行病学领域持续时间最长的队列研究之一。研究于1948年启动地点在马萨诸塞州Framingham小镇最初招募了超过5000名30到62岁的健康居民之后又加入了他们的后代和第三代子孙一追就是几十年。这件事对医学界最大的贡献是确立了今天我们耳熟能详的心血管危险因素概念。吸烟、高血压、高胆固醇、糖尿病、肥胖——这些因素和心脏病之间的关联正是通过Framingham研究逐步被量化确认的。后来基于这些长期随访数据研究者还推出了著名的Framingham Risk Score用于估算一个人在10年内发生冠心病事件的概率。这套评分体系至今仍是临床风险评估的参考工具之一。那么它和机器学习有什么关系因为研究本身持续收集了大量纵向数据涵盖生活方式、生理指标、病史等多个维度。数据科学圈的人把它整理成了结构化表格每一行是一位受试者的基线记录每一列是一个风险因素目标变量是未来10年内是否发生冠心病。这就是我们在Kaggle等平台上常见的Framingham数据集。它既有清晰的业务背景又有明确的预测目标特征数量适中、样本量适中非常适合作入门级分类建模、特征工程和模型评估的教学样例。我用的时候注意到网上流传的版本通常是经过二次整理的大概包含4240条样本、15个输入特征和一个二分类标签。相比动辄上百万行的互联网数据集它体量不大但正因为小反而适合做深度分析你可以把每个字段吃透可以把数据可视化做得很细可以手动对比多种模型的效果差异也可以把特征重要性解读到医学意义层面。这也是我这次选择它的核心原因——在工程项目之外偶尔回到一个能完全掌控细节的经典数据集上做练习对建模手感的提升非常有帮助。2. 逐字段拆解15个特征背后的医学含义以及哪些变量最容易在预处理时翻车拿到数据后第一件事不是跑模型而是先把字段清单过一遍理解每一列在临床上代表什么、数据是什么类型、取值范围是否合理、缺失情况如何。这一步做扎实了后续清洗和特征工程才有方向。2.1 人口学与生活方式特征性别、年龄、教育、吸烟male性别二分类0表示女性1表示男性。临床数据里性别几乎总是必须纳入的特征因为男女在心血管疾病发病率、激素水平、症状表现上都有系统性差异。age年龄数值型按岁记录。年龄是心血管疾病的独立危险因素风险随年龄增长显著上升。实际建模时我会留意年龄和心脏病之间是否存在非线性关系比如是否可以用age的平方项或分段编码来捕捉这种趋势。education教育程度分类变量取值为1到4代表不同的教育层级。教育程度本身不直接影响血管但和收入、健康意识、医疗可及性等社会经济因素相关在风险评估里有辅助预测作用。currentSmoker当前是否吸烟二分类0/1。吸烟是明确的危险因素但它只是一个是/否标记丢失了吸烟量信息。cigsPerDay每天吸烟支数数值型。理论上它比currentSmoker携带更多信息可以量化暴露剂量。但它在不吸烟人群中取值为0且和currentSmoker有天然依赖关系做特征工程时要注意多重共线性和缺失值处理。2.2 病史与并发症特征高血压、中风、糖尿病prevalentStroke是否曾患中风二分类0/1。中风和冠心病共享大量动脉粥样硬化危险因素历史上发生过中风的人未来发生冠心病的风险会被拉高。prevalentHyp是否患高血压二分类0/1。这是已确诊高血压标记和后面要提到的连续血压测量值不同它代表的是医生诊断结果。diabetes是否患糖尿病二分类0/1。糖尿病是冠心病的等危症这个变量在临床上权重很高。BPMeds是否服用降压药二分类0/1。服用降压药本身就说明血压有问题同时药物控制状态也可能影响血压测量值所以这个变量需要和血压字段结合着看。2.3 生理测量特征血压、胆固醇、BMI、心率、血糖totChol总胆固醇数值型单位mg/dL。总胆固醇包括LDL、HDL和甘油三酯中的胆固醇部分是经典的风险指标。sysBP收缩压数值型单位mmHg。收缩压是心脏收缩时动脉壁承受的压力年龄增长后收缩压往往会升高。diaBP舒张压数值型单位mmHg。反映心脏舒张期动脉血管的弹性回缩压力。BMI体质指数数值型通常以kg/m^2表示。网上版本的数据直接提供数值没有给身高体重没法自己重算所以要注意单位的统一。heartRate静息心率数值型单位次/分钟。glucose血糖数值型单位mg/dL。空腹血糖或随机血糖的含义不同但整理好的表里通常都归一化处理了。2.4 目标变量TenYearCHDTenYearCHD二分类0/1表示受试者在进入研究后的10年内是否发生了冠状动脉心脏病事件。1代表发生0代表未发生。这就是我们要预测的标签。这15个字段里我自己在清洗时最容易出问题的是两个currentSmoker与cigsPerDay的联动缺失以及血压相关的多重共线性。前者常见的情况是有人在currentSmoker里标记为吸烟但cigsPerDay为空或者反之cigsPerDay不为空但currentSmoker为0。遇到这种不一致需要结合业务逻辑决定是填充还是丢弃。后者则是收缩压、舒张压、是否高血压、是否服用降压药四个字段之间存在信息重叠建模时如果不做处理树模型一般还好但对逻辑回归或线性模型来说方差膨胀因子会拉得很高影响系数稳定性。3. 数据质量探查缺失值分布、类型陷阱与描述性统计的真实形态我对数据集做了一次完整的数据质量探查这一步的花样很多直接决定后续建模的底子。3.1 先看数据规模和字段总览我加载完数据后第一行代码通常是df.info()和df.head()。头部看一下数据结构再确认每列的非空数量、数据类型。这个数据集的常见情况是education有少量缺失cigsPerDay有大约29%的缺失totChol约有5%缺失BPMeds约有2.8%缺失glucose约有9%缺失diaBP和sysBP基本完整。这个缺失比例在公共医学数据集中并不算特别糟糕但cigsPerDay缺失近三成就不能简单忽略了后面专门讲它怎么处理。3.2 类别分布标签确实很不平衡我统计了TenYearCHD的分布发现1的比例大约只有15%0的比例有85%。这是一个典型的类别不平衡二分类问题。如果直接用准确率来评估模型即使全部预测为0也能拿到85%的准确率看起来很美实际一点用都没有。所以后续评估必须围绕ROC-AUC、PR-AUC、召回率、精确率来做这点我会在建模环节详细展开。3.3 描述性统计值域里藏着异常值信号通过df.describe()能快速看到每个连续变量的均值、标准差、最小最大值。这个数据集里我注意到几个值得深挖的信号totChol最小值出现在70到100左右最大值出现在400到600之间不同版本会有点差异。有些极端高胆固醇值疑似录入错误但也可能是个别严重高脂血症患者需要结合业务判断。heartRate最小值在40多、最大值在140到200之间静止心率超过160甚至200的大概率是异常测量值。BMI最大值有的版本能到56这超出了常见正常范围但并非不可能。要警惕的是BMI如果低于15或高于60基本可以不问原因直接视为异常。异常值处理不能一刀切。对树模型来说个别极端值影响有限但对逻辑回归、线性SVM这类依赖距离的模型极端值可能扭曲梯度更新方向。我的习惯是先用箱线图或IQR方法定位异常点再看它们的业务合理性如果是合理范围内的极端表现考虑采用分位数截断或保留并做鲁棒缩放如果是明显录入错误直接剔除。3.4 字段类型检查防止数字藏在字符串里公共数据集的列类型很多时候并不理想有的CSV会把数值列读成object因为某些行里混入了空字符串或特殊符号。我用pd.to_numeric()做一次强制转换配合errorscoerce把无法解析的部分变成NaN再统一处理。这个步骤特别重要因为如果不做后续算均值、做填充、训练模型时会直接报错或产生隐性错误。4. 缺失值处理从均值填充到按分组插补哪种方案在医学数据里更可靠缺失值处理没有银弹要分字段讨论。我的原则是能不填的让模型自己处理比如XGBoost原生支持缺失值但为了做对比实验通常至少准备一份传统的填充版本。4.1 数值型字段的缺失填充对于totChol、glucose这类连续变量最朴素的做法是用全局中位数或均值填充。但医学数据里缺失往往不是完全随机的。血糖和年龄、糖尿病状态强相关总胆固醇和年龄、性别也有关。如果只用一个全局均值去填会抹掉这些依赖关系。我尝试了三种填充方案做对比填充方式做法适用场景全局均值/中位数直接用所有非缺失样本的均值填充缺失率很低且字段和业务关联度不高时分组统计填充按性别、年龄段、是否糖尿病等分组用组内中位数填充字段与分组变量在医学上存在明确关联时模型预测填充用其余特征训练一个小模型预测缺失值特征间关联复杂缺失值本身有信息量时实测下来在这个数据集上分组填充和模型预测填充的效果差异其实不大尤其对逻辑回归这种性能上限有限的模型提升多在0.5到1个百分点以内。但分组填充胜在可解释性强而且不容易过拟合。4.2 吸烟相关字段的特殊处理currentSmoker为0的人cigsPerDay理论上应该是0不应该缺失。遇到currentSmoker0但cigsPerDayNaN的样本直接用0填充是合理的因为业务逻辑强制它必须是0。而currentSmoker1但cigsPerDayNaN的样本才是真正的缺失可以按吸烟人群的中位数填充比如每天20支左右。这个逻辑如果搞反比如给不吸烟的人硬填了一个20支/天模型会学到完全错误的关系。我特意检查过这个点很多公共教程里直接全列均值填充实际上把不吸烟人群的cigsPerDay也填成了十几这会导致模型对吸烟量的解读产生系统性偏差。4.3 是否填充、还是删除缺失率极低的字段比如BPMeds只缺2.8%两种选择都可以。缺失率较高且与标签相关的字段删除会造成信息浪费。在这个数据集里cigsPerDay缺失率接近29%但它是吸烟暴露量的重要指标直接删除整列损失太大。我更倾向于保留并做分组填充。对样本量只有4240的表格数据来说每减少一个有效特征模型表达力就下降一分。5. 建模前的特征工程从标准化到交互项我在对比实验里看到的真实差异预处理完成后进入特征工程阶段。这个阶段的目的不是创造复杂特征而是让已有特征更适合模型的学习方式。5.1 连续变量的分布与缩放策略totChol、glucose等变量在群体中的分布右偏。右偏分布对线性模型不太友好因为极端值会主导损失函数。我试过两种处理对totChol、glucose做log1p变换压缩右尾。对BMI、sysBP、diaBP、heartRate做标准化StandardScaler让均值归0、方差归1。实测下来对逻辑回归来说标准化几乎是必须的否则系数大小没法直接比较正则化项对不同量纲的字段惩罚也不公平。对树模型来说标准化不影响结果因为分裂点是基于排序而非距离但统一做标准化不会损害模型只增加计算开销。5.2 血压字段的交互项尝试systBP和diaBP的差就是脉压脉压增大往往和大动脉硬化有关。我构造了sysBP - diaBP作为交互特征。此外BMI和glucose的乘积也可以视为胰岛素抵抗相关信号但这些交互项在本数据集的增益有限。原因可能是样本量不够大真实验信号在噪声中被淹没了。我做了一组对比实验特征组合逻辑回归 ROC-AUCGBDT ROC-AUC原始15个特征0.74左右0.73左右原始特征脉压交互项0.74左右0.73左右原始特征脉压吸烟量二次项0.74左右0.73左右这个结果其实说明了一个问题在这个数据集上特征工程的边际收益有限。与其在交互项上投入过多时间不如把精力花在模型调参和类别不平衡上。5.3 关于独热编码的细节education是序数型分类变量1到4的取值。我用两种方式尝试一是当作数值型直接纳入二是用One-Hot编码展开。结果差异不大因为教育程度在这个数据集中本身不是强预测因子。如果换成性别这种无序分类变量One-Hot编码是有意义的但二分类变量本身0/1编码即可不需要额外展开。这里有个容易犯的错误把male、diabetes这种二分类变量也做One-Hot结果多出一列冗余特征导致完全共线性。做One-Hot之前先想清楚每一列的取值是有序还是无序、是二分类还是多分类。6. 建模实战逻辑回归、随机森林、XGBoost的完整对比流程建模部分我采用了一个简洁但完整的流程划分训练集与测试集训练三个基线模型做超参数粗调再结合类别不平衡处理最后对比评估指标。6.1 训练集与测试集划分我使用分层抽样StratifiedShuffleSplit划分数据保证训练集和测试集中TenYearCHD的类别比例与原始数据一致。这个步骤千万别省如果不分层随机划分很容易让少数的1类样本在某个子集里比例失衡。代码示意以Python为例from sklearn.model_selection import StratifiedShuffleSplit sss StratifiedShuffleSplit(n_splits1, test_size0.2, random_state42) for train_idx, val_idx in sss.split(X, y): X_train, X_val X.iloc[train_idx], X.iloc[val_idx] y_train, y_val y.iloc[train_idx], y.iloc[val_idx]我用random_state固定随机数种子确保每次实验可复现。在论文或报告中这个步骤也是必须交代的。6.2 逻辑回归一个强且稳的基线逻辑回归在这个数据集上不是最强的模型但作为基线非常合适。它可以给出每个特征的系数帮助判断哪些变量独立影响风险。我实验得到的结果也和临床认知基本一致年龄、收缩压、总胆固醇、吸烟量、血糖等系数的方向都是正号说明这些因素越高预测风险越大。我完整跑了一次逻辑回归用StandardScaler预处理加L2正则化。测试集ROC-AUC在0.73到0.75之间取决于缺失值填充方案和特征选择。这个数字意味着什么AUC为0.5是随机猜测0.6到0.7算弱预测能力0.7到0.8有实际参考价值。以纯基线指标预测10年和冠心病这种多因素结局0.74已经是一个合理的预测水平毕竟临床上还有大量未采集的遗传、饮食、运动信息会影响结局。6.3 随机森林与梯度提升树的表现对比随机森林需要关注的超参数主要是n_estimators、max_depth、min_samples_split。我粗调后随机森林的ROC-AUC和逻辑回归接近或略低一点但PR曲线形态在某些阈值下会好一些。原因可能在于特征之间以线性关系为主树模型的非线性切分优势发挥不出来。XGBoost在这个数据集上通常表现略好尤其在经过参数调节、学习率调低、n_estimators调高后。我用的重点是限制树的复杂度防止在小样本上过拟合。关键参数组合大概是learning_rate0.05n_estimators300max_depth4subsample0.8colsample_bytree0.8gamma1min_child_weight3。这些参数不是最完美的但对4000多条样本的表格数据来说足够稳定。模型ROC-AUC含特征工程备注Logistic Regression0.74基线系数可解释Random Forest0.73方差大需调参XGBoost0.75略优需防过拟合这个对比至少说明一件事在不做深度堆叠、不做大规模特征工程的前提下这几个主流模型的表现差距没有想象中悬殊。Framingham数据集给机器学习模型的性能天花板并不高因为数据本身包含的信号有限模型算法差异很难突破数据本身的信息瓶颈。6.4 类别不平衡处理策略过采样与代价敏感紧接着处理类别不平衡问题。我试过SMOTE过采样、class_weight调整、阈值移动三种方式。SMOTE会在少数类样本之间合成新样本它能提高召回率但也会改变数据的原始分布因此我在最终评估中没有把它作为首选而是作为对比方案。class_weightbalanced是让模型在损失函数里给少数类更高权重实现简单且不改数据分布。阈值移动是训练时不处理但在预测阶段降低正类的判定阈值从默认的0.5下调到0.3左右以提高召回率。对于预测10年冠心病风险这个场景漏掉一个高风险患者的代价比误报一个高风险患者的代价更高所以我的目标是保证召回率可接受的前提下尽量维持精确率。最直观的做法是看PR曲线在0.3到0.4的阈值区间选一个平衡点。7. 评估指标与结果解读从ROC-AUC到Calibration我在模型看起来很好时踩过的坑模型跑完不能只看AUCAUC高说明排序能力好但不代表概率值本身可靠。尤其在医学风险预测场景里概率校准Calibration非常关键。我遇到过一个典型情况模型AUC到了0.76看起来不错但输出的正类概率普遍偏高把很多10年风险只有5%的人预测成了30%。这在临床上会产生较大的心理负担和过度医疗。7.1 概率校准检查与处理我通过绘制校准曲线Calibration Curve发现了这个问题。解决方式是用Platt缩放或Isotonic回归对概率做后处理。在实际项目中我更推荐Isotonic回归但要求验证集样本量足够。对于4000多条的样本我用交叉拟合的方式做校准避免过拟合。校准方法适用场景注意事项Platt缩放样本量小基于逻辑回归单调变换适合AUC较高时Isotonic回归样本量充足可能过拟合需交叉拟合7.2 用SHAP解释模型预测原因为了理解模型为什么给某个样本打高分我顺手做了SHAP分析。虽然这个数据集比较小SHAP解释仍然值得做它能显示每个特征对单个预测的贡献方向。比如一个60岁的吸烟者SHAP图里会明确看到age贡献是正方向cigsPerDay贡献也是正方向两者叠加把预测概率推高。这种可解释性在向业务方或临床使用者汇报时极其重要——只说模型判断风险高是不够的还得说清楚是哪些因素把风险推高的。7.3 一个实际样本的预测流程演示我拿一条新样本做个直观演示55岁男性吸烟每天20支收缩压150mmHg总胆固醇240mg/dL血糖110mg/dL无糖尿病史无卒中史BMI28。经过相同的预处理和特征变换后模型输出的概率在0.55到0.65之间显著高于平均风险水平。虽然不能说个体概率等于未来事件的真实概率但在群体风险排序中这类人确实属于高危人群。8. 踩坑记录我重复测了几轮才发现这些问题这一节专门记录我在实操过程中反复折腾出来的经验希望能让后面的人少走弯路。8.1 训练集与验证集的一致性被破坏有一次我做数据清洗时先对整个数据集做了缺失值均值填充然后才划分训练集和验证集。表面看没什么问题但潜在风险在于均值填充用的统计量已经包含了验证集的信息这属于数据泄漏的一种。正确做法是先划分训练集/验证集再分别在训练集上计算填充值并应用到两边。8.2 特征工程的最优方案在验证集上失效我在训练集上精心挑选了一组交互特征使AUC提升了0.01喜不自胜。但换到验证集后发现没有提升甚至略有下降。这就是过拟合到训练集特征的模式。要避免这一点减少特征工程中的试错次数最好基于业务逻辑而非验证集表现来选择特征。8.3 复制不同版本的数据集导致结论不一致Framingham数据集在不同平台上有多个版本字段名可能略有差别比如有的把prevalentHyp写成prevalentHyper有的是TenYearCHD有的是CHD样本量也可能从3656到4240不等。不同版本缺失值比例差异较大直接导致模型性能波动。我在做分析之前必须确认数据集版本并记录MD5号或样本量避免换台电脑后跑出的结果对不上。8.4 类别不平衡时单纯看准确率会误判我最初拿随机森林跑完准确率接近85%以为效果很好。但一看混淆矩阵才发现模型几乎把所有样本都预测成了0类1类的召回率只有10%不到。这个数据集告诉我对不平衡分类问题任何时候都不能只看准确率要同时看混淆矩阵、召回率、精确率、ROC-AUC和PR-AUC。9. 扩展思考数据集还能怎么用Framingham数据集虽然经典但它的应用场景不止于跑一个分类模型。这里分享几个后续可以延伸的方向利用多个随访节点构建生存分析模型比如Cox比例风险回归预测的是事件发生的时间而不是是否发生。Framingham原始研究本来就是纵向设计只是整理后的公开版本丢失了时间维度。构造风险分层工具把预测概率映射到低、中、高危三个层级类似临床分级。做交叉验证和超参数调优案例教学因为样本量适中可以完整演示K折交叉验证、学习曲线、验证曲线等概念。作为不平衡学习和概率校准的基准数据集与其他同样存在不平衡问题的数据集对比。我个人更推荐站在业务落地的角度来看这个数据集它教会你的不是把AUC从0.74提到0.75而是要理解当一个模型用于风险预警时模型只是决策链的一环你还需要解释性、校准度、误报容忍度、群体公平性等维度。如果你能把Framingham这个项目从数据理解做到SHAP解释再做到一份完整的分析报告那么你处理其他真实业务数据的流程感会完全不同。建模这件事模型复杂度反而是最不值得炫技的部分真正拉开差距的是对数据含义的理解、对评估方式的审慎选择以及能不能把模型输出翻译成业务语言。这也是我在这次项目中最深的体会。本文还有配套的精品资源点击获取