☰
回归分析人口数量预测:从一元回归到非线性拟合实战
2026/11/2 21:30:33 网站建设 项目流程

简介:《基于回归分析的人口数量预测》是一份面向统计学、应用数学及数据分析学习者的人口预测课程实验论文,围绕回归建模解决人口数量与经济指标关系的分析与预测问题。压缩包内仅含1个doc文档,大小约442KB,正文按一元线性回归、多元线性回归与非线性模型展开,涉及散点图、理论模型设定、回归诊断、数据中心化标准化、逐步回归、多重共线性检测、主成分分析、曲线拟合及Logistic模型等内容。文档目录较完整,包含摘要、前言、各章分析与结论、参考文献,适合用于课程论文写作参考、回归分析实验复现和建模思路梳理。目前已有396人学习下载,可为统计建模入门与人口预测案例练习提供较具体的结构范本。

1. 35 行数据、6 个模型:一份统计系课程论文把回归分析人口数量预测讲透了

翻到一份统计系的课程实验论文,正文三十多页,核心数据就一张表:1980 到 2014 年,每年 5 个字段。它做的事听起来朴素——用回归分析做人口数量预测,但路子铺得很完整:先拿粮食产量做一元线性回归,再把 GDP、出生率、死亡率加进去做多元回归,接着用逐步回归和主成分分析处理共线性,最后换赛道用三次曲线和 Logistic 曲线做非线性拟合,每个模型都拿 2014 年的真实值回测一次。对刚上手建模的人,这比看十篇讲原理的帖子管用,因为它把「拟合优度高」和「预测得准」这对矛盾摆在明面上:一元回归 R² 0.826,预测却差了五千多万人;三次曲线 R² 冲到 1.000,误差压到 607 万。整份材料的代码量不大,但每个表格背后都有可复现的操作路径,适合拿来当回归建模的练手底稿。

2. 数据底座与技术栈:五个字段、35 个样本怎么喂进 SPSS 和脚本

建模类项目最容易被低估的是数据层。这份论文的数据量小得可怜——35 个年度观测、5 个字段,但正是因为小,任何一处录错都会在后面的系数表里被放大成结论错误。先把数据结构、工具链和预处理讲清楚,后面的模型才有讨论基础。

2.1 指标选择与样本口径:一个因变量配四个自变量

论文的因变量 y 是年末总人口(万人),四个自变量分别是粮食产量 x1(万吨)、GDP x2(亿元)、出生率 x3、死亡率 x4,样本区间 1980—2014 年。选这四个指标的思路是「经济条件 + 食物供给 + 人口自身再生产」三条线:粮食产量代表生存条件,GDP 代表经济承载力,出生率和死亡率直接决定自然增长。这个组合在人口预测里算经典配置,缺点是 GDP 与粮食产量本身高度相关,后面多重共线性几乎必然出现。

论文的目录结构值得单独看一眼,它其实就是整个项目的执行流水线:

章节内容对应模型关键产出
第 1 章一元线性回归总人口 ~ 粮食产量散点图、模型摘要、系数表
第 2 章多元线性回归总人口 ~ 四个自变量方差分析表、逐步回归、共线性诊断、主成分
第 3 章非线性模型三次曲线、Logistic曲线拟合图、参数估计
结论模型对比六个模型的 2014 回测误差选型结论

注意出生率的单位:论文表头写的是「%」,但 12.37 这个量级显然是千分率。这种表头与数值不匹配的情况在课程作业里很常见,录入时按数值原样保留、在字段名里标清 ‰,比事后改数安全。

2.2 把表格录成 DataFrame:一次录入,四处复用

SPSS 点选的问题是无法版本管理,换个机器就得重来一遍。我的习惯是先把原始表格固化成一份可执行的 DataFrame,之后 SPSS、statsmodels、MATLAB 都从这份数据走。

import pandas as pd import numpy as np # 字段:年份、粮食产量(万吨)、GDP(亿元)、出生率(‰)、死亡率(‰)、年末总人口(万人) # 按论文原表录入,未做任何修正,异常值留到清洗环节处理 raw = [ (1980, 32055.5, 4551.6, 18.21, 6.34, 98705), (1981, 32502.0, 4898.1, 20.91, 6.36, 100072), (1982, 35450.0, 5333.0, 22.28, 6.60, 101654), (1983, 38727.5, 5975.6, 20.19, 6.90, 103008), (1984, 40730.5, 7226.3, 19.90, 6.82, 104357), (1985, 37910.8, 9039.9, 21.04, 6.78, 105851), (1986, 39151.2, 10308.8, 22.43, 6.86, 107507), (1987, 40297.7, 12102.2, 23.33, 6.72, 109300), (1988, 39408.1, 15101.1, 22.37, 6.64, 111026), (1989, 40754.9, 17090.3, 21.58, 6.54, 112704), (1990, 44624.3, 18774.3, 21.06, 6.67, 114333), (1991, 43529.3, 21895.5, 19.68, 6.70, 115823), (1992, 44265.8, 27068.3, 18.24, 6.64, 117171), (1993, 45648.8, 35524.3, 18.09, 6.64, 118517), (1994, 44510.1, 48459.6, 17.70, 6.49, 119850), (1995, 46661.9, 61129.8, 17.12, 6.57, 121121), (1996, 50453.6, 71813.6, 16.98, 6.56, 122389), (1997, 49417.2, 79715.0, 16.57, 6.51, 123626), (1998, 51229.5, 85195.5, 15.64, 6.50, 124761), (1999, 50838.6, 90564.4, 14.64, 6.46, 125786), (2000, 46217.5, 100280.1, 14.03, 6.45, 126743), (2001, 45263.7, 110863.1, 13.38, 6.43, 127627), (2002, 45705.8, 121717.4, 12.86, 6.41, 128453), (2003, 43069.5, 137422.0, 12.41, 6.40, 129227), (2004, 46947.0, 161840.2, 12.29, 6.42, 129988), (2005, 48402.2, 187318.9, 12.40, 6.51, 130756), (2006, 49804.2, 219438.5, 12.09, 6.81, 131448), (2007, 50160.3, 270232.3, 12.10, 6.93, 132129), (2008, 52870.9, 319515.5, 12.14, 7.06, 132802), (2009, 53082.1, 349081.4, 11.95, 7.08, 133450), (2010, 54647.7, 413030.3, 11.90, 7.11, 134091), (2011, 57120.9, 489300.6, 11.93, 7.14, 134735), (2012, 58958.0, 540367.4, 12.10, 7.15, 135404), (2013, 60193.8, 595244.4, 12.08, 7.16, 136072), (2014, 60702.6, 643974.0, 12.37, 7.16, 136782), ] df = pd.DataFrame(raw, columns=["year", "grain", "gdp", "birth", "death", "pop"]) df = df.sort_values("year").reset_index(drop=True) # 三个基础体检:行数、缺失、量级 print(df.shape) # 期望 (35, 6) print(df.isna().sum().to_dict()) # 期望全 0 print(df[["grain", "gdp", "birth", "death", "pop"]].describe().T[["min", "max"]])

这段代码的三个检查点对应三类常见事故:行数不对说明有年份漏录或被去重吞掉;缺失值非零会让后续 OLS 静默丢样本;量级检查最关键,gdp的最小值和最大值如果只差一个数量级,基本可以断定某几年录错了单位。原表在 1995—1997 这几年就出过量级异常,后面第 5 章会专门讲怎么定位。

2.3 中心化与标准化:能改的是系数解释,改不了的是拟合优度

论文第 2 章开头写「中心化和标准化的意义是取消量纲不同引起的误差」,这句话方向对,但容易被理解成标准化能提升模型精度。实际不会:对 OLS 来说,中心化只改变截距,标准化同时缩放自变量和系数,拟合值、R²、t 值、p 值全部保持不变。标准化真正解决的问题是让系数之间可以比较——粮食产量的系数 1.045 和出生率的系数 -1360.798 放一起看,你会误以为出生率重要一千倍,其实只是单位不同。

from sklearn.preprocessing import StandardScaler feat = ["grain", "gdp", "birth", "death"] # 中心化:只减均值,不除标准差 centered = df[feat] - df[feat].mean() # 标准化:z-score,减均值再除标准差 z = pd.DataFrame(StandardScaler().fit_transform(df[feat]), columns=feat) # 标准化前后各跑一次 OLS,比较 R² 与拟合值 import statsmodels.api as sm m_raw = sm.OLS(df["pop"], sm.add_constant(df[feat])).fit() m_z = sm.OLS(df["pop"], sm.add_constant(z)).fit() print(m_raw.rsquared, m_z.rsquared) # 两个值应当相等 print(np.allclose(m_raw.fittedvalues, m_z.fittedvalues)) # True print(m_z.params[feat]) # 这组系数才是可比的

代码里m_z.params[feat]输出的就是标准化回归系数(SPSS 系数表里的 Beta 列),绝对值大小直接反映各自变量对因变量的相对影响力。论文表 2-3 的 Beta 列显示粮食产量 0.647、出生率 -0.496,说明在这四个变量里粮食产量的解释力最强,这一步不做,光看非标准化系数根本判断不出来。

2.4 工具链分工:SPSS 出表格,脚本管回测

论文用的是 SPSS 22.0,MATLAB 只出现在 Logistic 拟合那一段。这个组合的优点是出表快,缺点是每次改数据都要重走一遍菜单。我一般会做分工:SPSS 负责生成论文格式的模型摘要表、方差分析表、共线性诊断表,方便直接贴进文档;Python 负责数据清洗、批量回测和误差汇总,因为这两件事需要循环和版本对比,点选做不了。两边的数据源必须是同一份 CSV,否则改了数据忘了同步,表里的结论和代码里的结论就对不上,这种血泪经验几乎每个做实证的人都遇到过。

3. 一元线性回归:粮食产量和总人口那条 R²=0.826 的直线

一元线性回归是整份论文的起点,也是最能暴露「拟合优度陷阱」的地方。它只用粮食产量一个自变量,模型简单到可以手算,但预测 2014 年时给出的数字和真实值差了五千多万,这个落差恰好是后面所有复杂模型的动机。

3.1 散点图先看形状:样本点为什么落在一条带子上

论文在建模前先画了粮食产量与总人口的散点图,这一步不能省。从 1980 到 2014,粮食产量从 32055 万吨涨到 60702 万吨,总人口从 98705 万人涨到 136782 万人,两组数据同向增长,样本点在图上明显呈一条向上的带状分布,只是随着年份推移,带子的斜率在放缓——早期产量涨一点人口涨很多,后期产量涨人口几乎不动。这个放缓趋势在一元线性模型里被完全忽略,是后面误差偏大的根源。

散点图还有个作用是排查异常点。1985 年粮食产量比 1984 年回落了约 2800 万吨,但人口照涨,1998 到 2000 年粮食产量连续三年下滑,人口同样没有跟着掉。这些反向年份会让直线的斜率被拉平,如果不看图上这些散点,你很难意识到「粮食产量解释人口」这个因果链在这份数据里其实很弱。

3.2 回归诊断表逐行读:R、R²、标准误、t 与 sig

论文表 1-1 的关键数字:R=0.909,R²=0.826,调整后 R²=0.821,标准误 4985.99669。这意味着粮食产量能解释总人口 82.6% 的变异,剩下 17.4% 落在残差里,残差的标准差接近 5000 万人,这个量级已经相当于一个中等规模地区的全部人口。

表 1-2 的系数:常数项 53054.404(标准误 5500.013,t=9.646,sig=0.000),粮食产量系数 1.468(标准误 0.117,标准化 Beta 0.909,t=12.513,sig=0.000)。两个系数都通过了 0.05 的显著性检验,模型写成:

ŷ = 53054.404 + 1.468 × x1

系数的现实含义是「粮食产量每增加 1 万吨,总人口平均增加 1.468 万人」。这个解读在统计上成立,但在业务上站不住——不是粮食多了人就多,而是两者都被时间推着走,属于典型的伪相关。论文没在这一点上展开,是这份材料的一个短板,复现时值得在文档里补一段说明。

回测 2014 年:把 x1=60702.6 代入,得 ŷ=142165.84,真实值 136782,误差 5383.84 万人,相对误差约 3.9%。这个结果是后面所有模型对比的基准线。

3.3 用 statsmodels 复现并回测 2014

把上面的过程落成代码,重点在输出项的命名对应,方便和 SPSS 表逐项对照。

import statsmodels.api as sm # 一元模型:总人口 ~ 粮食产量 X = sm.add_constant(df["grain"]) m1 = sm.OLS(df["pop"], X).fit() # 对应论文表 1-1 模型摘要 print("R:", np.sqrt(m1.rsquared).round(3)) # 0.909 print("R2:", m1.rsquared.round(3)) # 0.826 print("adj R2:", m1.rsquared_adj.round(3)) # 0.821 print("SE:", np.sqrt(m1.mse_resid).round(3)) # 4985.997 # 对应论文表 1-2 系数表 print(m1.params.round(3)) # const 53054.404 / grain 1.468 print(m1.bse.round(3)) # 标准误 print(m1.tvalues.round(3)) # t 值 print(m1.pvalues.round(3)) # 显著性 # 回测 2014 x2014 = df.loc[df.year == 2014, "grain"].iloc[0] y2014 = df.loc[df.year == 2014, "pop"].iloc[0] pred = m1.params["const"] + m1.params["grain"] * x2014 print(pred, y2014, pred - y2014) # ≈142165.8 / 136782 / ≈5383.8

m1.mse_resid是残差均方,开根号才是 SPSS 里那个「标准偏斜度错误」,直接读m1.mse_resid会得到 2485 万量级的平方值,这是新手对照表格时最常犯的错。m1.params是按列名索引的 Series,用params["grain"]取系数比按位置取更安全,特征顺序一变也不会错位。

3.4 一元模型的边界:R² 高不等于外推准

0.826 的 R² 在课程作业里算漂亮数字,但它衡量的是样本内的解释力,不是样本外的预测力。这条直线在 1980—2014 区间内拟合得不错,一旦外推到 2015 年以后就会出问题:按 1.468 的斜率,粮食产量每涨 1 万吨人口涨 1.47 万,而近十年实际人口增速已经接近零,模型会持续高估。论文用 2014 年做回测时误差 5383 万,本质就是这个结构性变化在残差里的体现。

判断一个一元模型能不能用,我一般看三个东西:残差随自变量是否呈现系统性弯曲(有弯曲说明该换非线性形式)、残差随年份是否单调漂移(有漂移说明漏了时间趋势项)、预测区间宽度是否大得没有意义。这份数据的残差在 1998 年前后明显偏低、2010 年后明显偏高,第二条就已经不满足了,所以后面必须往多元和非线性走。

4. 从多元回归到非线性拟合:共线性、逐步回归与三次曲线的取舍

第 1 章的误差是 5383 万,第 2 章把四个自变量全塞进去,误差降到 1400 万;逐步回归再砍掉一个变量,误差降到 172 万;最后三次曲线把误差压到 607 万但 R² 是 1.000。这组数字之间的关系不直观,得逐个模型拆开看。

4.1 四变量多元回归:方差分析表告诉我们模型整体显著

多元模型把粮食产量、GDP、出生率、死亡率同时放进方程,论文表 2-1 给出 R=0.972,R²=0.944,调整后 R²=0.936,标准误 2967.56830。相比一元模型,R² 提升了 0.118,标准误从 4990 降到 2968,解释力确实上了一个台阶。

表 2-2 的方差分析:回归平方和 4448849243.984,自由度 4;残差平方和 264193847.616,自由度 30;总计 4713043091.600,自由度 34;F=126.295,sig=0.000。F 检验通过说明「四个系数不全为零」,但通不过说明不了每个变量都该留。表 2-3 的系数表里,GDP 的 sig=0.531、死亡率的 sig=0.275,两个都没通过 0.05,只有粮食产量(sig=0.000)和出生率(sig=0.000)显著。

feat = ["grain", "gdp", "birth", "death"] X = sm.add_constant(df[feat]) m2 = sm.OLS(df["pop"], X).fit() print(m2.rsquared.round(3), m2.rsquared_adj.round(3)) # 0.944 / 0.936 print(m2.fvalue.round(3), m2.f_pvalue) # 126.295 / ≈0 print(m2.params.round(3)) print(m2.pvalues.round(3))

m2.fvalue和m2.f_pvalue对应方差分析表最后两列,m2.params的顺序跟feat一致,读取时按列名索引。注意sm.add_constant必须显式调用,statsmodels 默认不加截距,漏了这一步系数会和 SPSS 完全对不上。

4.2 多重共线性诊断三件套:特征值、条件指数、VIF

论文表 2-5 给了三个判据:特征值有 3 个接近 0、条件指数有 3 个大于 10、死亡率对应的方差比例接近 1。这三条分别对应共线性的三种表现——特征值看整体维度的病态程度,条件指数是最大特征值与其他特征值之比的平方根,方差比例看具体某个变量在哪个维度上贡献过大。

表 2-6 在剔除 GDP 之后给出 VIF:出生率 2.868、粮食产量 4.146、死亡率 1.808,全部小于 10,容许度分别是 0.349、0.241、0.553,都大于 0.1,判定共线性不严重。这套阈值(VIF<10、容许度>0.1)是经验值,样本小的时候偏保守,样本大的时候可能太松。

from statsmodels.stats.outliers_influence import variance_inflation_factor X = sm.add_constant(df[feat]) for i, name in enumerate(X.columns): if name == "const": continue # 常数项不参与共线性诊断,值没有意义 print(name, round(variance_inflation_factor(X.values, i), 3))

variance_inflation_factor的第一个参数必须是带截距的设计矩阵,第二个参数是列索引。常数项那一列的 VIF 通常大得离谱,直接跳过,否则会误判成严重共线。VIF 是成对诊断,只能告诉我们「某个变量能被其余变量线性表示的程度」,看不了三个以上变量的联合共线,所以论文同时给了特征值和条件指数,这个组合是对的。

4.3 逐步回归的有进有出:砍掉 GDP 之后

逐步回归的逻辑是每引入一个变量就回头检验已引入的变量,不显著的剔除,直到没有变量可进可出。论文表 2-4 给了三步过程:第一步只留出生率,R² 未列;第二步加入粮食产量;第三步加入死亡率。最终方程:

ŷ = 134678.878 - 1366.202×x3 + 1.012×x1 - 5681.557×x4

GDP 被剔除,理由是它在四变量模型里 sig=0.531,且与粮食产量高度相关。回测 2014 年得 138506.22,误差 172.22 万人,比全变量模型的 1400.25 小了近一个数量级。这个结果看起来反直觉——变量少了反而更准——原因在样本量只有 35、自变量却有 4 个,自由度被吃得太多,多出来的变量带来的方差增量超过了偏差减少量。

def stepwise(X, y, threshold=0.05): """按 p 值后向剔除,threshold 是保留变量的显著性门槛""" cols = list(X.columns) while len(cols) > 1: model = sm.OLS(y, sm.add_constant(X[cols])).fit() pvals = model.pvalues.drop("const") worst = pvals.idxmax() if pvals.max() > threshold: cols.remove(worst) # 最不显著的先出局 else: break return model, cols model_step, kept = stepwise(df[feat], df["pop"]) print(kept) # 应当是 grain / birth / death print(model_step.params.round(3))

threshold控制剔除力度,0.05 是常用值,样本量小时可以放宽到 0.10 避免剔得太狠。这个实现是纯后向剔除,没有「进」的过程,和 SPSS 的逐步回归结果在变量选择上可能略有差异,但最终保留的变量集一般一致。想完全复刻 SPSS,需要在每剔一个变量后重新检查已剔除变量能否重新进入,多一层循环。

4.4 主成分回归:降维换来的精度代价

论文表 2-7 的主成分分析显示,第一主成分解释 76.004% 的方差,第二主成分解释 18.176%,累计 94.179%,所以取前两个主成分建立回归。表 2-8 的回归方程:

ŷ = 53075.711 + 1.468×x1 + 2.494e-5×x2

回测 2014 年得 142180.53,误差 498.53 万(论文写 142180.52727,与真实值 136782 相比误差约 5398 万),实际上比逐步回归差得多。论文自己的解释是主成分的含义模糊、降维后的变量失去了原始业务解释力。

from sklearn.decomposition import PCA from sklearn.preprocessing import StandardScaler Z = StandardScaler().fit_transform(df[feat]) pca = PCA().fit(Z) print(pca.explained_variance_ratio_.round(3)) # 各主成分贡献率 print(pca.explained_variance_ratio_.cumsum().round(3)) # 累计贡献率 print(pca.components_[:2].round(3)) # 前两个主成分的载荷

explained_variance_ratio_就是论文表 2-7 里「变异 %」那一列,cumsum()对应「累加 %」。做主成分回归时必须先标准化,否则量纲最大的 GDP 会独占第一主成分,得到的成分完全没有统计意义。

4.5 三次曲线与 Logistic:非线性模型的两种写法

论文第 3 章换了思路,不再用经济指标解释人口,而是直接对时间 t 拟合趋势曲线。以 1980 年为 t=1,2014 年为 t=35,三次曲线模型:

ŷ = 96103.588 + 1849.094t - 17.683t² - 0.070t³

R=1.000,R²=0.999,标准误 365.498,三个系数全部通过显著性检验(t 分别为 28.230、-

本文还有配套的精品资源,点击获取

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

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

立即咨询