UCI心脏疾病数据分析全流程:清洗、可视化与建模实战

发布时间:2026/9/14 14:50:14
UCI心脏疾病数据分析全流程:清洗、可视化与建模实战 简介这是一份面向计算机相关专业毕业设计、课程设计或期末大作业的完整实践项目基于UCI公开心脏疾病数据集使用Python完成从数据清洗、探索性可视化到机器学习建模的全流程分析可用于识别心脏病风险因素。压缩包共70个文件以PNG/JPG图表、PY源码、CSV数据集为主附带PDF分析报告、PPT答辩演示及README说明整体大小约23.24MB结构清晰便于按阶段研读。资源已吸引81人学习尤其适合希望快速上手数据分析项目并产出完整报告的学生或入门学习者。内容涵盖数据预处理、特征分布与统计图表、多种模型对比及评估指标并配有混淆矩阵、ROC曲线等关键结果展示同时提供答辩用PPT帮助使用者直接理解项目思路并补充讲解细节。整体是一份可直接参考的完整数据科学案例。1. 心脏疾病数据分析为什么绕不开UCI数据集一个初入行或准备转行的数据分析师拿着“心脏疾病数据分析”这个题目练手时最常见的卡点不是不会写Python而是找不到一份像样的数据或者拿到数据后不知道从哪一步开始。UCI Machine Learning Repository里的Heart Disease数据集恰好是这类项目的标配起点字段数量适中、有真实医学含义、带有缺失值和类型混用问题非常适合用来走完“清洗-探索-建模-展示”的完整流程。这篇文章就顺着你最后的交付物Python代码、数据集、报告、PPT演示来拆解一整套可落地的做法包括如何用pandas处理UCI原始字段、如何用seaborn画关键图表、如何用scikit-learn评估模型以及最后如何用nbconvert和python-pptx把结果固化成报告与幻灯片。全篇不跳步每个环节给可直接跑的代码和参数说明。2. 用pandas清洗UCI心脏数据缺失值、类型转换与目标变量构造2.1 认识UCI Heart Disease数据集的字段与口径UCI上面的heart disease数据其实指向的是Cleveland、Hungarian等几个来源最常用的是Cleveland那份总共303条样本、14个主要属性。我们做分析时通常把最后一列num作为目标变量它表示血管狭窄程度是否大于50%取值为0到4其中0代表没有心脏疾病1-4代表不同程度的病变。绝大多数教学项目和论文会把num二值化为0和1因为这样可以直接当分类问题处理也方便和文献里的AUC数值做对比。需要注意底层的原始txt文件里缺失值是用?占位的而不是空字符串或NaN。如果用pd.read_csv()直接读?会被当成普通字符串导致age、trestbps这些数值列变成object类型。这个问题是新手第一次跑数据时最容易翻车的地方。建议读文件时直接给na_values参数传?或者读进来后再统一替换。除此之外ca主要血管数和thal地中海贫血类型这两个字段在Cleveland集中都有空值剔除后样本会少了6条左右对总量影响不大。目标变量构造的另一个常见问题是有人直接用df[num] df[num] 0映射成布尔值之后又要传给scikit-learn。这里的坑是布尔值在部分版本里会被当成0/1但在做特征重要性排序或SHAP解释时不够直观。我更建议显式转成整数列import pandas as pd df pd.read_csv( data/heart.csv, na_values? # 把原始数据里的 ? 统一解析为缺失值 ) df[target] (df[num] 0).astype(int)上面这段代码做了两件事第一读取时直接把?变成NaN后续就能用isna()统一处理第二生成新的target列0表示无病变1表示有病变建模时不需要再单独处理num里1-4的细分程度。如果不传na_valuesdf[ca]会被读成object类型后面画图或做相关矩阵时会直接报错或者异常显示。2.2 处理缺失值与字段类型的完整清洗代码清洗这一步的核心任务有三个缺失值处理、数值类型修正、还有面向后续建模的字段规范化。下面是一段我常用的完整代码可以直接抄下来放到项目里跑。df[ca] pd.to_numeric(df[ca], errorscoerce) df[thal] pd.to_numeric(df[thal], errorscoerce) fill_rules { ca: df[ca].median(), # 0 最多用中位数更稳 thal: df[thal].mode()[0] # 用众数填充维持类别分布 } df df.fillna(valuefill_rules) numeric_cols [ age, trestbps, chol, thalach, oldpeak, slope, cp ] df[numeric_cols] df[numeric_cols].astype(float)逐行说明pd.to_numeric配合errorscoerce能把无法解析的字符串变成NaN比直接astype(float)安全得多。fillna传入一个字典时pandas会按列名匹配填充值ca用中位数是因为它分布极度偏向0中位数比均值更能代表典型值thal是分类语义用众数填不会引入不存在的细分类别。最后再统一把几个连续变量转成float避免后面建模时类型不一致报错。这里容易忽略的是cp和slope这两个字段它们虽然用数字表示但本质是序数或类别变量转成float只方便画图建模时要么做独热编码要么用sklearn里的OrdinalEncoder单独处理。如果直接扔进逻辑回归数值大小的含义会被当成线性关系结论容易出现偏差。还有一点经验是清洗完以后不妨跑一句df.info()检查每列的非空数量确认没有哪一列被误伤。2.3 验证清洗结果的几个关键参数清洗完成后我会立刻做两件事打印每列的缺失值数量和数据类型再跑一次描述性统计看数值范围是否合理。下面这段代码是验证环节的模板print(df.isna().sum()) print(df.dtypes) print(df.groupby(target)[[age, thalach, chol]].mean())isna().sum()的输出应该全是0说明缺失值已经处理干净。dtypes里数值列都应该是float64或int64如果还有object说明某个字段没转成数值。groupby这一步是为了快速检查两类人群的特征均值差异通常target1的人群thalach最大心率偏低、age偏大如果数据方向和常识完全相反多半是读取时把num和target的对应关系搞反了。3. Python探索性分析UCI心脏数据可视化与特征对比3.1 用seaborn画出目标变量下的年龄与最大心率分布探索性分析的目的是为后面的建模提供直觉依据同时也是报告和PPT里最核心的素材。对于心脏疾病数据两个最值得先看的变量是age和thalach前者反映患病随年龄的整体趋势后者是运动试验中能达到的最大心率临床上和心血管储备直接相关。可以用seaborn的kdeplot或者histplot按target分组来看分布差异。import seaborn as sns import matplotlib.pyplot as plt sns.set_theme(stylewhitegrid) fig, axes plt.subplots(1, 2, figsize(12, 4)) sns.kdeplot(datadf, xage, huetarget, fillTrue, axaxes[0]) axes[0].set_title(Age Distribution by Target) sns.kdeplot(datadf, xthalach, huetarget, fillTrue, axaxes[1]) axes[1].set_title(Max Heart Rate by Target) plt.tight_layout() plt.savefig(assets/distribution.png, dpi150) plt.show()这段代码把两张密度图并排放置huetarget会自动按0/1分组并绘制两条曲线fillTrue让面积带透明度重叠部分能直观看出分布偏移。保存成PNG的步骤很关键后续生成PPT演示时只需要add_picture插入图片不需要重新跑一遍绘图逻辑。对age图来说如果两条曲线中心位置只差一两岁可能单变量区分度不够强对thalach来说通常患病组峰值向左移也就是最大心率偏低这个特征会是模型里很重要的一维。3.2 相关性矩阵与分组统计表画完分布图接下来要看特征之间的相关性以及不同字段在target分组下的数值差异。相关性矩阵适合发现多重共线性比如oldpeakST段压低和slopeST段斜率在医学含义上相关如果两个都进模型它们的系数解释会互相干扰。分组统计表则是为了在报告里写“两组人群在X指标上有显著差异”这类结论。cols [age, trestbps, chol, thalach, oldpeak, target] corr df[cols].corr() plt.figure(figsize(8, 6)) sns.heatmap(corr, annotTrue, fmt.2f, cmapcoolwarm, vmin-1, vmax1) plt.title(Correlation Matrix) plt.savefig(assets/correlation.png, dpi150)df[cols].corr()默认使用皮尔逊相关系数对线性关系敏感。热力图里annotTrue会把数值标在格子里方便直接读数字cmapcoolwarm用红蓝双色区分正负相关暖色正相关、冷色负相关。看这张图的时候不要只盯着和target的相关系数还要看特征之间有没有绝对值超过0.7的配对如果有后续建模时就要考虑删掉一个或做正则化否则逻辑回归的系数会变得不稳定。数值型特征的组间差异可以继续用groupby加agg来算按target分组对关键字段求均值和标准差生成一张可以直接放进报告正文的统计表。标准差比单纯均值更能反映组内波动写分析结论时也更有说服力。3.3 分类变量怎么展示才不踩坑sex、cp、exang、slope这些字段是分类变量画图的方式不能照搬连续变量。用histplot画它们会自动变成柱状图但不如直接看分组比例直观。更好的做法是统计每个类别下target1的占比然后画条形图。cp_group ( df.groupby(cp)[target] .agg([mean, count]) .reset_index() ) cp_group[positive_rate] cp_group[mean] * 100 plt.figure(figsize(7, 4)) sns.barplot(datacp_group, xcp, ypositive_rate, palettecrest) plt.ylabel(Positive Rate (%)) plt.title(Disease Rate by Chest Pain Type) plt.savefig(assets/cp_rate.png, dpi150)这里的cp取值在Cleveland集中有歧义不同版本的说明文档对1-4的标签不完全一致所以我建议在报告里不要纠结具体哪种胸痛类型的名称而是强调“不同cp取值的患病率差异明显最高和最低能差到30个百分点以上”。palettecrest是seaborn自带的一套色板适合白色背景。柱子的高度是阳性率而不是样本量这一点要在图标题或caption里写清楚否则看图的人容易误读成人数。4. 用scikit-learn训练分类模型从逻辑回归基准到随机森林调参4.1 构造训练集并划分评估集建模步骤不能省略特征与目标分离。把target从特征矩阵中拆掉然后用train_test_split留出20%的数据作为测试集。分层抽样这个参数经常被忽略但对二分类来说非常重要尤其当正负样本比例不是1比1时不分层可能导致测试集里某一类样本过少评估结果波动很大。from sklearn.model_selection import train_test_split feature_cols [c for c in df.columns if c not in [target, num]] X df[feature_cols] y df[target] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy ) print(ftrain: {X_train.shape}, test: {X_test.shape}) print(y_test.value_counts())第一行里的列表推导式把除target和num之外的所有列都当作特征好处是以后加了新字段不用改这里。stratifyy会确保训练集和测试集里正负样本比例和原始数据一致random_state42固定随机种子这样别人复现你的代码时能得到一模一样的划分结果。print两行的目的一个是确认维度另一个是看测试集里两类样本数量是否接近如果某一类只有个位数说明切分可能有问题。4.2 逻辑回归做基准三个必调参数逻辑回归在医学数据里依然是很好的起点因为它的系数自带可解释性能直接在报告里写“某特征每增加一个单位患病几率变化多少”。但默认参数跑出来的结果只能当基准线要调的是下面三个点C正则化强度的倒数、max_iter迭代次数、class_weight类别权重。Cleveland数据里target1的样本通常是多数如果不处理类别不平衡模型会倾向把样本预测为1。from sklearn.linear_model import LogisticRegression from sklearn.preprocessing import StandardScaler from sklearn.pipeline import make_pipeline model make_pipeline( StandardScaler(), LogisticRegression( C0.5, max_iter1000, class_weightbalanced, random_state42 ) ) model.fit(X_train, y_train)StandardScaler放在管道里先标准化再进逻辑回归避免chol数值在200-400这种大数值变量主导梯度更新。C0.5表示中等正则化强度数值越小正则化越强防止系数过大。max_iter1000是保险丝因为默认100次可能不收敛特别是数据量小但有较多类别特征时。class_weightbalanced会自动按类别频率调整权重让少数类被错分的代价更高。跑完这段代码可以用model.named_steps[logisticregression].coef_查看系数排序后找出对患病几率影响最大的正向和负向特征。4.3 随机森林调参并对比AUC与混淆矩阵逻辑回归有可解释性优势但拟合复杂非线性关系的能力有限。随机森林是下一步的常见选择三个最关键的超参数是n_estimators树数量、max_depth树深度、min_samples_leaf叶子节点最少样本数。为了不手工试几十组组合直接在GridSearchCV里搜一组网格即可。from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import GridSearchCV rf RandomForestClassifier(random_state42, class_weightbalanced) param_grid { n_estimators: [100, 200], max_depth: [3, 5, None], min_samples_leaf: [1, 2, 4] } grid GridSearchCV( rf, param_grid, cv5, scoringroc_auc, n_jobs-1 ) grid.fit(X_train, y_train) print(grid.best_params_) print(grid.best_score_)GridSearchCV里cv5表示5折交叉验证会把训练集再切成5份轮流当验证集。scoringroc_auc告诉sklearn用AUC而不是准确率来挑选参数因为AUC对类别不平衡更稳健。n_jobs-1让所有CPU核心并行跑能明显缩短调参时间。跑完以后best_params_会打印出最优组合注意网格范围不要太大这个数据集只有303条树数量超过200对性能提升很小反而增加训练时间。最终用grid.best_estimator_对测试集做预测打印混淆矩阵和ROC曲线下的面积和逻辑回归的结果放在一起对比。如果随机森林的AUC比逻辑回归高不到0.02报告里就可以说两者性能相当解释性强的那一方更值得选用。5. 把分析结果落成报告与PPT演示三种可复用路径5.1 用jupyter nbconvert导出带图表的Markdown报告分析做完之后要形成报告常见做法是先把notebook整理成一个从标题、字段说明、清洗过程、图表到结论和模型评估都齐全的文档再统一导出。如果不想手动复制图表和代码输出可以用nbconvert把notebook直接转成Markdown或HTML图片会自动以相对路径方式嵌入。jupyter nbconvert --to markdown heart_analysis.ipynb --output-dir ./report这条命令把heart_analysis.ipynb转成report/heart_analysis.md包括所有代码输出里的图表。用Markdown的好处是后续可以再挂到GitLab或GitHub Pages上也可以直接交给团队在Typora里做二次编辑比提交一个巨大的HTML文件更利于版本管理。转出来之后检查一下图表路径nbconvert通常会把图片存到同名文件夹偶尔会因为notebook里的plt.savefig路径和输出目录不一致而显示不出来。5.2 用python-pptx生成PPT演示的模板代码PPT演示更推荐用python-pptx直接从代码生成而不是手动做十张幻灯片。原因是你之后只要数据一更新重新跑一遍脚本就能生成新PPT。下面这段代码是核心模板建立演示文稿、加标题页、插入之前的图表和结论文本。from pptx import Presentation from pptx.util import Inches prs Presentation() title_layout prs.slide_layouts[0] slide prs.slides.add_slide(title_layout) slide.shapes.title.text 心脏疾病数据分析 content_layout prs.slide_layouts[1] slide2 prs.slides.add_slide(content_layout) slide2.shapes.title.text 年龄与最大心率分布 slide2.shapes.add_picture( assets/distribution.png, leftInches(0.8), topInches(1.6), widthInches(8) ) bullet slide2.placeholders[1].text_frame bullet.text 患病组最大心率整体偏低 bullet.add_paragraph().text 年龄分布两组差异不显著 prs.save(output/Heart_Disease_Analysis.pptx)slide_layouts[0]是标题布局slide_layouts[1]通常是标题加内容布局具体编号在不同模板下略有差异建议先用len(prs.slide_layouts)确认模板里的布局数量。add_picture必须传入图片文件的绝对或相对路径图片尺寸用width参数控制高度会自动等比缩放通常不用再指定height。placeholders[1]是内容占位符它的text_frame支持多段文字第一段用.text赋值之后的段落用add_paragraph追加这样就能做出一张带标题、图表和三点结论的幻灯片。5.3 报告和PPT里必放的4类图表与结论结构做出来的材料和给别人讲其实是两回事。报告里信息密度越高越好但PPT幻灯片上每页只讲一个核心结论。我梳理了四类必放内容第一是数据概览表包含样本量、字段数、目标变量正负样本比例第二是分布图至少包含age和thalach的分组密度图第三是相关性热力图方便读的人一眼看到哪些特征和target相关最后是模型对比表格列出逻辑回归和随机森林的准确率、AUC、精确率和召回率。PPT的结论页不要写大段分析过程而是用两三行字把模型结果讲清楚。比如“随机森林在测试集上AUC达到0.88高于逻辑回归的0.84但性能差距在交叉验证中并不显著”这种说法既体现你做了实验对比又不会夸大结果。最后一页放后续建议即可例如可以尝试加入年龄分箱、用SHAP解释模型输出或者收集更多样本减小标准差。整个流程跑通之后这套“代码-数据-报告-PPT”的交付结构就能复用到其他UCI分类数据集上只需替换字段名和结论。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询