☰
Python数据挖掘实战:人均预期寿命变化分析全流程复盘
2026/9/30 12:07:46 网站建设 项目流程

做数据分析的人应该都遇到过一类项目:标题看起来非常明确,比如“python基于数据挖掘的人均预期寿命变化分析”,但真正拿到数据之后才会发现,数据口径、缺失值、因子筛选、模型解释每一个环节都有坑。这篇不算教科书式的教程,更像是我把这类项目完整跑过一遍之后的复盘。如果你正准备做健康数据、人口数据或者社会经济数据的挖掘分析,或者只是想看看Python数据分析到底怎么落地,这篇内容应该能给你一套可以复用的思路。

先说清楚一件事:项目最核心的目标不是算出某一年的人均预期寿命是多少,而是通过数据挖掘的手段,搞清楚预期寿命在时间轴上怎么变、为什么变,以及哪些因素和它的变化关系最紧密。这也是“变化分析”这四个字里最关键的部分。适合看这篇内容的人大概有三类:刚入门Python、想拿真实数据练手的学生;需要基于公开数据做行业报告或政策研究的从业者;想系统了解数据挖掘完整流程的爱好者。用到的主要工具是Python生态下的pandas、numpy、scikit-learn和matplotlib,这套组合足以覆盖从数据处理到建模可视化的完整链路。

还要提醒一句容易被忽视的基础概念:人均预期寿命并不等于“平均死亡年龄”。它是生命表里一个标准化指标,反映的是当前年龄别死亡水平。很多初学者会把这两个概念混在一起,导致后面所有分析方向都跑偏。先把这个词定义清楚,后面才能继续往下谈。

1. 需求拆解与方案选型

1.1 从“变化”二字反推分析目标

“人均预期寿命变化分析”这句话看起来信息量不大,但拆开之后会发现它至少包含三个层次的问题:第一,寿命在不同年份之间的整体趋势是怎样的,是持续增长还是存在波动;第二,不同群体之间的差异是在扩大还是缩小;第三,哪些社会经济或公共卫生指标与这种变化关联最强,能不能建立可解释的关系模型。

如果不做拆解,很容易一上来就去下载单一年份的数据,画一张条形图就结束。但“变化”二字决定了这必须是面板数据,也就是同一个观察单位在多个时间点上的数据。我建议项目开始之前,把目标写成三个可执行的子问题:计算年化增长率、识别趋势拐点;按地区或收入水平分组做聚类;用回归模型做因子重要性排序。这样每一步都有明确产出,最后交付的也不只是一张图,而是一整套证据链。

我在实际项目中还给这个需求起了个内部代号hx4065,方便在版本管理和任务追踪时直接召唤。这类编号在团队协作里很常见,不影响分析本身,但归档时非常有用。

1.2 为什么用Python而不是Excel或SPSS

这个项目如果只用Excel,做透视表和折线图没有问题,但一旦数据量到几十万行、需要多表合并和重复清洗,效率和可复现性就跟不上了。SPSS这类软件适合标准化的统计分析流程,但想自定义聚类、随机森林或者时间序列交叉验证,操作起来非常绕。Python的优势是开源生态完整,一个环境里能把数据获取、清洗、建模、可视化全部串起来,而且每一步都能留痕、可重现。

具体到工具链,我使用的是Anaconda管理Python解释器,VSCode写代码。Anaconda的好处是自带numpy、pandas、matplotlib等常用库,省去很多安装编译的步骤;VSCode配合Jupyter扩展之后,既能分步调试,又能直接展示中间结果。如果你还没配置好Python环境,这一套组合是最稳妥的上手路线。

1.3 方案设计背后的逻辑

整个分析方案我采用“先探索、后建模、再解释”的顺序。先做描述性统计和可视化,看看数据长什么样;然后做聚类和回归,挖掘结构;最后回到行业知识解释结果。这样可以避免一个常见错误:一上来就跑随机森林,得到一堆特征重要性数字,却不知道这些数字到底在讲什么。预期寿命这种公共卫生指标,背后有大量医学和社会经济背景,只有结合常识来解释,结果才有真正的说服力。

2. 数据获取与清洗

2.1 公开数据源怎么选

做这类分析,数据源首选公开权威数据库,而不是自己爬取散落网页的数据。世界银行WDI数据库覆盖全球大多数国家的人均预期寿命、出生率、死亡率、人均GDP等指标,时间跨度长,格式统一。世界卫生组织的全球健康观测站更偏重医疗卫生维度,比如每千人医生数、死因构成数据。联合国人口司则提供按年龄分性别的人口数据和生命表,适合做更精细的生命表分析。

实际使用中,我倾向于以世界银行数据为主干,用WHO数据做补充。原因是世界银行的数据集是全球面板,按国家、年份、指标组织,导入之后非常规整。如果你做的是国内区域分析,也可以使用各省份的统计年鉴,但一定要提前确认每个统计口径是不是一致。建议下载的时候优先用CSV或者Excel格式,如果能直接调API,也可以写成脚本定时拉取。

2.2 宽表转长表:pandas里的关键技术点

公开数据集下载下来之后通常是宽表:每一行是一个国家,后面每一列是一个年份。这种结构人眼看起来方便,但pandas做groupby分析时就很别扭。第一步永远是把宽表转换成“长表”:一行对应一个“国家-年份-指标值”的记录。

import pandas as pd df = pd.read_csv('life_expectancy.csv') df_long = df.melt( id_vars=['country', 'code'], var_name='year', value_name='value' ) df_long['year'] = df_long['year'].astype(int) df_long = df_long.sort_values(['country', 'year']).reset_index(drop=True)

这个操作里有一个很容易踩的坑:年份列从CSV读进来是字符串,如果不先转成int,后面按年份排序时会得到“1990、2010、2020、1995”这种字典序,而不是真正的年份顺序。别问我是怎么知道的,我第一次跑这个项目时就因为这个原因把趋势图画成了锯齿状。

如果数据源里有多个指标,需要把指标名称单独放一列。比如指标列分别有“Life expectancy at birth”“GDP per capita”等,最好在读取时用sep参数清洗好列名,再合并成一张宽表。最理想的结构是这样的:

countrycodeyearlife_expectancygdp_per_capitahealth_expenditure
ExampleEXM200072.185006.2

这种结构方便后续做相关性分析和建模。

2.3 缺失值和异常值分别处理

预期寿命这类指标通常不会有太多缺失,但经济类协变量很常见缺失。我的策略是:如果一个特征缺失比例低于5%,用中位数填充;如果高于30%,直接删除这个特征,因为填充出来的数据本身就不够可信。

特别提醒一下时序数据的缺失值不能简单用全局均值填充。比如某国2005年GDP缺失,如果用所有年份GDP的均值填进去,会把这个国家当年的经济水平抹平,破坏时间趋势。更合适的做法是使用前后年份插值:

df = df.sort_values(['country', 'year']).reset_index(drop=True) df['gdp_per_capita'] = ( df.groupby('country')['gdp_per_capita'] .apply(lambda x: x.interpolate(method='linear')) )

这样填充出来的值会沿着该国家自己的趋势走,相对合理。但插值也不适合连续缺失太多的情况,如果某国家连续缺失超过五年,我宁可把该国家从样本中剔除,也不要硬补。

异常值方面,预期寿命一般落在50到85之间,偶尔会看到低于40或高于90的数值,这时先不要急着删,要去查原始来源。某些高死亡率国家确实存在不到50岁的真实预期寿命,这不是数据错误。我会用z-score方法标记极端值,再结合上下文判断是否保留。

2.4 构建统一的分析数据集

清洗完成后,需要把多个指标合并到一个DataFrame里。注意不同数据源的列名、国家代码可能不一致。我的做法是统一转小写,并使用ISO3国家代码作为唯一键,避免中文简称不一样导致合并错位。

合并之后还要做一个横向检查:每个国家应该覆盖多少年、有没有明显的年份断层。我通常写一个简单的透视表统计每个国家出现的年份数,然后把覆盖不足20年的国家剔除掉。这一步能稳定后续聚类和回归的样本基础。

3. 数据挖掘核心环节

3.1 特征工程与指标体系构建

预期寿命是一个综合结果,不能只靠一两个变量解释。我在项目里建的临时特征池包括:出生率、婴儿死亡率、成人死亡率、人均GDP对数、教育支出占GDP比重、医疗卫生支出占GDP比重、城市化率、每千人医生数和成年吸烟率。

这里的关键点是“对数化”:人均GDP分布非常右偏,少数高收入国家能把均值拉得很高。如果把原始GDP直接放进模型,会放大高收入国家的影响。取对数之后,GDP差异变成了倍率差异,更符合公共卫生领域的常识认知。

建模之前还要注意量纲问题。线性模型和聚类分析都需要先做标准化,否则GDP这种数值较大的特征会主导距离计算,吸烟率这种百分比特征反而变得无关紧要。树模型不需要标准化,但为了统一流程,我经常在建模前就做一个标准化副本,后面用哪个模型就取哪份数据。

3.2 趋势分解与年化增长率计算

趋势分析是“变化分析”最基础的一环。我不会只画一条总额均值折线,而是同时计算每个国家或地区的年化增长率,用来量化“变化速度”。公式很简单:

df_year = df.pivot_table( index='year', columns='country', values='life_expectancy' ) valid = df_year.dropna(axis=1) rate = ( (valid.iloc[-1] / valid.iloc[0]) ** (1 / (valid.index[-1] - valid.index[0])) - 1 )

为什么要用首尾值算年化增长率?因为它代表整个时间段的复合增速,能直接跨国家比较。但也要警惕首尾年份是否存在异常波动,所以我还会额外计算五年移动平均,观察增长是否平稳。比如某地区1980年代因为特殊原因出现波动,随后恢复增长,只看首尾值会掩盖中间的惨烈变化。将这些信息整理成一张增长率排名表,比单纯放一张折线图更有说服力。

3.3 聚类分析:识别不同发展模式

不同地区在人预期寿命变化上可能不完全同步。为了发现结构,我使用KMeans对“基期预期寿命、年化增长率、人均GDP对数”三个特征做聚类。这三个特征组合起来能区分出几类典型模式:高基数低速增长、中等基数中等增长、低基数高速追赶型。

聚类前必须做MinMaxScaler标准化,否则数量级差异会直接把聚类带偏。选择K值时,我用轮廓系数和肘部法则对照判断。轮廓系数越高代表簇内越紧凑、簇间越分离,但它不是越高越好,K等于样本数时轮廓系数会极端,所以要结合实际业务解释。

聚类结果出来之后,把cluster标签合并回原始数据,按类别统计平均寿命和平均GDP。这样就能看到,低基数高增长的那一组到底是什么样的国家或地区。要注意聚类不揭示因果关系,它只是把数据中隐藏的组合模式显性化。

3.4 回归分析:解释因子的影响

要回答“哪些因子在影响预期寿命”,我同时跑了线性回归和随机森林回归。线性回归的优势是系数可以直接解释,比如医疗卫生支出占比每提高一个百分点,预期寿命平均增加多少年。但它的短板也很明显,对多重共线性极其敏感。随机森林则能捕捉非线性关系,输出特征重要性,但不好给出传统意义上的“影响方向”。

流程上,我先用train_test_split切分数据,随机森林R2往往能到0.9以上,线性回归大概在0.7左右。这个差异本身就能说明数据中存在非线性交互关系。再看随机森林的特征重要性,GDP、成人死亡率、医疗支出通常位列前三。

在线性回归之前,我还会检查VIF(方差膨胀因子),把VIF大于10的变量剔除或合并。比如GDP和教育支出经常高度相关,两个都放进模型会导致系数不稳定,甚至出现违背常识的负号。这时候不要急着调参,应该从数据关系上找原因。

4. 可视化与结果解读

4.1 时序图:直观展现变化过程

可视化不是最后用来装饰的,而是建模前后验证假设的工具。第一张图我通常画全样本均值的时间序列,并把总预期寿命、男性、女性分成三条线。性别差距一般会稳定存在,但不同国家差距大小不同,这张图能直观回答整体趋势。

画图时有几个细节:x轴标签如果每一年都显示,20年的数据不算密集,但60年就会挤成一团。我会使用plt.xticks每隔五年设置一个刻度,图例放在图外或者右上角,避免遮挡数据线。配色上选择色盲友好的颜色,比如蓝、橙、绿组合,不要用红绿对撞。

plt.figure(figsize=(12, 6)) plt.plot(df.groupby('year')['life_expectancy'].mean(), marker='o', label='total') plt.plot(df[df['sex'] == 'Male'].groupby('year')['life_expectancy'].mean(), label='male') plt.plot(df[df['sex'] == 'Female'].groupby('year')['life_expectancy'].mean(), label='female') plt.xticks(range(1990, 2021, 5)) plt.legend() plt.show()

这段代码在数据已经整理成“year、sex、life_expectancy”结构时可以直接跑通。如果数据里没有性别维度,就只画总寿命这一条线,同样能说明问题。

4.2 热力图:快速筛查因子关联

建模之前,我会用seaborn画一张相关系数热力图,把所有连续指标和预期寿命两两之间的线性相关展示出来。预期寿命通常和人均GDP、医疗支出、教育水平呈正相关,和婴儿死亡率、成人死亡率呈负相关。这张图能快速发现哪些变量高度抱团,也能提示我们不要在同一个模型里同时放入两个几乎共线的指标。

import seaborn as sns corr = df[['life_expectancy', 'gdp_log', 'health_exp', 'smoking', 'infant_mortality', 'urban_rate']].corr() sns.heatmap(corr, annot=True, cmap='coolwarm', center=0)

画热力图之前必须保证数据对齐到同一组“国家-年份”记录,否则缺失值会变成NaN,相关系数矩阵里出现大量空格。还要明确一件事:相关系数只刻画线性关系,非线性关系在热力图里可能看不出信号,但这不代表不存在。所以我把热力图当作快速筛查工具,之后还是会用随机森林这种非线性模型做交叉验证。

4.3 区域差异与动态展示

如果想把“变化”讲得更生动,可以尝试动态散点图:横轴是人均GDP对数,纵轴是预期寿命,散点大小代表人口规模,每个年份一帧,连续播放就能看到所有地区整体向右上方移动的过程。matplotlib的animation模块可以实现,保存成gif时要注意控制帧率和dpi,不然文件会大到没法在网页里打开。

如果不想做动画,也可以用分面图:把时间切成几个有代表性的年代,每个子图画一个散点图。打印到纸质报告里也很清晰。但不管用什么形式,动态展示都只是辅助表达,最终分析结论还是要落到数字和模型上。不要为了炫酷而牺牲可读性。

5. 常见问题与排查实录

5.1 数据口径不一致

这类项目最容易踩的坑是数据口径不一致。同一个国家,在不同数据源里可能用不同名称,比如“Cote d'Ivoire”和“Ivory Coast”;有的数据源用FIPS国家代码,有的用ISO3,直接按名称合并会出现大量匹配不上的记录。

我的解决办法是建立一个“国家名称-ISO3代码”对照表,把所有后续合并都基于代码来做。如果数据集里没有代码列,就先根据名称映射成代码,再进行合并。另外,不同指标所属的时间口径也可能不一样。比如财政年度和自然年度混用,日期向后偏移几个月,这样的偏差短期看不出问题,但跨20年累计后会严重影响趋势分析。

5.2 版本兼容与环境管理

Python库版本变化非常快,sklearn在不同小版本之间API都会有调整。我经常遇到的情况是:在旧环境里能跑的代码,换到新环境就报错,错误信息指向某个模型参数不存在。解决方式就是使用虚拟环境:

conda create -n life_expectancy python=3.9 pip install pandas==1.5.3 scikit-learn==1.2.2 matplotlib==3.7.1

把项目依赖写成requirements.txt锁定版本,等到交付或复现时直接一键安装。这个小习惯能省掉无数“在我机器上是好的”这种尴尬。

5.3 时间序列中的过拟合陷阱

做面板数据时,随机抽样会泄露未来信息,导致评估结果虚高。比如用2010年的特征预测2010年的寿命,再拿同样数据集里的随机样本做测试,模型很可能已经“见过”该国家其他年份的信息,预测精度自然虚高。更合理的做法是使用TimeSeriesSplit:训练集永远在测试集之前,测试集只包含之后年份的数据。

from sklearn.model_selection import TimeSeriesSplit tscv = TimeSeriesSplit(n_splits=5) for train_idx, test_idx in tscv.split(X): # 每次切分,train_idx 都在 test_idx 之前 ...

这样评估出来的性能更接近真实预测场景。做特征重要性分析时,也尽量不要只看一次随机切分的结果,而是多次运行取平均。

5.4 大表合并与内存优化

数据量到了几十万行,pandas的一些操作会变得非常慢。比如for循环逐行填充新列,在大的DataFrame上基本不能等。我的优化思路是:优先使用vectorized操作,比如groupby+transform代替循环;合并时减少数据副本,提前删除不需要的列。

还有一个容易被忽略的点:CSV里的年份列读进来可能是object类型,既占内存又导致排序错误。读取时直接指定dtype,或者之后astype转换,能显著降低内存。如果数据量真的很大,还可以用polars替代pandas,接口类似但性能好很多。对于这类型项目,数据量还不至于到必须用polars,但提前了解一下没有坏处。

6. 项目扩展方向

6.1 加入死因结构数据

预期寿命本身是一个“结果”,要解释变化背后的原因,最好把死因构成数据合进来。比如传染病、慢性病、交通事故、孕产妇死亡等分死因数据,能更细致地说明不同年龄段死亡水平变化对总寿命的贡献。这块数据的质量参差不齐,需要谨慎处理缺失值,但做出来后解释力会强很多。

6.2 从趋势分析走向预测建模

当时间跨度足够长时,可以尝试用ARIMA、Prophet或者LightGBM对人均预期寿命做未来10年预测。预测要特别注意置信区间,寿命预测不像股票预测,外推太远没有实际意义。我一般只预测5到10年,并强调模型结果只是趋势参考,不是定论。

最后分享一点个人感受:这类“基于数据挖掘的某指标变化分析”项目,最考验人的往往不是算法本身,而是对数据的敬畏。你能从数据里挖掘出什么结论,首先取决于你清洗时有没有把口径理清、缺失值处理对、异常值判断准。如果你现在正准备开启类似项目,建议先从一份多年份多地区的公开数据开始,把上述这些坑提前避掉,至少能省下一半时间。另外我常做的一件事是:每个阶段的分析表都导出一份带日期和数据字典的CSV,存到一个固定的项目目录里。这样即使三个月后回头看,还能立刻想起当时为什么做某个取舍。希望这个复盘能给你一些参考。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询