ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于Python数据挖掘的预期寿命影响因素分析与建模

基于Python数据挖掘的预期寿命影响因素分析与建模 人均预期寿命这个指标近几年在公共卫生和经济研究里出镜率特别高。它看着简单——一个国家或地区的人平均能活多少岁——但背后牵扯的因素极多医疗资源、免疫覆盖率、经济水平、教育投入、生活习惯、传染病负担……到底哪些因素影响最大影响程度是多少这正是数据挖掘最擅长回答的问题。我最近完整跑了一个 Python 数据挖掘项目代号 hx4065对一组覆盖全球 180 多个国家和地区、跨越 2000 到 2015 年的人均预期寿命面板数据做了一整套从清洗到建模的分析。这篇文章就把整个项目的完整流程、关键代码、踩坑经验全部复盘一遍给你一套可以直接照着复现的完整方案。这个项目适合三类人看一是正在学 Python 数据分析、想练手完整数据挖掘流程的初学者二是做社科、公共卫生相关研究需要处理面板数据的朋友三是对“到底什么在决定人的寿命”这件事本身有好奇心、想知道数据怎么回答这个问题的人。我在里面每个环节都会讲清楚当时的选型理由和实操细节而不只是贴一段代码。1. 项目整体设计与思路拆解1.1 为什么选“人均预期寿命”作为分析主题数据挖掘项目最怕的不是没有算法而是没有好问题。预期寿命这个指标天然适合做数据挖掘原因有三个。第一它的数据足够开放。世界卫生组织WHO有公开的生命统计数据库很多机构也基于它整理过分析用的面板数据集里面除了预期寿命本身还包括成人死亡率、婴儿死亡率、HIV 感染率、免疫接种率、医疗支出、GDP、教育年限等几十个维度的字段。这种“一个目标变量 多维度特征”的结构非常适合做相关性分析、回归建模和特征重要性排序。第二它有明确的现实意义。预期寿命是联合国可持续发展目标里的核心健康指标分析它的影响因素可以直接落到公共卫生政策评估、医疗资源配置这些具体问题上。第三它没那么“脏”。相比电商点击流、用户行为日志这类海量高噪声数据预期寿命面板数据规模适中几千条级别结构清晰但又不是完全干净的——有缺失值、有异常值、有不同国家的量纲差异处理起来能练到数据挖掘的大部分基本功又不至于被数据清洗劝退。1.2 技术栈选型为什么是 Python 这套组合这个项目的核心工具链是 Pandas NumPy Matplotlib/Seaborn Scikit-learn都是 Python 数据挖掘的标配。我解释一下每部分的角色方便新手理解。Pandas 负责数据读取、清洗、聚合和特征工程——面板数据的各种纵向横向操作用 DataFrame 处理非常顺手。NumPy 主要是配合做数值计算比如缺失值填充时的分位数计算。Matplotlib 和 Seaborn 负责可视化探索性分析阶段画趋势线、相关性热力图靠它们把数据里的结构“看”出来。Scikit-learn 负责建模包括数据集划分、线性回归、随机森林、梯度提升树还有交叉验证和评估指标。为什么不选 Spark 或深度学习框架因为这份数据只有几千条记录、几十个特征属于典型的小规模表格数据用单机 Pandas Scikit-learn 就是最高效的方案引入分布式计算反而徒增复杂度。没必要为了“显得高级”去堆技术栈这是做数据挖掘项目最需要想清楚的事。1.3 整体分析框架从问题到结论的完整路径我拿到这个项目后没有直接开始写代码而是先画了一条分析路径后面所有工作都是按这条线推进的。第一步是明确问题预期寿命的变化受哪些因素影响能不能建立可解释的预测模型。第二步是数据准备做缺失值处理和异常值检查这一步直接决定分析质量。第三步是探索性数据分析EDA通过可视化和相关性矩阵先对数据形成整体直觉。第四步是特征工程补充分组变量、派生指标。第五步是建模与评估用线性回归做基线再用树模型做提升最后对比效果。第六步是结论解读输出特征重要性排序落到业务解释上。这个路径的核心思想是“先理解再建模”。很多人一上来就调包跑模型结果数据里全是坑却不自知模型跑出来的数字再漂亮也是废的。我下面每个环节都会给你看具体代码和当时的判断逻辑。2. 数据准备与清洗实操2.1 数据集结构与字段解析我用的是 WHO 全球预期寿命数据集按“国家-年份”为粒度一共 2938 条记录覆盖 2000 到 2015 年。每条记录包含 22 个字段这里挑关键字段做个说明字段名含义数据类型实际作用Country国家/地区对象实体标识Year年份整数时间维度Status发展状态发达国家/发展中国家对象分层对比变量Life expectancy出生时预期寿命岁浮点目标变量Adult Mortality成人死亡率每千人浮点疾病负担指标Infant Deaths婴儿死亡数浮点妇幼健康指标Alcohol人均酒精消费量升浮点生活习惯指标Percentage Expenditure卫生支出占个人支出比例浮点医疗投入指标Hepatitis B乙肝免疫覆盖率浮点免疫接种指标Measles麻疹发病数浮点传染病负担指标BMI平均身体质量指数浮点营养状况指标Polio脊灰免疫覆盖率浮点免疫接种指标Total Expenditure卫生总支出占GDP比例浮点医疗投入指标Diphtheria白喉免疫覆盖率浮点免疫接种指标HIV/AIDS艾滋病致死率浮点重大传染病指标GDP国内生产总值美元浮点经济水平指标Population人口数浮点规模控制变量Schooling平均受教育年限浮点教育水平指标这个结构比较理想既包含宏观经济学指标也包含医疗卫生和疾病负担指标适合做多维度归因分析。2.2 缺失值处理按发展状态分组填充的中位数方案读入数据之后第一步是检查缺失情况。当时我跑了一下 info()发现 Missing 值主要集中在 Hepatitis B、BMI、Total Expenditure、Schooling 这几个字段整体缺失比例在 5% 到 20% 之间。面对缺失值有删除、均值填充、中位数填充、模型预测填充等方案我选了按 Status 分组的组内中位数填充。为什么不是全样本均值填充因为这个数据集中发达国家和发展中国家的卫生经济指标差异非常大如果拿全样本均值去填一个发展中国家的 Schooling 缺失值会直接把它拉高好几个点严重扭曲后续的分析。按 Status 分组填充相当于默认“同类国家的该指标更接近”这个假设在业务上更站得住脚。import pandas as pd import numpy as np df pd.read_csv(life_expectancy.csv) df.columns df.columns.str.strip() # 清洗列名 # 查看各列缺失比例 missing_rate df.isnull().mean().sort_values(ascendingFalse) print(missing_rate[missing_rate 0]) # 按 Status 分组的组内中位数填充 fill_cols [Hepatitis B, BMI, Total Expenditure, Schooling, Alcohol, GDP, Population, Income composition of resources] for col in fill_cols: df[col] df.groupby(Status)[col].transform(lambda x: x.fillna(x.median()))这一段代码里 transform 的作用是把分组计算的中位数映射回每一行而不是生成一个一列的分组汇总表这样填充才能落到原数据的对应位置上。新手很容易在这里踩坑一用 groupby 就丢维度transform 就是专门解决这个问题的。2.3 异常值与逻辑校验不是所有“怪值”都要删聚合数据里的异常值处理比想象中要谨慎。我当时的检查方式是先看描述性统计再结合业务逻辑判断。比如预期寿命最小值出现 36 左右是不是异常我查了原始数据这是某些国家在特定年份受战乱、重大传染病等因素影响下的真实记录虽然极端但不是录入错误因此保留。还有一类典型问题是部分字段的 0 值。比如 Measles 这个字段有大量 0它可能代表当年没有麻疹发病记录是真实的业务零值不是缺失值。这里就特别强调做数据挖掘时要区分缺失值、零值和异常值三者的处理策略完全不同。如果你把零值当成缺失值填充等于凭空制造了错误数据后面所有相关性分析都会被污染。我当时用 Z-score 做了一轮快速扫描只对那些明显不合理的极端值做了处理。比如某条记录的 BMI 超过 60这在成年人中几乎不可能直接做删除处理。删之前还从原始数据里复核了具体是哪一年的哪条记录确认不是字段错位。3. 探索性数据分析与关键发现3.1 全球预期寿命的时序趋势分收入组看更有意义把所有国家混在一起画时间序列预期寿命的上升趋势会被平均掉很多细节所以我按 Status 做了分组。下面是核心代码import matplotlib.pyplot as plt import seaborn as sns plt.rcParams[font.sans-serif] [SimHei] # 解决中文乱码 plt.rcParams[axes.unicode_minus] False trend df.groupby([Year, Status])[Life expectancy].mean().unstack() trend.plot(figsize(10, 6)) plt.title(2000-2015年分发展状态预期寿命趋势) plt.ylabel(预期寿命岁) plt.tight_layout() plt.show()从趋势图里能清楚看到两组差异发达国家预期寿命始终高于发展中国家两者差距保持在 10 岁以上两组都在缓慢上升但发展中国家的上升斜率更大说明追赶趋势在发生。还有一个细节值得注意2005 到 2010 年之间部分区域的曲线出现小幅度波动这和当时的区域传染病暴发、局部冲突是吻合的数据挖掘的价值恰恰体现在把这些宏观事实从数字里“挖”出来。3.2 相关性热力图哪些指标和预期寿命最相关探索性分析中最关键的一步是把所有数值特征和目标变量的相关性一次性算出来。我用 Pandas 的 corr() 和 Seaborn 的热力图做了可视化。import seaborn as sns num_cols df.select_dtypes(include[np.number]).columns corr_matrix df[num_cols].corr() plt.figure(figsize(12, 10)) sns.heatmap(corr_matrix, annotFalse, cmapRdBu_r, center0, linewidths0.1, cbar_kws{label: Pearson相关系数}) plt.title(各特征与预期寿命的相关性热力图) plt.tight_layout() plt.show()把热力图拉出来看有几个非常醒目的结论。预期寿命与 Adult Mortality成人死亡率的相关系数约为 -0.83是最强的负相关指标这符合医学常理一个国家的成人死亡负担越高整体预期寿命必然被拉低。HIV/AIDS 与预期寿命的相关系数约为 -0.52也是显著的负相关因子。正向相关里最强的是 Schooling受教育年限约 0.75、Income composition of resources资源收入构成指数约 0.72和 BMI约 0.48。看到这里可以做一个初步判断经济和教育水平通过提升医疗可及性和健康意识来影响寿命而这个结论在后面的模型特征重要性会再次得到印证两条路径最终汇合。3.3 分组对比用箱线图看不同维度的差异在相关性矩阵之外我还做了一组分组箱线图把 Status 这个分类变量和预期寿命的关系画出来。箱线图能揭示分布形态而不仅仅是均值。plt.figure(figsize(8, 6)) sns.boxplot(datadf, xStatus, yLife expectancy, paletteSet2) plt.title(发达国家与发展中国家的预期寿命分布) plt.tight_layout() plt.show()发达国家组的箱体明显整体上移而且下须和中位数之间的距离更小说明发达国家内部预期寿命水平更均质发展中国家组箱体更长下须延伸到很低的位置说明发展中国家内部差异极大既有接近发达国家水平的高寿命地区也有因为疾病负担、经济落后而低于 50 岁的国家。这种内部异质性提醒我后续建模时单纯靠 Status 做哑变量可能不够用还要靠 GDP、HIV/AIDS、Schooling 这些连续变量去捕捉组内差异。另外一个很有价值的发现是发达国家存在少量低预期寿命的离群值我查了一下这通常对应的是某些经济发展水平较高但受重大疾病负担影响明显的东欧国家。这说明分类变量不能过度简单化真实世界的因素往往是多维度叠加的。4. 模型构建与预测对比4.1 建模前的数据准备划分与编码探索性分析做完之后我进入建模环节。第一步是把分类变量转换成模型可用形式这里我把 Status 做了标签编码因为它是二分类变量直接用 0/1 编码即可。然后把特征和目标变量分开按 8:2 划分训练集和测试集并且固定随机种子保证结果可复现。from sklearn.model_selection import train_test_split df_model df.copy() df_model[Status] df_model[Status].map({Developing: 0, Developed: 1}) X df_model.drop(columns[Life expectancy, Country, Year]) y df_model[Life expectancy] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) print(f训练集样本数: {X_train.shape[0]}, 测试集样本数: {X_test.shape[0]})这里特别说明一个取舍我没有把 Country 和 Year 放进特征。Country 如果做哑变量会产生 180 多个特征对这个小数据集来说维度太高容易引发过拟合Year 则因为它本身是时间序号和预期寿命的长期趋势存在线性关系放进模型会让模型退化成“按年份插值”模糊掉真实影响因素的分析。做可解释性分析时优先保留业务意义明确的特征更重要。4.2 三个模型同台对比线性回归、随机森林与梯度提升线性回归是我的基线模型它胜在可解释性能给出每个特征的系数和方向。随机森林和梯度提升树则用来捕捉非线性关系和高阶交互。这里直接贴三个模型的核心代码和评估逻辑。from sklearn.linear_model import LinearRegression from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor from sklearn.metrics import r2_score, mean_squared_error models { 线性回归: LinearRegression(), 随机森林: RandomForestRegressor(n_estimators500, random_state42), 梯度提升: GradientBoostingRegressor(random_state42) } results [] for name, model in models.items(): model.fit(X_train, y_train) y_pred model.predict(X_test) r2 r2_score(y_test, y_pred) rmse mean_squared_error(y_test, y_pred, squaredFalse) results.append({模型: name, 测试集R2: round(r2, 4), RMSE: round(rmse, 4)}) print(f{name}: R2 {r2:.4f}, RMSE {rmse:.4f})实际跑下来结果对比是这样的模型测试集 R2RMSE年线性回归0.78564.0217随机森林0.94831.9742梯度提升0.94282.0876随机森林效果最好测试集 R2 达到 0.948平均预测误差不到 2 岁。线性回归作为基线没有及格R2 只有 0.785说明特征和预期寿命之间存在明显的非线性关系比如 GDP 对预期寿命的边际贡献就是递减的——穷国经济增长对寿命提升很显著富国再增长基本无感这种关系线性模型很难刻画。4.3 特征重要性解读到底什么在决定寿命随机森林跑赢之后最关键的一步是把特征重要性打出来落到业务解释上。importances pd.Series(models[随机森林].feature_importances_, indexX.columns) print(importances.sort_values(ascendingFalse).head(10))我实际得到的特征重要性前五名是Adult Mortality成人死亡率、HIV/AIDS、Income composition of resources、Schooling、BMI。这个排序和前面相关性热力图的结论高度一致说明方向是稳定的。成人死亡率和 HIV/AIDS 这两个“疾病负担类”指标排在最前模型在告诉我们直接决定一个国家平均寿命的是它的人群健康损伤程度而不是单纯的经济体量。GDP 虽然在相关性分析中与预期寿命正相关但在重要性排序中被成人死亡率碾压这说明经济水平更多是“间接因素”——它通过改善医疗条件、降低疾病负担来延长寿命而模型直接抓住的是更本质的“死亡风险”指标。Schooling 进入前五也是一个很值得讨论的结果。教育程度影响健康意识和就医行为这是有大量实证研究支撑的。线性回归的系数同样显示 Schooling 每增加一年预期寿命约上升 0.4 岁左右方向可信。5. 常见问题与排查技巧实录5.1 缺失值过多导致模型直接报错这个项目的数据集缺失比例不算极端但我之前做类似项目时遇到过更脏的数据某个特征缺失率超过 70%。面对这种字段填充已经没有意义因为它携带的信息量不足强行填充只会给模型注入大量噪声。经验做法是先看缺失率超过 50% 的字段直接删除或者先保留做一版对比实验确认它不贡献增益之后再删。5.2 多重共线性让线性回归系数“变脸”在这个数据集里GDP、Income composition of resources、Schooling 两两之间的相关系数都在 0.7 以上存在明显的多重共线性。后果就是线性回归的单个特征系数不稳定你删一个特征另一个特征的系数符号和大小就变了很难用系数做“控制其他变量不变”的业务解释。处理办法有两个一个是做 VIF 检查VIF 大于 10 的特征考虑去掉另一个是改用随机森林这类树模型它们不受共线性影响特征重要性排序也更稳健。在可解释性和模型性能之间我选择把随机森林作为主模型线性回归只做方向验证。5.3 过拟合的典型信号训练集满分测试集拉胯随机森林如果 n_estimators 设置过小、树深度不限制很容易在训练集上 R2 接近 1.0但测试集一塌糊涂。我当时先跑了默认参数的随机森林训练集 R2 0.99测试集 0.91差距较大。通过调大 n_estimators 到 500、限制 max_depth 并配合交叉验证最终把差距缩小到 0.03 以内。这里的关键不是追求某个参数而是养成观察“训练-测试差距”的习惯差距过大就是过拟合信号差距过大但两者都低则是欠拟合。5.4 中文绘图乱码和 pandas 读取小坑Matplotlib 默认字体不支持中文图里的标题和图例全是方块。解决方法是设置中文字体Windows 上一般用 SimHeimacOS 用 Arial Unicode MS同时设置 axes.unicode_minus 为 False 解决负号显示问题。pandas 读取 CSV 时还有一个隐藏坑列名里如果有首尾空格直接访问 df[Life expectancy ] 会报 KeyError我养成了读取后立刻执行 df.columns df.columns.str.strip() 的习惯这个习惯省了我很多排查时间。5.5 常见问题速查表问题表现排查方向解决方案缺失值过多模型报错或特征重要性失真打印每列缺失率超过 50% 直接删其余按业务分组填充多重共线性线性回归系数异常、符号变化VIF 计算去除高相关特征或改用树模型过拟合训练集 R2 高但测试集差对比训练/测试指标差距调树深度、增 n_estimators、加交叉验证中文乱码图中文字显示为方块检查 matplotlib 字体设置设置 rcParams 字体为 SimHei列名空格KeyError打印 df.columns 查看读取后 strip 列名6. 项目扩展思路与下一步玩法这个项目的模型已经做到 R2 0.948我再提供几个可以直接在这个基础之上展开的方向方便你把它变成一篇更有深度的分析报告或课程项目。第一个方向是时间维度的深化建模。目前的模型把年份当成普通样本没有利用时间序列结构。你可以把单一国家截出来用 ARIMA 或 Prophet 做分国别的预期寿命时间序列预测对比不同区域未来 10 年的预期寿命走势。第二个方向是空间可视化。把预测结果或特征重要性按国家画到世界地图上用颜色深浅表示预期寿命区间做出来的图信息密度极高特别适合做数据新闻风格的输出。第三个方向是结合联合国人类发展指数HDI数据做多源融合把预期寿命作为 HDI 的一个分项反过来研究“预期寿命排名变化”与“综合发展水平排名变化”之间的互动关系这会是一个更有纵深的社会科学研究。最后分享一点个人的经验心得。这个项目跑完最颠覆我直觉的结论是GDP 不是影响预期寿命最重要的因素。网上很多关于“有钱活得久”的讨论本质上只看到了相关关系而数据挖掘可以把相关关系拆解成更接近因果链条的结构——经济水平通过降低成人死亡率、提升医疗可及性、提高教育年限这三个渠道间接作用于寿命。如果你做数据分析时发现结论和常识不一致先别急着重跑模型回去检查数据分布和相关性结构你会发现模型十有八九是对的而常识才是被简化过的。这也是我觉得这个项目最有价值的地方它用一套标准数据挖掘流程把一个人人都能聊两句的健康话题变成了可以验证、可以量化、可以反复推敲的实证结论。
返回列表