
简介这份资源面向医学数据分析与机器学习入门者围绕心脏衰竭致死因素展开完整建模实践。基于临床记录数据集从12个相关因素切入先做可视化呈现变量关系再用统计学方法检验各因素与致死结局的相关性并借助Lasso方法筛选出更重要的特征最后分别构建逻辑回归、支持向量机与随机森林三种分类器进行预测对比帮助读者理解特征选择与模型评估的完整流程。压缩包共10个文件约240KB包含5个Python脚本、1个R脚本、1份PDF报告、1份CSV数据集及LICENSE、README等说明文件脚本按算法拆分便于逐项运行与对照报告则汇总分析结论与结果解读。目前已有804人学习下载适合希望掌握Lasso特征筛选、多模型分类对比及医学数据可视化分析的学习者参考复用。1. 心脏衰竭预测这道题为什么值得用 LASSO 逻辑回归认真做一遍心脏衰竭预测在临床数据挖掘里是个经典二分类问题给定患者的射血分数、血清肌酐、年龄、性别等指标判断其在随访期内是否发生死亡事件。这类数据集通常只有几百条样本、十几个特征但特征之间往往存在共线性——比如射血分数和纽约心功能分级高度相关血清钠和肾功能指标互相纠缠。如果直接把所有变量丢进逻辑回归系数估计会剧烈震荡模型在不同训练集上表现差异极大这就是所谓的“玄学”现象。LASSO 回归在这里的价值不是做预测而是做特征筛选它通过 L1 正则化把不重要的变量系数压缩到零留下一组稀疏且稳定的特征子集。再用这组特征跑逻辑回归模型的可解释性和泛化能力都会明显提升。这套组合特别适合两类人一是手上只有几百条结构化临床数据、想快速拿到可解释结果的分析人员二是正在做机器学习课程设计、需要完整代码和报告但不想只跑一个 sklearn 默认参数的学生。接下来我会把数据预处理、LASSO 筛选、逻辑回归建模、阈值调优和结果解读的完整路径拆开讲每一步都给出可复现的代码和参数说明。2. 数据准备与 LASSO 特征筛选从原始表格到稀疏系数2.1 心脏衰竭数据集长什么样先做三件事常见的心脏衰竭数据集包含 299 条记录、13 个特征目标变量是DEATH_EVENT0 表示存活1 表示死亡。特征包括年龄、贫血、糖尿病、高血压、性别、吸烟、射血分数、血清肌酐、血清钠、随访天数等。拿到数据后不要急着建模先做三件事检查缺失值、看类别分布、确认特征量纲差异。import pandas as pd import numpy as np from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler # 读取数据假设文件名为 heart_failure_clinical_records_dataset.csv df pd.read_csv(heart_failure_clinical_records_dataset.csv) # 1. 缺失值检查 print(df.isnull().sum().sum()) # 输出总缺失数 # 2. 类别分布 print(df[DEATH_EVENT].value_counts(normalizeTrue)) # 3. 特征量纲差异 print(df.describe().loc[[mean, std]].T)逻辑说明isnull().sum().sum()返回整个表缺失值总数心脏衰竭数据集通常没有缺失但这一步不能省。value_counts(normalizeTrue)看正负样本比例如果死亡事件占比低于 30%后续划分训练集时要考虑分层抽样。describe()看均值和标准差血清肌酐的标准差可能远大于其他特征不做标准化会让 LASSO 的惩罚项偏向数值大的特征。参数说明train_test_split中stratifyy必须加保证训练集和测试集的正负样本比例一致random_state固定为 42 方便复现test_size取 0.2 到 0.3 之间299 条样本建议留 60 到 90 条做测试。2.2 用 LASSO 做特征筛选alpha 怎么选系数怎么读LASSO 的本质是在逻辑回归的损失函数上加一项 L1 范数惩罚。对于二分类问题sklearn 的LogisticRegression设置penaltyl1和solverliblinear或saga即可实现。关键参数是正则化强度C它是 alpha 的倒数C 越小惩罚越强被压缩到零的系数越多。from sklearn.linear_model import LogisticRegression from sklearn.model_selection import cross_val_score # 标准化 scaler StandardScaler() X df.drop(DEATH_EVENT, axis1) y df[DEATH_EVENT] X_scaled scaler.fit_transform(X) # 分层划分 X_train, X_test, y_train, y_test train_test_split( X_scaled, y, test_size0.25, random_state42, stratifyy ) # 用交叉验证选 C C_values [0.01, 0.03, 0.1, 0.3, 1, 3, 10] cv_scores [] for c in C_values: lr LogisticRegression(penaltyl1, Cc, solverliblinear, max_iter1000) scores cross_val_score(lr, X_train, y_train, cv5, scoringroc_auc) cv_scores.append(scores.mean()) print(fC{c}, AUC{scores.mean():.4f}) best_C C_values[np.argmax(cv_scores)] print(f最佳 C: {best_C})逻辑说明cross_val_score用 5 折交叉验证评估每个 C 值下的 AUC选 AUC 最高的 C。注意这里用 AUC 而不是准确率因为心脏衰竭数据正负样本可能不平衡AUC 对阈值不敏感更能反映排序能力。solverliblinear对 L1 支持稳定数据量小的时候比saga收敛更快。参数说明max_iter1000防止迭代次数不足导致警告cv5在 299 条样本下每折约 45 条训练数据折数再高会导致单折样本过少scoringroc_auc需要正类有足够样本如果正类少于 30 条建议改用f1。选定 C 之后用全部训练集拟合 LASSO 逻辑回归查看哪些特征的系数被压缩为零。# 用最佳 C 拟合 lasso_lr LogisticRegression(penaltyl1, Cbest_C, solverliblinear, max_iter1000) lasso_lr.fit(X_train, y_train) # 系数表 coef_df pd.DataFrame({ feature: X.columns, coef: lasso_lr.coef_[0] }) coef_df[abs_coef] coef_df[coef].abs() coef_df coef_df.sort_values(abs_coef, ascendingFalse) print(coef_df)逻辑说明coef_返回每个特征对应的系数绝对值越大说明该特征对预测结果影响越强。系数为零的特征被 LASSO 剔除。常见结果是serum_creatinine、ejection_fraction、serum_sodium、time这几个特征保留下来而smoking、diabetes等可能被压缩为零。参数说明coef_df按绝对值排序后可以手动设定一个阈值比如 0.01进一步过滤极小系数。但要注意LASSO 筛选出的特征子集依赖于 C 的选择C 稍大一点可能多保留两个特征C 稍小可能只剩三个特征。建议把 C 在最佳值附近再取两三个值跑一遍观察保留特征是否稳定。3. 逻辑回归建模与阈值调优从概率输出到临床可用的二分类3.1 用筛选后的特征重新训练为什么 AUC 可能不降反升LASSO 筛选后用保留的特征重新训练一个不带正则化的逻辑回归或带 L2 正则化对比前后 AUC。常见结果是特征数从 12 个降到 5 到 7 个测试集 AUC 反而略升或持平。原因是剔除了噪声特征和共线性变量模型方差降低。from sklearn.metrics import roc_auc_score, classification_report, confusion_matrix # 保留系数非零的特征 selected_features coef_df[coef_df[abs_coef] 0.01][feature].tolist() print(f保留特征: {selected_features}) # 用筛选特征重新划分注意scaler 要重新拟合避免数据泄漏 X_sel df[selected_features] X_train_sel, X_test_sel, y_train_sel, y_test_sel train_test_split( X_sel, y, test_size0.25, random_state42, stratifyy ) scaler_sel StandardScaler() X_train_sel_scaled scaler_sel.fit_transform(X_train_sel) X_test_sel_scaled scaler_sel.transform(X_test_sel) # 训练逻辑回归 lr_final LogisticRegression(penaltyl2, C1.0, solverliblinear, max_iter1000) lr_final.fit(X_train_sel_scaled, y_train_sel) # 预测概率和 AUC y_prob lr_final.predict_proba(X_test_sel_scaled)[:, 1] auc roc_auc_score(y_test_sel, y_prob) print(f测试集 AUC: {auc:.4f})逻辑说明这里有一个容易翻车的地方——标准化必须在划分训练测试集之后用训练集的均值和方差拟合 scaler再变换测试集。如果先对全量数据做标准化再划分测试集的统计信息会泄漏到训练过程中导致 AUC 虚高。predict_proba返回两列第二列是正类死亡事件的概率。参数说明penaltyl2是默认值如果筛选后特征仍然有共线性可以继续用 L1C1.0是保守选择如果交叉验证显示更小的 C 更好可以调低。solverliblinear在小数据集上稳定数据量超过一万条时换saga。3.2 阈值不是 0.5用约登指数找最佳截断点逻辑回归输出的是概率默认以 0.5 为阈值划分正负类。但在临床场景下漏诊一个高风险患者的代价远大于误判一个低风险患者。这时候需要调整阈值常用方法是约登指数Youden index在 ROC 曲线上找到敏感度 特异度 - 1最大的点。from sklearn.metrics import roc_curve fpr, tpr, thresholds roc_curve(y_test_sel, y_prob) youden tpr - fpr best_idx np.argmax(youden) best_threshold thresholds[best_idx] print(f最佳阈值: {best_threshold:.4f}, 敏感度: {tpr[best_idx]:.4f}, 特异度: {1-fpr[best_idx]:.4f}) # 用最佳阈值生成分类结果 y_pred_best (y_prob best_threshold).astype(int) print(confusion_matrix(y_test_sel, y_pred_best)) print(classification_report(y_test_sel, y_pred_best))逻辑说明roc_curve返回不同阈值下的假正率、真正率和阈值数组。tpr - fpr即约登指数最大值对应的阈值就是兼顾敏感度和特异度的截断点。心脏衰竭预测中这个阈值通常低于 0.5比如 0.35 到 0.45 之间意味着模型对死亡风险的判定更保守。参数说明thresholds[0]可能是无穷大np.argmax不会选到它。如果正类样本极少少于 20 条约登指数波动大建议改用 F1 最大或直接固定敏感度不低于 0.85 来反推阈值。classification_report中的support列显示测试集中实际的正负样本数如果正类只有十几条精确率和召回率的置信区间会很宽报告里要注明。3.3 交叉验证的 AUC 和测试集 AUC 差多少算正常训练完模型后把交叉验证的 AUC 和测试集 AUC 放在一起看。如果交叉验证 AUC 是 0.88测试集 AUC 是 0.72差距超过 0.1说明模型过拟合或者数据划分有偏。常见原因是样本量太小测试集恰好包含了一些难分样本。# 交叉验证 AUC cv_auc cross_val_score(lr_final, X_train_sel_scaled, y_train_sel, cv5, scoringroc_auc) print(f交叉验证 AUC: {cv_auc.mean():.4f} ± {cv_auc.std():.4f}) print(f测试集 AUC: {auc:.4f})逻辑说明cv_auc.std()反映模型在不同折上的稳定性标准差超过 0.05 说明模型对数据划分敏感。如果测试集 AUC 明显低于交叉验证均值可以尝试换random_state重新划分观察 AUC 波动范围。如果波动超过 0.08说明 299 条样本对当前特征集来说偏少需要考虑收集更多数据或改用更简单的模型。参数说明cv5时每折约 45 条训练数据AUC 的方差本身较大。可以改用cv10但每折只有 22 条方差更大。折中方案是重复 5 折交叉验证 3 次取平均。4. 避坑与排查心脏衰竭预测建模中五个血泪教训4.1 标准化顺序搞反AUC 虚高 0.1 以上现象测试集 AUC 达到 0.92但换一组随机种子后掉到 0.78。原因先对全量数据做了StandardScaler再划分训练测试集测试集的均值和方差参与了标准化参数估计造成数据泄漏。解决永远先train_test_split再用fit_transform处理训练集用transform处理测试集。如果用了交叉验证标准化必须放在Pipeline里让每一折独立拟合。4.2 LASSO 的 C 选太大特征一个没删现象LASSO 跑完发现所有特征系数都非零和普通逻辑回归没区别。原因C 值设得太大比如默认的 1.0 或更大L1 惩罚力度不够系数压缩不到零。解决把 C 的搜索范围下探到 0.01 甚至 0.005用交叉验证 AUC 选最佳值。如果 C 小到 0.01 仍然保留全部特征说明特征本身区分度都较高或者样本量太少导致系数估计不稳定这时候可以手动设定系数绝对值阈值来筛选。4.3 用准确率评估模型正类全预测为负也能拿高分现象模型准确率 0.75但混淆矩阵显示正类一个都没预测对。原因心脏衰竭数据中死亡事件占比约 32%如果模型把所有样本预测为存活准确率也有 68%。解决评估指标必须包含 AUC、敏感度、特异度和 F1。在报告里同时给出混淆矩阵让读者看到正类召回率。如果正类召回率低于 0.5模型在临床场景下没有实用价值。4.4 特征筛选后重新标准化忘了用训练集的 scaler现象用筛选后的特征重新训练时对筛选后的数据重新fit_transform导致测试集变换参数和训练集不一致。原因筛选特征后重新划分了数据集scaler 也需要重新拟合。解决筛选特征后仍然用之前训练集的 scaler 对筛选后的特征做变换或者重新走一遍「划分→拟合 scaler→变换」的流程。关键是测试集的变换参数必须来自训练集。4.5 忽略随访天数time特征模型性能大幅下降现象LASSO 把time特征系数压缩到零剔除后 AUC 从 0.85 掉到 0.72。原因time是随访天数与死亡事件高度相关——随访时间短的患者可能因为早期死亡而中断随访。LASSO 在某些 C 值下可能低估它的重要性。解决如果领域知识明确某个特征重要不要完全依赖 LASSO 的自动筛选。可以强制保留该特征或者用SelectFromModel配合threshold参数手动调整。在报告里要说明哪些特征是强制保留的以及理由。5. 把模型变成可复现的报告结果表格、校准曲线与一个实用技巧5.1 报告里必须放的三张表一份能让人信服的心脏衰竭预测报告至少包含三张表。第一张是特征系数表列出 LASSO 筛选后保留的特征、系数值、优势比exp(coef)。优势比大于 1 表示该特征增加死亡风险小于 1 表示保护因素。第二张是模型性能对比表对比只用 LASSO 筛选特征和用全部特征的 AUC、敏感度、特异度、F1。第三张是混淆矩阵展示测试集上的实际分类结果。特征系数优势比方向serum_creatinine0.822.27风险ejection_fraction-0.650.52保护serum_sodium-0.410.66保护time-0.930.39保护age0.351.42风险优势比的计算方式是np.exp(coef)。血清肌酐优势比 2.27 意味着该指标每升高一个标准差死亡风险增加 127%。射血分数优势比 0.52 意味着每升高一个标准差死亡风险降低 48%。这些数字比单纯说“特征重要”更有说服力。5.2 校准曲线概率预测准不准看这条线AUC 衡量的是排序能力不衡量概率绝对值是否准确。如果模型预测某患者死亡概率 0.8实际只有 0.5AUC 可能仍然很高但临床决策会出错。校准曲线calibration curve把预测概率分箱看每个箱内实际正类比例是否接近预测均值。from sklearn.calibration import calibration_curve import matplotlib.pyplot as plt prob_true, prob_pred calibration_curve(y_test_sel, y_prob, n_bins5) plt.plot(prob_pred, prob_true, markero, label模型) plt.plot([0, 1], [0, 1], linestyle--, label理想校准) plt.xlabel(预测概率) plt.ylabel(实际正类比例) plt.legend() plt.show()逻辑说明calibration_curve把预测概率分成 5 个箱计算每个箱的平均预测概率和实际正类比例。如果点落在对角线上说明概率校准良好如果点在对角线下方说明模型高估了风险。心脏衰竭数据样本少n_bins取 4 到 6 之间再多每箱样本不足 10 条曲线会剧烈波动。参数说明n_bins5是经验值测试集 75 条样本时每箱约 15 条。如果测试集更小改用n_bins3。校准曲线不理想时可以用CalibratedClassifierCV对模型做 Platt 校准或等渗校准但会增加过拟合风险样本少于 500 条时不建议用。5.3 一个让我少走弯路的习惯先跑基线再调参我刚开始做心脏衰竭预测时花了两天调 LASSO 的 C 值和逻辑回归的 solver结果 AUC 只提升了 0.02。后来养成一个习惯先用默认参数跑一个基线模型记录 AUC、敏感度、特异度然后再动任何参数。如果调参带来的提升小于 0.03说明当前特征集的信息量已经接近上限继续调参的收益很低不如回头检查数据质量或补充新特征。这个习惯帮我省下了大量时间。具体做法是在代码最前面写一个baseline_model()函数用LogisticRegression()默认参数和全部特征跑一遍输出 AUC 和分类报告。之后所有实验都和这个基线对比提升不明显就果断放弃。心脏衰竭数据集只有 299 条模型性能的天花板大概在 AUC 0.85 到 0.90 之间超过这个范围要么是数据泄漏要么是测试集太小导致的随机波动。希望帮到你。本文还有配套的精品资源点击获取