Python人口趋势分析:数据清洗、ARIMA预测与聚类实战
2026/9/13 5:58:58 网站建设 项目流程

最近一段时间,全球人口长期趋势的讨论频繁出现在公共话题中,许多观点把人口结构变化看成未来几十年的核心变量。抛开观点本身不谈,这些讨论背后其实站着一批可以被量化、被清洗、被建模、被可视化的统计数据。对于数据从业者来说,这是一块非常适合练手的分析场景,它不像业务埋点数据那样杂乱,也不像金融高频数据那样需要处理极端噪声,而是同时具备时间序列、地域差异、多指标联动三个特点,刚好能覆盖数据分析工作中的完整流程。

这篇文章会用 Python 走一遍人口趋势数据分析的完整链路:从数据获取与清洗,到探索性可视化,再到时间序列预测模型和多国聚类分析。全文提供可复现代码,读者看完之后,完全可以从零搭建一套自己的“人口数据看板”,并把同样的方法迁移到其他领域,例如用户增长预测、销量趋势分析和宏观指标监控。

在开始之前先说清楚结论:人口数据的核心难点并不在于模型多么高级,而在于“数据质量”和“业务理解”。如果把生育率数据当成普通的时序数值直接丢进模型,结果往往很差。正确的做法是先理解指标含义,再处理数据口径,最后用合适的模型去逼近趋势。本文会按这个逻辑逐步展开。

1. 人口趋势分析为什么值得用 Python 做

先回答一个问题:为什么人口趋势值得专门写一篇文章来分析?对于很多开发者来说,人口数据可能一辈子都用不上。但换个角度想,人口数据本质上是一张“低频版”的用户增长表:国家是渠道,年份是时间,生育率是核心指标,死亡率是流失率,迁徙率是新增来源。理解人口数据分析的套路,对做业务数据同样有启发。

Python 在这个领域有三个明显优势。

第一,数据清洗便利。人口数据通常以 CSV 形式提供,包含几十个国家的多年记录,经常存在缺失值、口径不一致和历史修订问题。pandas 可以非常高效地完成读取、透视、分组和缺失值处理。

第二,统计建模生态成熟。statsmodels 提供完整的 ARIMA 模型,Prophet 在趋势预测和节假日效应上表现出色,scikit-learn 可以快速完成聚类和特征分析。这些都是经过大量生产环境验证的库,不用重复造轮子。

第三,可视化能力强。matplotlib、seaborn 和 plotly 组合在一起,既能输出适合写文章的高清静态图,也能生成可交互的网页看板。人口数据以折线图、热力图和金字塔图呈现时,信息表达效率非常高。

从学习路径来看,人口数据分析也是一个不错的过渡项目。它比课堂里的 iris 数据集复杂一些,有真实的时间依赖结构;但又不像电商订单数据那样需要复杂的采集链路。用它来练习“从 CSV 到预测结果”的完整流程,性价比很高。

2. 人口数据核心指标与建模基础

2.1 总和生育率(TFR)

总和生育率,Total Fertility Rate,是指一个妇女在育龄期间(通常为 15 到 49 岁)按当前年龄别生育率生育的平均子女数。它是衡量人口再生产水平的经典指标。

技术层面的理解如下:

  • TFR 大于 2.1,一般认为人口长期保持增长,这个 2.1 被称为“更替水平”。
  • TFR 小于 2.1,人口结构会缓慢收缩,但短期内人口规模不一定下降,因为存在“人口惯性”。
  • TFR 只反映生育行为,不直接等于人口增长率,建模时需要配合死亡率数据。

2.2 出生率、死亡率与自然增长率

与 TFR 容易混淆的三个指标是:

  • 出生率:某年每千人中的活产数。
  • 死亡率:某年每千人中的死亡数。
  • 自然增长率:出生率减死亡率。

TFR 衡量的是“每个妇女生几个”,出生率衡量的是“总人口中有多少新生儿”。两者方向和趋势通常一致,但幅度不同。做预测时,如果没有计划生育政策的上下文,直接用趋势外推 TFR 往往比直接外推出生率更稳健。

2.3 人口结构与人口金字塔

除了生育和死亡,人口结构变化也会显著影响未来人口规模。人口金字塔展示了不同年龄段的性别分布。年轻型结构的国家即使 TFR 下降,未来几十年人口仍可能继续增长;老龄化国家的死亡率会上升,但这不是因为健康水平下降,而是年龄结构决定的结果。

这个细节对建模非常关键:如果只是简单地把历史人口总数做 ARIMA 外推,会忽略结构变化;更合理的做法是分年龄组做队列推算,也就是“队列要素法”。本文更关注通用分析流程,但读者应该知道结构因素的存在。

2.4 时间序列预测与平稳性

人口数据是典型的时间序列。做 ARIMA 预测前,先理解两个基础概念:

  • 平稳性:序列的均值和方差不随时间变化。人口数据大多有长期趋势,通常不平稳。
  • 差分:将序列转化为前后两期之差。一阶差分可以把带线性趋势的数据变成近似平稳。

具体到代码中,可以通过 ADF 检验来确认是否需要差分。后文会给出示例。

3. 数据来源与开发环境准备

3.1 公开人口数据从哪里获取

真实的人口数据可以从以下渠道获取:

数据源内容特点典型指标
联合国人口司覆盖 1950 年至今,分年龄、分性别、分国家出生率、死亡率、人口结构
世界银行开放数据国家年度面板数据,指标体系统一总和生育率、人口总数、预期寿命
Our World in Data数据可视化优秀,提供 CSV 下载生育率、教育、健康等社会指标

需要注意的是,不同来源的数据质量存在差异。联合国人口司会对历史数据进行长期修订,世界银行的部分指标则来自各国上报,口径未必完全一致。这是数据处理阶段需要警惕的问题,后面会专门讨论。

为了避免读者下载数据时遇到字段不匹配问题,本文使用构造的模拟数据进行演示。模拟数据的目标是让流程跑通,真实项目只需把pd.read_csv指向官方 CSV 文件即可。

3.2 Python 环境准备

本文代码基于 Python 3.9 以上版本。推荐使用 Anaconda 或官方 Python 环境,并安装以下依赖:

pip install pandas numpy matplotlib seaborn statsmodels prophet scikit-learn

Prophet 在部分系统上安装较慢,如果安装失败,可以单独安装并确认编译器环境。订单模型和 ARIMA 部分是重点,Prophet 只是为了展示另一种建模思路,非必需。

4. 数据清洗与探索性分析

4.1 构造演示数据

先构造 1960 到 2022 年、若干国家的总和生育率模拟数据。这段代码保证了后续步骤的独立性,读者运行后可以完全复现。

import numpy as np import pandas as pd np.random.seed(42) countries = [ 'China', 'India', 'United States', 'Japan', 'Germany', 'Nigeria' ] initial_fertility = { 'China': 5.8, 'India': 6.0, 'United States': 3.6, 'Japan': 2.1, 'Germany': 2.5, 'Nigeria': 6.5 } years = np.arange(1960, 2023) rows = [] for country in countries: base = initial_fertility[country] for year in years: decline = (year - 1960) * 0.03 noise = np.random.normal(0, 0.1) fertility_rate = max(1.0, base - decline + noise) rows.append({ 'country': country, 'year': year, 'fertility_rate': round(fertility_rate, 3) }) df = pd.DataFrame(rows) print(df.head())

输出如下:

country year fertility_rate 0 China 1960 5.734 1 China 1961 5.672 2 China 1962 5.809 3 China 1963 5.839 4 China 1964 5.686

这段数据虽然简单,但已经具备真实数据的关键特征:整体下降趋势、国家间差异、随机噪声。如果读者需要使用真实数据,把这一段的生成逻辑替换成pd.read_csv("你的文件.csv")即可。

4.2 数据质量检查

拿到数据后,首先要做的是三个动作:检查缺失值、确认数据类型、验证取值合理范围。

print(df.isna().sum()) print(df.dtypes) print(df['fertility_rate'].describe())

模拟数据通常没有缺失值,但真实数据一定会出现。人口数据的缺失值往往不是随机出现的,可能是某些国家某年没有上报,或者是数据源本身没有收录。常用的处理方式包括:

  • 线性插值:适用于短时间缺失,使用df.interpolate()
  • 趋势外推:适用于长期缺失,但需要谨慎。
  • 直接删除:适用于同一国家多年缺失且不影响主分析。

再看数据类型。年份字段通常是 int64,生育率字段是 float64。最容易被忽略的是国家名称,它的取值可能包含空格、大小写不同和首字母缩写不一致。建议在数据清洗阶段统一处理:

df['country'] = df['country'].str.strip().str.title()

这个操作虽然简单,但在实际项目中能避免大量“对不上”的问题。

4.3 探索性可视化

用折线图观察各国生育率的变化:

import matplotlib.pyplot as plt import seaborn as sns plt.figure(figsize=(12, 6)) sns.lineplot(data=df, x='year', y='fertility_rate', hue='country') plt.title('Fertility Rate Trends (1960-2022)') plt.xlabel('Year') plt.ylabel('Total Fertility Rate') plt.grid(True, linestyle='--', alpha=0.4) plt.show()

如果数据源真实,读者会在这里看到很多有意思的现象:部分国家在 1960 年代出现明显下降,随后进入平台期;也有一些国家在政策调整后出现过短期反弹。这些“异常”都不是模型噪声,而是真实社会事件在数据中的投影。在做预测前,先通过可视化确认这些拐点,比直接建模更重要。

5. 时间序列建模:ARIMA 预测生育率

5.1 ARIMA 模型的基本理解

ARIMA,差分自回归移动平均模型,是经典的时间序列预测方法。它包含三个参数:

  • p:自回归阶数,代表用过去 p 期的值预测当前值。
  • d:差分次数,让序列变得平稳。
  • q:移动平均阶数,用过去 q 期的预测误差修正当前值。

人口数据大多具有明显趋势,d 取 1 或 2 较为常见。p 和 q 需要根据信息准则或自相关图选择。这里使用 AIC 自动搜索的思路,而不是拍脑袋指定参数。

5.2 单国数据处理

以日本为例,因为日本的生育率变化特征比较明显。先提取单国数据并转成时间序列:

japan = df[df['country'] == 'Japan'].sort_values('year') ts = japan.set_index('year')['fertility_rate'] print(ts.head())

5.3 自动搜索 ARIMA 参数

import itertools from statsmodels.tsa.arima.model import ARIMA import warnings warnings.filterwarnings("ignore") p = d = q = range(0, 3) pdq = list(itertools.product(p, d, q)) best_aic = float('inf') best_order = None best_model = None for order in pdq: try: model = ARIMA(ts, order=order) result = model.fit() if result.aic < best_aic: best_aic = result.aic best_order = order best_model = result except Exception: continue print(f'Best ARIMA order: {best_order}, AIC: {best_aic}')

AIC 是赤池信息准则,它综合考虑了拟合优度和参数数量。AIC 越小,模型越值得参考。实际运行时可能得到类似Best ARIMA order: (1, 1, 2), AIC: 207.35的结果。这个数字不必太在意,因为每次模拟数据的噪声不同。

5.4 预测未来 10 年

forecast = best_model.forecast(steps=10) forecast_df = pd.DataFrame({ 'year': np.arange(2023, 2033), 'fertility_rate': forecast.values }) print(forecast_df)

代码输出类似:

year fertility_rate 0 2023 1.386 1 2024 1.367 2 2025 1.348 ...

把预测结果和原始数据画在一起:

plt.figure(figsize=(12, 6)) plt.plot(ts.index, ts.values, label='Historical') plt.plot(forecast_df['year'], forecast_df['fertility_rate'], label='Forecast', linestyle='--', marker='o') plt.title('Japan Fertility Rate Forecast (ARIMA)') plt.xlabel('Year') plt.ylabel('Total Fertility Rate') plt.legend() plt.grid(True, linestyle='--', alpha=0.4) plt.show()

如果预测曲线几乎是一条水平线,这是正常现象。ARIMA 对强趋势数据的预测会逐渐收敛到序列的局部均值,尤其在模型选择为低阶 ARIMA 时。大多数情况下,人口趋势的长期预测更依赖外部假设,单纯的统计模型只能给出“趋势不变”情景下的参考值。

6. 通过 Prophet 做趋势预测

6.1 Prophet 的适用场景

Prophet 是 Facebook 开源的时间序列预测工具,适合具有明显季节性和趋势的业务数据。人口数据没有季节性,但 Prophet 对缺失值和异常值的容忍度较高,而且提供了直观的置信区间。用它来做人口趋势预测,更多是把它当成一个快速原型工具:几行代码就能得到一个 baseline 结果。

6.2 Prophet 使用流程

Prophet 要求数据包含两列:ds表示时间,y表示目标值。注意如果年份是 int 类型,需要先转成日期格式。

from prophet import Prophet japan_prophet = japan[['year', 'fertility_rate']].rename( columns={'year': 'ds', 'fertility_rate': 'y'} ) japan_prophet['ds'] = pd.to_datetime(japan_prophet['ds'], format='%Y') model_prophet = Prophet() model_prophet.fit(japan_prophet) future = model_prophet.make_future_dataframe(periods=10, freq='Y') forecast_prophet = model_prophet.predict(future) model_prophet.plot(forecast_prophet) plt.title('Japan Fertility Rate Forecast (Prophet)') plt.show()

Prophet 输出的yhat是预测均值,yhat_loweryhat_upper是置信区间上下界。与 ARIMA 相比,Prophet 更容易给出跨度较大的置信区间,因为它的不确定性建模更保守。这在做生产级分析时反而是一个优点:结果不会过度自信。

6.3 两种模型的差异

用表格对比:

维度ARIMAProphet
数据要求平稳序列或差分化处理原始时序即可
处理缺失值需要先处理内部有处理逻辑
趋势建模线性差分类分段线性/逻辑增长
置信区间相对较窄较宽,更保守
使用复杂度需要定阶配置简单

实际项目建议先用 Prophet 跑一个快速基线,再用 ARIMA 做细粒度验证。如果两种模型的预测方向不一致,优先检查数据预处理和趋势假设,而不是急着调参。

7. 完整示例:多国生育率聚类分析

7.1 什么是聚类分析

在单国预测之外,人口数据还有一个常见分析需求:把多个国家按生育率变化特征分组,了解哪些国家呈现相似的模式。聚类算法可以自动完成分组。这里使用的 KMeans 是一种基于距离的分组算法,通过迭代优化把样本划分到最近的簇中心。

7.2 提取国家级特征

构造两个特征:

  • 平均生育率:反映整体水平。
  • 总降幅:反映长期变化程度。
summary = df.groupby('country').agg( avg_fertility=('fertility_rate', 'mean'), total_decline=('fertility_rate', lambda x: x.iloc[0] - x.iloc[-1]) ).reset_index() print(summary)

特征提取是聚类分析中最关键的步骤。特征选得不好,聚类结果没有业务解释力。这里用“平均生育率”和“总降幅”两个维度,已经能把“高起点高下降”“低起点低下降”“缓慢下降”等国家模式区分开。

7.3 标准化与 KMeans

因为两个特征的量纲不同,需要先做标准化。

from sklearn.preprocessing import StandardScaler from sklearn.cluster import KMeans scaler = StandardScaler() features_scaled = scaler.fit_transform(summary[['avg_fertility', 'total_decline']]) kmeans = KMeans(n_clusters=3, random_state=42) summary['cluster'] = kmeans.fit_predict(features_scaled) print(summary.sort_values('cluster'))

这段代码将国家分成 3 类。n_clusters 的选择实际上需要一些判断。最简单的办法是绘制“簇内平方和”随 k 变化的曲线,选择拐点位置,这是手肘法。也可以结合业务:如果分析目的就是区分“高、中、低”三档,那么 k=3 就足够。

7.4 聚类结果可视化

plt.figure(figsize=(10, 6)) sns.scatterplot( data=summary, x='avg_fertility', y='total_decline', hue='cluster', s=120 ) for _, row in summary.iterrows(): plt.text( row['avg_fertility'] + 0.05, row['total_decline'] + 0.05, row['country'] ) plt.xlabel('Average Fertility Rate') plt.ylabel('Total Decline') plt.title('Countries Clustered by Fertility Characteristics') plt.grid(True, linestyle='--', alpha=0.4) plt.show()

聚类结果的核心价值在于“可解释”。如果分出的簇无法给出清晰的业务含义,说明特征选择或 k 值不合适。例如,一个簇全是非洲国家,特征是“起始生育率极高、下降速度较快”,另一个簇全是欧洲国家,特征是“起始较低、趋于稳定”,那么这个聚类结果就非常有解释力。

8. 常见问题与排查方法

问题现象可能原因排查方式解决方案
读取 CSV 后年份是字符串Excel 或 CSV 编码问题检查 dtype转换成pd.to_datetimeastype(int)
数据缺失值过多数据源口径不一致统计缺失率分布插值、删除或按地区使用相邻年份
ARIMA 运行报错序列不平稳或参数不合适查看错误详情,做 ADF 检验增加差分次数,或者改用 Prophet
预测结果是一条直线ARIMA 对强趋势数据不敏感查看模型摘要,检查是否用了差分调整模型阶数,或尝试分段建模
Prophet 安装失败编译环境缺少依赖查看安装日志使用 Python 3.10 以下版本或安装预编译包
聚类结果无法解释特征选择不合理查看每个簇的国家列表增加特征维度,或改用层次聚类

再补充一个容易被忽略的坑:时间索引必须是单调递增且去重后的。多个国家数据合并后,如果忘记按年份排序,ARIMA 会直接报错或者给出毫无意义的预测结果。每次建模前都执行一次sort_values是性价比很高的习惯。

9. 最佳实践与工程建议

9.1 数据来源要可追溯

人口数据的口径会随着统计方法更新而变化。在做任何报告前,先记录数据来源、下载时间和版本号。真实项目里,同一指标在不同年份发布的数据可能完全不同。建议在代码目录中保留一份原始数据快照,不要直接分析临时下载的文件。

9.2 预测要区分“统计预测”和“情景预测”

使用 ARIMA 或 Prophet 得到的结果,本质上是“历史趋势延续”的统计外推。它不代表未来的确定性判断。如果要用于政策评估或业务决策,应该设计多个情景,例如“趋势延续”“加速下降”“企稳回升”,然后分别给出预测区间。这是分析结果能否落地的关键差异。

9.3 建模前先画图再定特征

不要在数据清洗结束后立刻进入模型训练。至少先画出时间序列折线图、分布直方图和相关性热力图。人口数据通常只包含少量特征,单变量趋势加上区域差异,可视化能立刻暴露明显的异常值和结构性断点,节省大量调参时间。

9.4 评估指标不要只看 R2

时间序列预测的常见错误是拿整个历史序列计算误差指标。更好的做法是划分“训练集”和“测试集”,例如用 1960 到 2010 年的数据训练,2011 到 2022 年作为验证。计算 MAE、RMSE 和 MAPE,并观察误差是否随时间累积。

from sklearn.metrics import mean_absolute_error, mean_squared_error y_true = ts.loc[2011:] y_pred = best_model.predict(start=2011, end=2022) mae = mean_absolute_error(y_true, y_pred) rmse = mean_squared_error(y_true, y_pred, squared=False) mape = (abs(y_true - y_pred) / y_true).mean() * 100 print(f'MAE: {mae:.3f}, RMSE: {rmse:.3f}, MAPE: {mape:.2f}%')

这段代码中best_model.predict需要模型对象支持startend参数。实际使用时,如果模型是用完整序列训练的,这段回测代码的结果会偏乐观。做评估时应该严格将模型在训练集上重新拟合,再做回测。

9.5 安全与合规提醒

人口数据的价值往往体现在群体层面,但在分析之前要确认数据的使用权限。公开数据通常可以自由使用,但有些国家的细粒度人口统计可能涉及隐私。生产环境中不要将个人级别的统计数据与分析结果混在一起,数据文件也要设置好访问权限。

10. 总结与进一步学习方向

这篇文章从“人口趋势”这个热点话题切入,实际上完成了一个完整的数据分析项目:模拟数据生成、数据质量检查、探索性可视化、ARIMA 时间序列建模、Prophet 快速预测、基于 KMeans 的多国聚类,以及回测评估。整套流程并不依赖特定的业务背景,换一个数据集,比如用户注册量、App 活跃率、设备故障率,仍然是同样的套路。

对于已经跑通本文代码的读者,下一步有三个方向可以深入。第一,进入因果推断领域,熟悉“合成控制法”“双重差分法”,这些方法能帮助判断某项政策或事件是否真正改变了人口趋势。第二,学习更复杂的时间序列模型,比如贝叶斯结构时间序列和基于深度学习的时序模型,但要注意它们在小样本数据上并不一定优于 ARIMA。第三,尝试把分析结果做成一个可交互的仪表盘,用 Dash 或 Streamlit 将代码封装成产品原型,让决策者可以通过点击筛选国家、拖拽选择年份,直观地观察趋势变化。

人口数据最迷人的地方,在于它把社会现象变成了可以反复验证的数字。每一次数据处理上的疏忽,都会在预测结果中留下痕迹;反过来,只要尊重数据本身的逻辑,哪怕只用最基础的统计模型,也能得出有价值的判断。建议收藏这篇文章,下次需要做时间序列分析时,可以直接把里面的代码流程当作一个起点。

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

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

立即咨询