ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

城市居民健康影响因素分析:从数据清洗到可解释性建模的完整实践

城市居民健康影响因素分析:从数据清洗到可解释性建模的完整实践 简介本资源是2023年“深圳杯”数学建模挑战赛A题的完整参赛成果包面向高校数学建模参赛学生、指导教师及数据分析初学者聚焦慢性非传染性疾病心脑血管病、糖尿病、恶性肿瘤、慢阻肺影响因素的实证分析问题。资源共15个文件含8份PDF含赛题原文、调查问卷、膳食指南准则、完整论文与目录、4个Python脚本preprocess.py数据清洗、plot.py可视化、analyse.py统计建模、main.py主流程调度及3份Markdown文档含论文主体、README与赛事说明总大小11.88MB结构清晰、模块分工明确覆盖数据预处理→探索性分析→多变量建模→健康干预建议全流程。已有705人学习下载提供可直接运行的代码、符合学术规范的论文框架、基于真实流调数据的建模思路以及膳食准则与健康指标的交叉解读逻辑助力快速理解赛题内核并复现高质量解决方案。1. 拿到题目先别急着跑模型1.1 题目到底在问什么2023年“深圳杯”数学建模挑战赛A题这个题目全称叫“影响城市居民身体健康的因素分析”。单看这个题目很多人第一反应是“这有什么好分析的不就是拿一堆变量跑个回归吗”。实际做完整个赛程我的体会是这类题恰恰是数学建模竞赛里最容易翻车、也最考验建模基本功的题型。为什么这么说因为“影响居民身体健康的因素”不是一道凭空出给你的纯数学题它背后站着完整的流行病学和社会统计学框架。题目不会明着告诉你应该用哪个模型也不会告诉你数据长什么样更不会告诉你健康指标怎么量化。你要自己把这些东西从头到尾定义清楚再用建模语言翻译出来。这个项目的整体目标可以拆成三层第一层找出数据集里哪些因素和居民健康状况显著相关比如年龄、收入、运动频率、睡眠、饮食结构这些变量到底哪个对健康影响最大。第二层把“影响”的量化关系做出来不仅要说“运动影响健康”还要能回答“运动量每增加一个单位健康指标大概往哪个方向变、变化多少”。第三层在模型基础上给出可落地的政策或干预建议比如哪些人群是高风险群体优先改善哪些因素性价比最高。三层目标对应的交付物也明确一套能复现的数据处理流程、一份说明文档、一篇完整的建模论文。这套东西打包在一起就是一个典型的数据分析型数模竞赛参赛作品。1.2 破题从数据角度想问题我见过很多队伍拿到这类题第一件事就去翻机器学习教材想直接上随机森林、XGBoost这其实是本末倒置。这类城市居民健康问题数据往往来自问卷调查、城市统计年鉴或公开健康数据库它的典型特点是样本量大、变量杂、缺失值多、变量之间相关性高还有大量分类变量需要做编码处理直接套用高级模型反而容易翻车。这个项目里我是这么破题的先不看题目有没有给现成数据而是先逼自己回答三个问题。健康用什么指标来衡量是慢性病患病率、BMI、自评健康得分还是综合多个指标构建一个健康指数这个指标决定了后面所有模型的目标变量长什么样。影响因素的维度怎么划分常见的是人口学特征年龄、性别、受教育程度、生活方式运动频率、睡眠时长、吸烟饮酒习惯、社会经济状况收入水平、职业类型、环境因素居住地空气质量、绿化率维度划分决定了特征工程的结构。方法和工具选什么用Python还是R用统计检验还是机器学习模型用传统回归还是树模型团队里谁负责写哪一块。这三件事想清楚之后题目才真正变成“可实施的工程项目”。这个思路也直接决定了源码的目录结构、文档的写作重点、论文的组织框架后面每个环节都是围绕这个破题逻辑展开的。2. 数据清洗与探索性分析建模前最耗时的一环2.1 数据从哪来、长什么样深圳杯这种开放性题目通常不会像国赛那样给你打包好的标准数据集需要参赛者自己找数据或整理公开数据。这个项目里选用的数据来源是某城市居民健康调查的公开样本数据包含了两万多条有效记录字段覆盖人口学信息、生活方式、慢性病史和体检指标。拿到数据之后第一件事不是建模而是把数据完整读一遍搞清楚每个字段的含义、类型、缺失情况和取值范围。实际操作中我一般先做三件事用df.info()看字段概览用df.describe()看数值型字段的分布再用df.nunique()看每个字段的唯一值数量尤其是分类字段有没有异常取值。这段代码的副作用是几乎所有字段都会暴露出问题。比如年龄字段出现负数运动频率字段出现“每天3次”和“每天30次”这种明显矛盾的选项收入字段有大量缺失。这些在数据字典里看着干干净净的字段实际读出来全是坑。2.2 清洗是个体力活但有一些固定套路数据清洗没有太多技术含量但直接决定模型上线表现。我的清洗流程基本固定可以概括为五个步骤。第一步处理重复值。两万多条数据里找出完全重复或身份证号/编号重复的记录并去重这一步不需要什么高级技巧但必须做。第二步处理缺失值。先做缺失率统计缺失率超过40%的字段直接考虑删除缺失率较低的字段再判断填补策略。年龄、收入这类连续变量用中位数填补更稳受教育程度这类分类变量用众数填补。至于运动频率这种和健康强相关的字段我会先做个简单的分组检验看看缺失样本和未缺失样本在其他变量上有没有显著差异避免填补后引入偏差。第三步处理异常值。连续字段用3σ原则或IQR方法筛出离群点再结合领域常识判断是删除还是保留。比如BMI大于50的值基本是录入错误但年龄90岁以上如果身体指标正常可能是真实的老年样本不能一刀切删掉。第四步统一单位与取值范围。比如有的字段“吸烟频率”用0/1编码有的字段用“每天/每周/偶尔/从不”的文本需要统一映射成同一套编码规则。第五步构造派生变量。这个步骤很容易被新手忽略但效果非常明显。原始数据里可能没有“睡眠质量”这个字段只有“入睡时间”和“夜间醒来次数”就需要根据医学常识构造出一个综合指标。清洗完的数据要保证任何一个人拿到你的源码和数据按顺序跑一遍能复现出一模一样的结果。这也是数模竞赛源码部分的一个隐性评分点工程化能力在评委眼里是加分项。2.3 可视化找直觉不要跳过这一步数据清洗完了直接建模是大忌。我的习惯是先做一轮探索性可视化这一步的核心目的不是给论文配图而是让团队里的每个人都对数据建立直观感受。常用的可视化大概这么几类单变量分布图看目标变量健康指标是否近似正态有没有明显的偏态是否需要对数变换。目标变量与核心特征的交叉图比如分年龄段看健康评分均值看不同运动频率组的健康差异。相关矩阵热力图一眼看出哪些变量高度相关提前预判多重共线性问题。这轮可视化做下来我基本能写出一个“三句话摘要”健康指标呈右偏分布中老年群体健康评分显著低于年轻群体。运动频率和睡眠时长与健康评分的相关性强收入的影响没那么明显。教育和收入高度相关建模时不能同时放进线性模型。这三句话虽然简单但它是后面所有建模选择的依据。比如看到健康指标右偏我就知道做线性回归之前大概率需要对目标变量做变换看到教育和收入高度相关我就知道线性模型要处理共线性问题或者干脆后面换用树模型来规避这个坑。3. 建模路线从基线模型一路升级到可解释模型3.1 先做基线用传统统计模型打通流程建模阶段我的顺序是先搭一个简单但完整的全流程跑通基线再逐步升级模型。这个思路在竞赛里很重要——先把整套数据管线跑起来保证不报错、能出结果、能画图、能写进论文后面换模型只是局部替换不会手忙脚乱。基线模型我选了多元线性回归。目标变量是健康评分处理过的连续变量特征先做标准化分类变量做独热编码连续变量直接进入模型。之所以第一步选线性回归而不是XGBoost有三个原因训练快几秒钟出结果方便快速检查数据管线有没有问题好解释回归系数直接体现正负关系和影响大小心里有底能作为后续复杂模型的基线对照后面模型如果表现还不如线性回归说明特征工程有问题而不是模型不够高级。跑完之后我还会顺手做几件事画残差图检查是否满足线性回归的基本假设算VIF检查多重共线性用逐步回归或Lasso做一轮特征筛选。如果残差图显示明显的异方差或非线性模式就说明线性模型不够用要考虑更灵活的模型。3.2 树模型是这个题目的天然适配选择线性模型跑通之后我通常会加一个随机森林或梯度提升树。原因很实际城市居民健康数据里很多变量对健康的影响是非线性的比如睡眠时长和健康的关系是倒U型太长太短都不好这种结构用线性模型很难拟合但树模型天然擅长捕捉这种复杂交互关系。训练之前先划分训练集和测试集保持7:3的比例用分层抽样确保目标变量的分布一致性。树模型的调参围绕四个关键参数展开树的数量、最大深度、最小叶子节点样本数和特征采样比例。实操中我的经验是先用默认参数跑一遍看基线效果然后用手动搜索或网格搜索微调不要一上来就上贝叶斯优化时间成本高收益有限。调参的结果记录大概是这样的随机森林n_estimators300max_depth12min_samples_leaf5测试集R²在0.41左右梯度提升树学习率设置为0.05树数量200左右测试集R²在0.45上下。说实话这个R²在健康数据的预测任务里已经不算差了因为影响健康的因素太多问卷数据能解释40%左右的方差已经能说明很多问题。3.3 结果解释比预测本身更重要预测不是这个题目的终点。深圳杯这类比赛尤其是A题这种社会应用类题目评委更看重你能不能从模型结果里读出有价值的结论。这里我用到了特征重要性feature importance排序。随机森林输出特征重要性时排在最前面的几个因素基本稳定运动频率、年龄、睡眠时长、收入水平、吸烟史。这个结论在领域上完全说得通和流行病学研究的主流结论也一致说明模型确实学到了真实的规律。但特征重要性只告诉我们“哪些变量重要”不能告诉我们“影响方向和幅度”。为了把结论写得更扎实我另外做了部分依赖图PDP单独看健康评分随运动频率、睡眠时长变化的大致趋势。这两个图基本上就是论文里最有说服力的两张结果图。再往后我还会用SHAP值做一轮补充分析。SHAP相比特征重要性的优势在于它可以逐个样本解释看不同特征对不同人群的具体影响。比如对年轻人来说运动频率的SHAP值贡献更大对老年人来说慢性病史的贡献更大。这种分人群的结论能直接支撑论文里的政策建议部分。3.4 模型对比与选择的决策依据做完线性回归、随机森林、梯度提升树之后我整理了一张对比表把每一类模型的训练集效果、测试集效果、训练时间和可解释性打分明明白白列出来。表格的结构大致如下多元线性回归训练集R²0.31测试集R²0.28训练时间几秒可解释性高但拟合能力明显不足。随机森林训练集R²0.52测试集R²0.41训练时间几十秒可解释性中高。梯度提升树训练集R²0.58测试集R²0.45经过调参后测试集效果最好训练时间略长但可接受可解释性中高需要借助SHAP辅助解读。这个对比表的价值在于它给论文提供了“为什么最后选梯度提升树”的直接依据。不是因为它最复杂而是因为它在测试集上效果最好同时可以通过SHAP解释。评委看论文时最反感“上来就选了一个高级模型”有这样一个从简单到复杂的对比过程才是完整的建模思路。4. 源码组织与文档规范建模之外的另一半分数4.1 目录结构里的工程化思维很多参赛队伍不重视代码组织和文档质量但实际上这个打包文件里“源码文档论文”三件套是完整一体的。源码不是论文的附属品而是论文结论的工程支撑。合理的项目目录能帮你在比赛最后一天检查代码时省下大量混乱时间。我这个项目的目录组织大致如下project/ ├── data/ │ ├── raw/ # 原始数据 │ ├── processed/ # 清洗后的数据 │ └── external/ # 外部补充数据 ├── notebooks/ │ ├── 01_数据探索.ipynb │ └── 02_可视化分析.ipynb ├── src/ │ ├── data_preprocess.py # 数据清洗与特征工程 │ ├── model_linear.py # 线性回归模型 │ ├── model_tree.py # 树模型训练与调参 │ ├── explain.py # SHAP与特征重要性分析 │ └── utils.py # 公共工具函数 ├── results/ │ ├── figures/ # 图表输出 │ ├── tables/ # 结果表格 │ └── model/ # 训练好的模型文件 ├── docs/ │ └── 项目说明.md └── README.md这个结构的核心思想是“数据、代码、结果、文档四层分离”。原始数据永远不手动修改所有清洗逻辑都要在代码里体现分析和建模分开notebook负责探索正式代码负责产出可复现的结果。README从零开始写清楚环境和依赖保证任何人在一个新的conda环境里装好依赖就能原样复现全部结果。4.2 requirements与README的细节源码能不能被别人直接跑起来是“源码”能否加分的关键。我习惯在项目里附一个requirements.txt把所有依赖包和版本号列清楚包括pandas、numpy、scikit-learn、matplotlib、seaborn、shap这些核心库。README文档除了放目录结构和环境配置外我会额外写清楚三件事每个脚本的执行顺序和执行逻辑例如先跑data_preprocess.py生成processed数据再跑model_linear.py和model_tree.py最后跑explain.py得到解释性分析结果。数据集来源和字段说明这能极大减少别人理解代码的时间成本。关键参数说明包括随机种子、测试集比例、树模型调参后的参数值保证可重复性。比赛中后期有一个很常见的悲剧代码先跑通了一次后来改了数据或模型结构但README没同步更新最终提交时队友照着旧文档跑代码直接报错折腾到交卷前。所以我现在的习惯是每次改完代码顺手把README和注释一起更新这一分钟的时间能省下决赛前的一小时。4.3 notebook用在哪一层关于notebook的使用边界我也踩过一些坑。初赛阶段用notebook做探索性分析非常顺手画图方便代码和结果放在一起方便记录思路。但正式建模阶段如果还把所有代码都写在notebook里问题就会开始暴露单元格执行顺序变了结果就不同了可复现性极差。notebook里代码和输出混在一起改完代码之后忘记重新执行整页输出和代码对不上。多人协作时notebook合并冲突非常麻烦。所以我在这个项目里的规则是探索性分析放notebook正式建模流程沉淀为.py脚本统一从命令行调用用参数控制数据集路径和输出路径。这样做的好处是结果和代码严格对应脚本可以重新生成任何一张图表或一个指标论文里每张图都能溯源到具体参数。5. 论文写作的重点与节奏5.1 论文结构怎么排论文是这个项目打包文件里最重的交付物。数模论文的结构大体遵循一个固定框架摘要、问题重述、模型假设、数据来源与预处理、模型构建与求解、模型评价与改进、结论与建议大概七到八个部分。但深圳杯A题这类题目我的写作重心会有所调整。在标准化结构的基础上我会加大“数据来源与预处理”和“结果讨论”两块的篇幅因为这两个部分才是这道题区别于纯数理题的关键。城市居民健康问题本质上是应用型研究评委更关心你对数据理解的深度和结论的可信度。摘要部分我用三句话交代核心做了什么、用什么方法、得到什么结论。其中关键词要突出“梯度提升树”“SHAP解释”“影响因素排序”“政策建议”四个点。摘要不需要面面俱到但要让人一眼看出你的工作主线。5.2 结果呈现的常见细节问题论文的结果呈现有一些小细节稍不注意就会让评委觉得不严谨。比如表格里的数字格式要统一保留两位小数所有模型对比表的指标口径要一致不能线性回归放R²树模型放准确率这样没法横向比较。比如数据可视化部分图要自明不需要看正文就能读懂。坐标轴标签、单位、图例、显著性标记都要齐全。字体大小要考虑到打印或PDF缩小后依然可读。比如结论部分描述模型结果时不要只说“运动频率影响健康”要给出量化说法“在本数据集范围内运动频率每提升一个等级健康评分平均提升约2.3分且该效应在控制年龄、收入和其他协变量后依然显著。”这种写法的说服力远超一句定性的判断。论文写作的节奏上我的建议是不要等到模型全部调完再动手写。数据处理跑通之后就可以先写数据预处理部分基线模型跑完先写模型框架部分等最终模型确定后再补上结果与分析。这样整体效率高很多。6. 比赛全程踩坑实录6.1 数据对齐的坑这个项目里踩的最深的一个坑是特征编码不一致。前期的探索分析中我用pandas的factorize把“运动频率”这个文本字段转成了整数编码后面正式建模时换成了LabelEncoder同样是0到4的五个整数但编码顺序完全不一样。模型训练完之后再做特征重要性分析和模型解释时才发现变量名和编码逻辑对不上了。这个问题的教训很简单数据预处理中的编码映射关系一定要事先定义好并写到配置或文档中所有脚本共用同一个映射不能每个脚本各自编码。能避免这个坑的推荐做法是设定一个data_dictionary.md把所有分类变量的取值和编码一一列清楚。6.2 多重共线性处理最初用线性回归做基线时教育年限和收入水平这两个变量的影响系数符号不符合预期收入系数居然是负的。检查后确认是VIF太高两个变量存在严重共线性导致回归系数估计不稳定。处理方式有两种可选一个是只保留其中一个变量另一个是进行主成分回归或岭回归。考虑到论文里还想分别讨论教育和收入的影响我在最终模型选择里直接用了树模型天然规避了共线性问题然后在树模型上通过SHAP值单独看每个变量的贡献。这个处理思路写在论文里也体现了模型选型层面的合理性。6.3 时间分配的教训比赛过程中最痛的还不是技术问题而是时间分配。我在探索性分析和可视化上花了太多时间一度觉得每个图都很有意思每个分布都想深挖结果留给模型调参和论文打磨的时间明显不足。复盘来看合理的时间分配大概是数据清洗和探索性分析占三成时间模型构建与调参占三成结果解释与可视化完善占两成论文写作和格式调整占两成。这个比例不是死的但核心是给论文留出足够的时间因为论文的格式问题、图表编号、参考文献规范这些小事往往在截止前半小时才发现改起来极其烦躁。6.4 模型效果的真实预期最后想聊一下模型效果的预期管理。健康相关数据的预测任务R²能到0.4以上已经算比较理想了。很多新手做这类题目看到测试集R²只有0.4就觉得模型不行然后盲目堆特征、堆模型反而过拟合得不偿失。我自己做过的一些相似的数据集结论都类似问卷调查形式的健康数据噪声很大个人健康还受基因、生活环境等没采集到的因素影响能被模型解释的方差本来就有限。这个时候模型的价值不在于精确预测每一个个体的健康评分而在于从群体水平上识别出最重要的影响因素为政策建议提供定量依据。这种思路也是写论文时“结论与建议”部分的理论支撑——不是给某个具体人开处方而是基于群体数据给出一般性规律。本文还有配套的精品资源点击获取
返回列表