
简介这份资源面向机器学习入门与进阶学习者围绕心脏病数据集展开完整的分析与预测实战涵盖数据清洗、特征工程、可视化探索与多模型对比等典型环节适合希望系统练习分类建模流程、积累项目经验的高校学生与算法爱好者。压缩包共14个文件以11个Python源代码为主另含2个CSV数据集与1份说明文档整体约46KB代码手工整理、无语法错误可直接运行。资源覆盖逻辑回归、KNN、高斯朴素贝叶斯、决策树、SVM、随机森林、XGBoost、MLP等多种算法并涉及MinMaxScaler、StandardScaler、KNNImputer、ColumnTransformer与Pipeline等预处理工具同时包含混淆矩阵、分类报告、ROC曲线、AUC、学习曲线与排列重要性等评估手段还借助seaborn、dtreeviz及pandas绘图呈现数据分布与模型解释。目前已有98人学习下载读者可据此掌握从原始数据到模型评估的完整链路理解不同算法的适用场景与调参思路并复用其中的可视化与评估代码快速迁移到其他分类任务。1. 心脏病预测这套数据集为什么值得你花一个周末跑通拿到一份 150 KB 左右的心脏病数据集外加 11 个源代码文件很多人第一反应是「这么小能跑出什么」。我一开始也这么想直到把它当成一条完整的 AI 实战链路走了一遍从字段清洗、缺失值处理到特征工程、模型对比再到阈值调优和结果解释。它小但五脏俱全正好适合用来验证一套分析预测流程是否跑得通。这份数据集通常包含年龄、性别、胸痛类型、静息血压、胆固醇、空腹血糖、静息心电图、最大心率、运动诱发心绞痛、ST 段压低、坡度、主血管数、thal 等字段目标列是是否患病。11 个源代码文件一般覆盖数据加载、可视化、预处理、多模型训练、评估和简单调参。它解决的不是「发论文」的问题而是让你在本地把「心脏病数据集分析预测」这件事从头到尾做一遍知道每一步在干什么、参数怎么设、哪里容易翻车。适合刚接触结构化数据分类的新手也适合想快速验证特征工程思路的熟手。2. 先看清数据长什么样字段、分布与目标列2.1 字段含义与类型划分在动手写模型之前必须先把每个字段归到正确的类型里。常见的心脏病数据集字段可以分成三类字段类型说明age连续数值年龄通常 29~77sex二分类1 男 0 女cp多分类胸痛类型4 个取值trestbps连续数值静息血压chol连续数值血清胆固醇fbs二分类空腹血糖是否大于 120restecg多分类静息心电图结果thalach连续数值达到的最大心率exang二分类运动是否诱发心绞痛oldpeak连续数值运动相对静息 ST 段压低slope多分类ST 段坡度ca多分类主血管数thal多分类地中海贫血相关target二分类是否患病类型判断错了后面标准化和编码都会出问题。比如cp虽然是数字但它不是连续量不能直接丢进标准化。2.2 用最小代码把分布和目标列看清楚先跑一段加载和概览代码确认列名、缺失和类别平衡import pandas as pd import numpy as np # 列名按常见心脏病数据集命名实际以你手里的 csv 为准 cols [age,sex,cp,trestbps,chol,fbs,restecg, thalach,exang,oldpeak,slope,ca,thal,target] df pd.read_csv(heart.csv, namescols, header0) print(df.shape) print(df.isnull().sum()) print(df[target].value_counts(normalizeTrue)) print(df.describe().T[[mean,std,min,max]])这段代码做三件事看形状和缺失、看目标列比例、看数值字段的均值和极值。如果target比例接近 5:5说明类别比较平衡不需要额外过采样如果偏差大后面评估就不能只看准确率。describe里如果chol或trestbps出现 0 值要警惕那往往是缺失被填成了 0而不是真实测量值。2.3 缺失值与异常值的处理顺序常见做法是先处理异常值再处理缺失值。因为如果先把 0 当缺失填掉异常值判断就失去了依据。对于chol、trestbps这类字段0 值基本可以判定为无效建议先替换成np.nan再决定是删行还是填中位数。样本量本来就不大删行要谨慎我一般优先用中位数填充并额外加一列缺失标记让模型知道这个值原来是缺的。# 把医学上不可能的 0 值先转成缺失 for c in [chol,trestbps]: df[c] df[c].replace(0, np.nan) # 加缺失标记再填充 df[chol_missing] df[chol].isnull().astype(int) df[trestbps_missing] df[trestbps].isnull().astype(int) df[chol] df[chol].fillna(df[chol].median()) df[trestbps] df[trestbps].fillna(df[trestbps].median())逻辑说明先替换再标记保证标记列反映的是原始缺失情况填充用中位数而不是均值是因为医学指标常有偏态均值容易被极端值拉偏。参数上如果你发现缺失比例超过 20%就要考虑是不是数据采集本身有问题而不是硬填。3. 特征工程与模型选型把 11 个源代码文件串起来3.1 类别编码与数值标准化多分类字段cp、restecg、slope、thal不能当连续值用。常见做法是 one-hot 编码但要注意ca和thal如果本身有序也可以尝试序数编码后做树模型。数值字段age、trestbps、chol、thalach、oldpeak需要标准化尤其是你要跑逻辑回归或 SVM 的时候。from sklearn.preprocessing import StandardScaler, OneHotEncoder from sklearn.compose import ColumnTransformer from sklearn.pipeline import Pipeline num_cols [age,trestbps,chol,thalach,oldpeak] cat_cols [sex,cp,fbs,restecg,exang,slope,ca,thal] pre ColumnTransformer([ (num, StandardScaler(), num_cols), (cat, OneHotEncoder(dropfirst), cat_cols) ])dropfirst是为了避免独热编码后的共线性对线性模型友好树模型其实不 drop 也行但统一处理更省事。标准化只在训练集上 fit验证集和测试集只 transform这一点如果写错就是典型的数据泄漏。3.2 多模型对比的最小训练脚本11 个源代码文件里通常会有多个模型。我一般先跑一个基线逻辑回归再跑随机森林和梯度提升最后看谁在交叉验证上更稳。from sklearn.model_selection import cross_val_score, StratifiedKFold from sklearn.linear_model import LogisticRegression from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier from sklearn.pipeline import Pipeline cv StratifiedKFold(n_splits5, shuffleTrue, random_state42) models { lr: LogisticRegression(max_iter1000, C1.0), rf: RandomForestClassifier(n_estimators300, max_depth5, random_state42), gb: GradientBoostingClassifier(n_estimators200, learning_rate0.05, random_state42) } for name, clf in models.items(): pipe Pipeline([(pre, pre), (clf, clf)]) scores cross_val_score(pipe, X, y, cvcv, scoringroc_auc) print(name, scores.mean().round(4), scores.std().round(4))逻辑说明用StratifiedKFold保证每折里正负样本比例一致评分用roc_auc而不是准确率是因为医学场景更关心排序能力。参数上随机森林的max_depth先压到 5 左右防止小数据集过拟合梯度提升的learning_rate设 0.05配合 200 棵树通常比默认 0.1 更稳。3.3 阈值调整与混淆矩阵默认 0.5 阈值在医学预测里往往不是最优。你可以画出 ROC 曲线找到约登指数最大的点再把这个阈值固定下来。from sklearn.metrics import roc_curve, confusion_matrix import numpy as np # 假设 y_prob 是交叉验证或留出集上的预测概率 fpr, tpr, thresholds roc_curve(y_true, y_prob) youden tpr - fpr best_thr thresholds[np.argmax(youden)] print(best threshold:, best_thr) y_pred (y_prob best_thr).astype(int) print(confusion_matrix(y_true, y_pred))这段代码先算 ROC再用约登指数选阈值最后输出混淆矩阵。注意thresholds的第一个值可能是无穷大实际取的时候要过滤掉。混淆矩阵能让你看到漏诊和误诊各有多少比单看 AUC 更有落地感。4. 避坑与排查小数据集上最容易翻车的 5 个点4.1 现象交叉验证分数很高换一批数据就崩原因在预处理阶段把标准化或编码 fit 到了全量数据造成数据泄漏。解决所有 fit 操作必须放进 Pipeline只在训练折上执行。检查方法是把 Pipeline 拆开确认StandardScaler没有在cross_val_score之前单独 fit 过。4.2 现象chol和trestbps出现大量 0 值模型仍然能跑原因0 被当成真实值参与训练树模型会学到一个「0 值对应低风险」的伪规律。解决先替换成np.nan再填充中位数并加缺失标记列。如果缺失比例过高考虑直接删掉这两个字段做对比实验。4.3 现象随机森林特征重要性里age排第一但业务上说不通原因小数据集上树模型的特征重要性不稳定尤其是存在相关特征时。解决改用排列重要性并在多个随机种子上重复计算取平均值。如果age和thalach高度相关重要性会被分散不要只凭一次结果下结论。4.4 现象阈值调到 0.3 后召回上去了但误诊暴增原因约登指数只考虑 TPR 和 FPR 的差没有考虑实际代价。解决先明确漏诊和误诊哪个更不能接受再手动设定代价矩阵或者直接固定召回下限在满足下限的前提下选阈值。不要盲目追求约登指数最大。4.5 现象11 个源代码文件里有的用target0/1有的用 1/2原因不同来源的数据集目标编码不一致直接合并会出错。解决统一映射成 0/1并在读入后立刻打印value_counts确认。如果发现 1/2 编码用df[target] df[target].map({1:0, 2:1})转换转换后再检查一遍分布。5. 把模型变成可复现的验证习惯固定种子与结果存档5.1 固定随机种子与交叉验证重复小数据集上单次划分的波动可能比模型差异还大。我一般会做 10 次不同种子的 5 折交叉验证看 AUC 的均值和标准差而不是只看一次结果。from sklearn.model_selection import RepeatedStratifiedKFold rcv RepeatedStratifiedKFold(n_splits5, n_repeats10, random_state42) scores cross_val_score(pipe, X, y, cvrcv, scoringroc_auc) print(scores.mean().round(4), scores.std().round(4))n_repeats10意味着每种划分重复 10 次总共 50 次训练。如果标准差超过 0.03说明模型对数据划分很敏感这时候不要急着调参先检查特征工程是否稳定。5.2 结果存档与对比表每次实验至少记录模型名、关键参数、AUC 均值、AUC 标准差、阈值、召回、精确率。用一张表存下来比在脑子里记靠谱。实验模型关键参数AUC 均值AUC 标准差阈值召回精确率1LRC1.00.890.020.500.820.852RFdepth50.910.030.450.860.833GBlr0.050.920.020.420.880.84这张表能让你一眼看出哪个模型在召回和精确率之间更平衡。如果某个模型 AUC 高但召回低就要结合业务判断是否可用。5.3 一个我常犯的错忘了保存预处理对象训练完模型直接关掉 notebook下次想预测新样本时发现标准化参数没了。后来我养成习惯用joblib把整个 Pipeline 存下来包括预处理和模型。import joblib pipe.fit(X_train, y_train) joblib.dump(pipe, heart_pipeline.pkl) # 下次直接加载 pipe_loaded joblib.load(heart_pipeline.pkl) pred pipe_loaded.predict(X_new)这样新数据进来时标准化和编码会自动按训练时的参数执行不会因为手动处理顺序不同而出错。这个习惯帮我省了很多后悔药。5.4 验证方法用置换检验看模型是否真的学到了东西如果 AUC 看起来不错但你不确定是不是运气可以做置换检验把标签打乱重新跑同样的流程看 AUC 分布。如果真实 AUC 明显高于打乱后的分布说明模型确实学到了信号。from sklearn.utils import shuffle null_scores [] for i in range(50): y_shuffled shuffle(y, random_statei) s cross_val_score(pipe, X, y_shuffled, cvcv, scoringroc_auc).mean() null_scores.append(s) print(real:, real_auc, null mean:, np.mean(null_scores))如果真实 AUC 和 null 均值差距不到 0.05就要警惕模型可能只是在拟合噪声。这个检验在小数据集上尤其值得做因为样本少偶然性大。我自己的习惯是每拿到一份新的结构化数据集先跑一遍置换检验再决定要不要投入时间调参。心脏病这份数据不大但把上面这套流程走完你对分析预测的理解会比跑十个大项目还扎实。希望帮到你。本文还有配套的精品资源点击获取