简介这份资源面向具备一定R语言基础、希望深入掌握时间序列建模的数据分析与预测从业者聚焦加法与乘法两类过程并延伸至广义可加模型GAM的非线性建模思路。内容围绕趋势、季节性与随机成分的组合方式展开涉及ARIMA、SARIMA、STL分解以及mgcv包中gam()函数的应用帮助读者理解不同模型框架的适用场景与选择依据。压缩包内共1个文件为一份R脚本整体约1KB代码涵盖数据加载、模型构建与结果解释等环节可直接运行对照学习。目前已有618人学习下载适合作为时间序列建模的实操参考便于读者在真实数据上复现加法与乘法过程并借助GAM处理复杂非线性趋势提升预测与诊断能力。1. 时间序列里的加法与乘法为什么你的 GAM 预测总在季节峰值上翻车很多做时间序列的人第一次接触 GAM都是被它那句“自动拟合非线性趋势”吸引进来的。可真正把mgcv::gam()套到带明显季节性的数据上十有八九会遇到同一个翻车现场趋势拟合得挺漂亮一到每年旺季的尖峰就被系统性低估淡季又被高估。你以为是平滑参数没调好其实根子在于——你默认用了加法结构而数据生成过程很可能是乘法的。时间序列模型里的“加法”和“乘法”说的不是算术而是成分之间怎么组合。加法过程假设趋势、季节、残差是相加关系季节波动的绝对幅度全年恒定乘法过程假设它们相乘季节波动的幅度随趋势水平同比放大。R 语言里无论是decompose()的typeadditive/multiplicative还是 SARIMA 的差分与对数变换本质上都在处理这个选择。而 GAM 作为广义可加模型它的可加性又是另一层含义——是“平滑项相加”不是“成分相加”。这两层“加法”混在一起正是大多数人踩坑的起点。这篇笔记就按“先分清两种过程 → 用 GAM 分别建模 → 避坑 → 进阶验证”的顺序把这条链路走通适合已经会用 R 跑回归、但被季节数据折磨过的从业者。2. 加法与乘法过程先分清数据生成机制再谈建模2.1 加法过程与乘法过程的判别标准加法过程的数学形式是 $y_t T_t S_t e_t$趋势项 $T_t$、季节项 $S_t$、残差 $e_t$ 直接相加。它的关键特征是季节振幅不随趋势变化——不管趋势在高位还是低位季节波峰波谷的绝对落差基本一致。乘法过程是 $y_t T_t \times S_t \times e_t$季节因子是一个围绕 1 波动的比例趋势越高季节的绝对波动越大。判别方法不用太玄学画一张图基本就能看出来。把数据按时间画出来观察季节波峰到波谷的垂直距离如果这个距离在趋势上升段和下降段差不多偏加法如果趋势越高距离越大偏乘法。更严谨一点可以对数据取对数再画一次如果取对数后季节振幅变得均匀那原始数据就是乘法过程——这正是对数变换能把乘法转成加法的原理。R 语言里decompose()和stl()都提供type参数typeadditive对应加法typemultiplicative对应乘法。但要注意decompose()要求周期至少两个完整循环且它对趋势的估计用的是移动平均端点会缺失。实际项目里我更倾向先做判别再决定后续建模路径而不是直接丢给decompose()了事。2.2 用 R 快速判别一张图加一个方差比下面这段代码做两件事画出原始序列与对数序列的对比图并计算分段的季节振幅比给出一个粗略的量化判据。# 假设 ts_data 是 ts 对象频率为 12月度数据 library(forecast) # 1. 原始序列与对数序列对比 par(mfrow c(2, 1)) plot(ts_data, main 原始序列, ylab 值) plot(log(ts_data), main 对数序列, ylab log(值)) # 2. 分段季节振幅比把序列切成前后两半各算季节振幅 n - length(ts_data) half - floor(n / 2) amp_front - diff(range(tapply(ts_data[1:half], cycle(ts_data)[1:half], mean))) amp_back - diff(range(tapply(ts_data[(half 1):n], cycle(ts_data)[(half 1):n], mean))) cat(前半段季节振幅:, round(amp_front, 2), \n) cat(后半段季节振幅:, round(amp_back, 2), \n) cat(振幅比(后/前):, round(amp_back / amp_front, 3), \n)逻辑说明cycle()返回每个观测所处的季节位置tapply()按季节位置求均值得到季节轮廓diff(range(...))就是该段的季节振幅。参数说明frequency必须设对月度是 12季度是 4周度是 52设错会导致cycle()分组全乱。判读标准振幅比接近 1 偏加法明显大于 1比如 1.5 以上且对数序列振幅变均匀就按乘法处理。这个比值只是辅助最终还要结合业务判断——比如销售额、流量这类量纲随规模增长的指标天然偏乘法。2.3 为什么 GAM 的“可加”和过程的“加法”不是一回事这是最容易混淆的地方。GAM 的可加性指的是模型形式$g(\mu) \beta_0 f_1(x_1) f_2(x_2) \dots$多个平滑函数相加。它描述的是预测变量对响应变量期望的影响如何叠加跟时间序列成分是相加还是相乘没有直接关系。换句话说你完全可以用一个“可加”的 GAM 去拟合一个“乘法”过程的时间序列前提是你对响应变量做了对数变换或者把季节项建成随趋势变化的交互平滑。反过来如果你把乘法过程的数据直接喂给加法形式的 GAM模型会试图用一条平滑曲线去同时解释趋势和放大的季节波动结果就是趋势被季节带偏季节峰值被平滑掉。理解这一层后面选建模路径才不会迷。3. 用 mgcv 把加法与乘法过程分别建成 GAM3.1 加法过程的 GAM 建模趋势平滑加周期因子加法过程最直接的做法是把趋势建成时间索引的平滑项季节建成周期因子或周期平滑项两者相加。library(mgcv) # 构造数据框time 为连续时间索引month 为季节因子 df - data.frame( y as.numeric(ts_data), time as.numeric(time(ts_data)), month factor(cycle(ts_data)) ) # 加法 GAM趋势用薄板样条季节用因子 fit_add - gam(y ~ s(time, k 20) month, data df, method REML) summary(fit_add) plot(fit_add, pages 1, shade TRUE)逻辑说明s(time, k 20)是趋势平滑项k是基函数维度决定平滑的最大弯曲能力month是季节因子估计 12 个月各自的截距偏移。method REML是平滑参数估计方法比默认的 GCV 更稳定不容易过拟合。参数说明k不能设得比数据能支撑的自由度还大月度数据 10 年也就 120 个点k 20已经够用设到 50 会触发k.check()警告。plot()里shade TRUE画出置信带pages 1把多个平滑项画在一页。加法 GAM 的优点是解释性强趋势和季节的贡献可以分开看。缺点是它假设季节振幅恒定如果数据其实是乘法的month的系数会是一个折中值旺季低估、淡季高估。3.2 乘法过程的 GAM 建模对数变换与变系数平滑乘法过程有两条主流路径。第一条是对数变换后套加法 GAM因为 $\log(T \times S \times e) \log T \log S \log e$乘法变加法。第二条是保持原始尺度把季节项建成随趋势变化的变系数平滑用by参数实现。# 路径一对数变换 加法 GAM df$log_y - log(df$y) fit_log - gam(log_y ~ s(time, k 20) month, data df, method REML) # 预测时记得指数还原 pred_log - predict(fit_log, newdata df, type response) pred_orig - exp(pred_log) # 路径二变系数平滑季节平滑随趋势水平变化 fit_var - gam(y ~ s(time, k 20) s(time, by month, k 10), data df, method REML)逻辑说明路径一里log_y把乘法转成加法exp()还原时要注意如果type response已经做了链接函数反变换再exp()会重复。gam()默认高斯族用恒等链接type response不改变尺度所以这里exp()是必要的。路径二里s(time, by month)是变系数平滑为每个月拟合一条随时间的平滑曲线相当于允许季节形态随时间演变能捕捉乘法过程里季节振幅随趋势放大的特征。参数说明by变量必须是因子k 10控制每条曲线的复杂度月份多的时候要适当降低k避免过拟合。两条路径怎么选数据量小、季节形态稳定用对数变换更稳数据量大、季节形态本身在演变用变系数平滑更灵活。我一般先跑对数变换版本作为基线再用AIC()或k.check()对比变系数版本看提升是否值得多出来的复杂度。3.3 用 AIC 与残差诊断对比两种结构建完模型不能只看summary()的 p 值要做残差诊断和模型对比。# 残差诊断 par(mfrow c(2, 2)) gam.check(fit_add) gam.check(fit_log) # AIC 对比注意对数模型和原始尺度模型的 AIC 不可直接比 AIC(fit_add, fit_var) # 若要比对数模型需在对数尺度上比 fit_add_log - gam(log_y ~ s(time, k 20) month, data df, method REML) AIC(fit_add_log, fit_log)逻辑说明gam.check()输出残差图、QQ 图、残差与拟合值图以及k.check的基函数维度检验。如果k.check的 p 值显著说明k设小了平滑项被截断。参数说明AIC 对比必须在同一响应尺度上进行fit_add和fit_var都是原始尺度可以比fit_add_log和fit_log都是对数尺度可以比跨尺度比是常见错误。残差如果呈现明显的季节自相关说明模型没吃干净季节结构要考虑加 AR 项或改用gamm()。4. 避坑与排查GAM 拟合时间序列最常见的五个翻车点4.1 现象季节峰值系统性低估残差在旺季为正原因数据是乘法过程却用了加法 GAM季节因子被平均化旺季的真实放大效应被压缩。解决先做 2.2 的振幅比判别确认乘法后改用对数变换或变系数平滑。判断依据是残差图——如果残差在旺季集中为正、淡季集中为负基本就是结构选错。4.2 现象gam.check()报k值过小平滑项被截断原因s(time, k ...)的k设得不够趋势的弯曲能力被限制模型欠拟合。解决逐步提高k直到k.check的 p 值不显著但不要一次设太大否则计算慢且容易过拟合。经验做法是从k 10起步每次加 5配合method REML让平滑参数自己收缩。4.3 现象预测新数据时报因子水平不匹配原因训练时的month因子水平和新数据的month水平不一致比如训练数据只有 1 到 10 月预测数据有 12 月。解决在构造因子时显式指定levels 1:12保证训练和预测的水平集合一致。这是 R 因子建模的血泪经验predict()报错十有八九是这个。4.4 现象对数变换后预测值还原出现偏差原因log()变换后的预测是条件中位数exp()还原得到的是中位数而非均值直接当均值用会偏低。解决如果业务要的是均值预测还原时要加方差修正项近似为exp(pred sigma^2 / 2)其中sigma^2是残差方差。或者干脆在原始尺度上用变系数平滑绕开还原问题。4.5 现象残差自相关严重AIC 看着好但预测差原因GAM 假设观测独立时间序列的残差自相关被忽略模型把自相关误当成信号拟合了。解决改用gamm()加corAR1()相关结构或先做差分再建 GAM。检查方法是acf(resid(fit_add))如果滞后 1 或 12 阶超出置信带就要处理。5. 进阶用变系数平滑验证乘法结构并做滚动预测走到这里模型基本能跑了。但真正决定这套方案值不值得投入的是它能不能在滚动预测里稳定。我一般会做两件事用变系数平滑的显著性验证乘法结构是否真实存在再用时间序列交叉验证看预测误差。先看变系数平滑的验证。如果s(time, by month)的各个平滑项整体显著且不同月份的曲线斜率方向不一致说明季节振幅确实随趋势变化乘法结构成立。可以用summary(fit_var)看每个by水平的 edf有效自由度edf 接近 1 说明该月基本是线性edf 明显大于 1 说明该月季节响应非线性。# 查看变系数平滑各水平的 edf summary(fit_var) # 时间序列交叉验证滚动原点预测 library(forecast) ts_cv - tsCV(ts_data, function(y, h) { df_train - data.frame( y as.numeric(y), time as.numeric(time(y)), month factor(cycle(y), levels 1:12) ) fit - gam(y ~ s(time, k 20) month, data df_train, method REML) df_test - data.frame( time max(df_train$time) 1:h, month factor(cycle(y)[1:h], levels 1:12) ) predict(fit, newdata df_test) }, h 12) # 汇总各步误差 cv_rmse - sqrt(colMeans(ts_cv^2, na.rm TRUE)) print(round(cv_rmse, 2))逻辑说明tsCV()做滚动原点交叉验证每次用历史数据训练、预测未来h步h 12对应一年。参数说明tsCV的window参数控制训练窗口长度不设则用扩张窗口。colMeans(ts_cv^2)按预测步长汇总 RMSE能看出误差随步长怎么增长。如果 1 步 RMSE 明显小于 12 步说明模型短期可用、长期衰减快业务上就要限制预测跨度。一个具体技巧把加法 GAM、对数 GAM、变系数 GAM 三条路径的滚动 RMSE 放在一张表里对比比单看 AIC 靠谱得多。模型路径1 步 RMSE6 步 RMSE12 步 RMSE适用场景加法 GAM较低中等偏高季节振幅稳定对数 GAM低低中等乘法结构明显、数据量中等变系数 GAM最低低低季节形态随时间演变、数据量大这张表不是标准答案是我在几个项目里反复跑出来的经验区间。对数 GAM 通常在乘法数据上全面优于加法 GAM变系数 GAM 在数据量足够时最好但数据少于三四个完整周期时容易过拟合反而不如对数 GAM 稳。最后说个我自己的习惯每次建完 GAM我都会把残差按季节位置分组画箱线图如果某个季节的残差箱体明显偏离零线不管 AIC 多好看我都会回去检查结构选型。这个动作帮我拦下过好几次“指标漂亮但预测翻车”的模型。时间序列建模没有后悔药结构选错后面调参都是白费。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?