简介:一份基于ARIMA模型的新能源汽车销量预测学术论文PDF,内容选自《企业科技与发展》2020年第10期。论文以ARIMA时间序列模型为核心,选取2014年1月至2019年5月新能源汽车月度销量数据,依次完成差分平稳化、ACF与PACF分析、模型识别定阶和SPSS参数估计,最终建立ARIMA(1,0,0)(1,1,0)模型,并对2019年3至5月销量进行预测验证,结果显示相对误差在6%至9%之间,能较好反映销量趋势。内容还讨论了新能源汽车的市场销量实际情况及我国厂商研究起步较晚等背景,适合车辆工程、统计及数据分析方向的学生与研究者作为建模参考,也可用于课程论文或销量预测课题的方法借鉴。资料共1个PDF文件,压缩包大小1.11MB,图表和公式完整,便于阅读与引用;已有479人学习浏览。阅读后可以快速掌握ARIMA模型从数据预处理、平稳化、建模到短期预测的完整流程,并了解新能源汽车市场的销量波动规律。
1. 先看懂这张表:新能源汽车销量预测为什么绕不开 ARIMA
年底的排产会上,计划员问:这款车下个月产能到底扩多少?销售说市场还在涨,供应链说库存压不起,谁都拿不出一个带置信区间的数。基于 ARIMA 模型的新能源汽车销量预测,解决的就是这个真实痛点。它不需要深度学习的算力环境,也不需要外部特征工程,只用销量自身的历史轨迹,就能给出未来几个周期的点预测和区间预测。对生产排产、库存管理、销售目标拆解来说,ARIMA 是性价比最高的起点,也是任何前沿模型都绕不过去的对比基线。这份方案通常以一份包含数据预处理、定阶、回测与结论的 PDF 报告交付,而这篇笔记会把背后的完整链路拆开,从平稳性一路讲到滚动回测和残差诊断,全程用新能源月度销量数据走一遍,新手能跟得上,熟手也能看到参数边界。
2. 把 ARIMA 拆到能上手:平稳性、差分与 p d q 三个参数的直觉
2.1 从工程师视角看平稳性:为什么非平稳序列会让模型失效
ARIMA 的全称是 Auto Regressive Integrated Moving Average,自回归、差分、移动平均三件套。自回归项描述的是惯性:上个月的销量会通过订单结转、口碑扩散影响这个月。移动平均项描述的是冲击的衰减:一次补贴政策、一次降价促销造成的销量扰动,会在之后几个月逐步消化。这两个机制都假设序列围绕一个相对稳定的均值在波动。
问题在于,新能源汽车月销量从 2020 年的十几万台级别涨到 2023 年的八十万台级别,均值一直在抬升,根本不满足这个假设。如果直接拿原始序列去拟合自回归项,模型会把上升趋势当作“常态”学进去。一旦趋势斜率变化,比如补贴退坡导致增速放缓,模型的下一个预测就会大幅跑偏。平稳性因此成了 ARIMA 能不能成立的前提。
判断平稳性最常见的量化指标是 ADF 检验。它的原假设是序列存在单位根,也就是不平稳;p 值小于 0.05 时拒绝原假设,认为序列平稳。statsmodels 里一行就能调用。但这里有一个常见误用:很多同学看到 p 值是 0.04 就欢呼“平稳了”,而销量序列往往是“趋势明显但恰好通过检验”的边界情况。我一般会把 ADF 结果和折线图合在一起看,两条都站得住脚再往下走,毕竟统计检验不是免死金牌。
另外,单看均值还不够,方差也要稳。年末冲量月份的销量波动明显放大,这种异方差性 ARIMA 不能直接处理,通常先做对数变换或 Box-Cox 变换把波动压一压,再进入平稳性检验流程。这一步能显著降低后面残差诊断时出现“尖峰厚尾”的概率。
2.2 差分到底在消除什么:d 的取值逻辑与过度差分的代价
差分的直觉很简单:今天的值减昨天的值,把上升趋势变成围绕某个常数的波动。一阶差分y_t - y_{t-1}消除线性趋势;如果趋势带加速度,也就是增长率本身在变化,可能需要二阶差分。对新能源汽车月销量来说,一阶差分通常就够,因为绝对值虽然在涨,环比的波动相对稳定。
过度差分是新手最容易踩的坑。差分两次之后,序列方差被放大,原本干净的自相关结构被破坏,拟合出来的参数极不稳定。经验法则:d 能取 1 就不取 2;取 2 之前先看一阶差分后的自相关图,如果自相关系数在零附近小幅波动,说明趋势已经被清干净了,没必要再差一次。
用代码做差分没有专门的“差分函数”,pandas 的.diff()一步完成:
import pandas as pd x_diff = df["sales"].diff().dropna()差分之后重新画图、重跑 ADF。我习惯的做法是把 d=0、1、2 三种情况各算一遍 ADF,选一个 p 值最小且方差膨胀最小的 d,而不是只盯着 p 值看。统计量过了,还要看差分后的序列在业务上有没有意义——比如差分后出现大量负值,说明销量在环比下滑,这本身就是管理要看的信息。
2.3 ACF 与 PACF 看图定 p 和 q:不靠玄学,靠两个判据
差分完成后,要回答的问题是:自回归用几阶,移动平均用几阶。这里靠两张图:ACF 画的是序列与自身滞后值的相关性如何随阶数衰减;PACF 画的是剔除中间滞后项干扰后,某阶滞后的净贡献。判读规则很经典,做成表就是下面这样:
| 图形表现 | 模型选择 |
|---|---|
| PACF 在 k 阶后截尾,ACF 拖尾 | AR(k),p 取 k |
| ACF 在 k 阶后截尾,PACF 拖尾 | MA(k),q 取 k |
| ACF 与 PACF 都拖尾 | ARMA,需要网格搜索 |
| 两者都快速截尾 | 序列接近白噪声,不该硬套 ARIMA |
注意,图上的“截尾”在真实数据里很少干净利落。月度销量受节假日、政策、新车型影响,ACF 和 PACF 往往不是教科书形态。所以看图只用来初筛方向,最终定阶交给 AIC 网格搜索。statsmodels 的plot_acf和plot_pacf可以直接画这两张图,看的时候记得把显著相关的阈值线标出来,不要被几个恰好超过阈值的点带偏。
阶数也不是越大越好。p+q 一般控制在 5 以内,月度数据样本量通常只有几十个月,阶数一高,参数估计的方差立刻变大,出现“训练集拟合很好、验证集一塌糊涂”的情况。记住这个边界,后面网格搜索才不会把范围拉得离谱。
3. 用 Python 跑通最小预测链路:从新能源汽车月度销量到 ARIMA 拟合
3.1 数据准备:拿到销量序列后先做的三件事
拿到一张“年月 + 销量”的表,不要急着 fit。先做三件事:格式转换、缺失值检查、频率对齐。这三件事做不对,后面的滞后阶数全是错的。
import pandas as pd df = pd.read_csv("nev_sales_monthly.csv") df["month"] = pd.to_datetime(df["month"]) df = df.set_index("month").sort_index() # 检查 index 是否连续:生成完整月度序列,差集就是缺失月份 full_range = pd.date_range(start=df.index.min(), end=df.index.max(), freq="MS") print("缺失月份数量:", len(full_range.difference(df.index))) # 缺失值用上月值填充,注意不要引入未来信息 df = df.reindex(full_range).ffill() print(df.tail())逻辑说明:to_datetime把字符串统一成时间类型,set_index加sort_index保证时间轴有序。freq="MS"表示月度起点,用它生成完整日历范围,再做差集就能精确知道缺了哪些月份。reindex把缺失月份补成 NaN,ffill用上个月的值回填,这样模型在计算滞后阶数时不会因为索引跳变而错位。
参数说明里最容易忽略的是freq的写法。“MS”是 Month Start,旧版本里“M”表示日历月末但已经被标记废弃,新版本建议直接写“ME”。月度销量数据用“MS”最直观,既能对齐自然月,又能避免月末那一天的时区问题。
3.2 平稳性检验与差分:ADF 检验的代码与判读
from statsmodels.tsa.stattools import adfuller x = df["sales"] adf_result = adfuller(x.dropna()) print(f"ADF 统计量: {adf_result[0]:.4f}") print(f"p 值: {adf_result[1]:.4f}") if adf_result[1] < 0.05: print("原始序列近似平稳,d 可以取 0") else: x_diff = x.diff().dropna() adf_diff = adfuller(x_diff) print(f"一阶差分后 p 值: {adf_diff[1]:.4f}")逻辑说明:adfuller返回的元组里第 0 个是检验统计量,第 1 个是 p 值。小于 0.05 拒绝单位根原假设。这段代码先测原始序列,不平稳再做一阶差分,而不是一上来就无脑 d=1。很多入门教程直接默认差分,遇到本身平稳的序列反而引入了多余的移动平均项,属于帮倒忙。
这里的边界情况要特别注意:如果原始序列 p 值是 0.06,一阶差分后 p 值是 0.001,那显然选 d=1。如果原始序列 p 值是 0.04 但折线图趋势明显,我倾向于也做一次差分再看 ACF,因为差分后的模型通常更稳健。统计检验是参考,不是决策本身。
3.3 模型定阶与拟合:用 AIC 在网格搜索里选 (p, d, q)
import warnings import itertools from statsmodels.tsa.arima.model import ARIMA p_range = range(0, 4) q_range = range(0, 4) d_range = range(0, 2) best_aic = float("inf") best_order = None best_model = None warnings.filterwarnings("ignore") for p, d, q in itertools.product(p_range, q_range, d_range): try: model = ARIMA(x, order=(p, d, q)) res = model.fit() if res.aic < best_aic: best_aic = res.aic best_order = (p, d, q) best_model = res except Exception: continue print(f"最优阶数: {best_order}, AIC: {best_aic:.2f}")逻辑说明:itertools.product把 p、d、q 的所有组合遍历一遍,每组都拟合一次,记录最小 AIC 的阶数。try/except是因为部分阶数组合在数值上会收敛失败,直接跳过。
参数说明里,p_range 和 q_range 设成 0 到 3 是有意的。月度销量数据的滞后相关性一般两三个月就衰减完了,阶数再高只会增加过拟合风险。d_range 设 0 和 1,是因为前面 ADF 判定大概率只需要一阶差分。如果数据有强季度周期,比如 12 个月一个完整峰谷,纯 ARIMA 不够用,要考虑 SARIMA 的季节项(P,D,Q,12)。网格搜索的范围先小后大,基线 AIC 出来后再往邻近阶数微调,性价比最高。
3.4 训练测试切分与滚动预测评估
import numpy as np from sklearn.metrics import mean_absolute_error, mean_squared_error train = x.iloc[:-6] # 留最后 6 个月做验证 test = x.iloc[-6:] model = ARIMA(train, order=best_order).fit() forecast = model.forecast(steps=len(test)) mae = mean_absolute_error(test, forecast) rmse = mean_squared_error(test, forecast, squared=False) mape = np.mean(np.abs((test - forecast) / test)) * 100 print(f"MAE: {mae:.0f} 辆, RMSE: {rmse:.0f} 辆, MAPE: {mape:.2f}%")这段代码把最后 6 个月作为验证段,前面的数据参与训练,用forecast生成未来 6 步的预测,再和真实值对比。MAPE 是百分比误差,给业务方汇报时最直观;但当某个月销量因疫情停工等原因跌到几百台时,MAPE 会瞬间爆炸成几千,这种情况改用 MAE 或 RMSE 更稳。
forecast和predict的区别在这里很关键:forecast(steps=6)是纯样本外预测,predict(start, end)可以指定起点,起点落在训练集内时输出的是拟合值。做评估一定用forecast,不要用predict把训练集内的拟合值混进误差计算,那会严重低估真实误差,让你误以为模型精度很高。
4. 销量预测里的 5 个必调参数:从季节分解到置信区间
4.1 季节分解:月度销量的周期到底藏在哪里
新能源汽车销量有两个明显的周期性来源:一是 12 月厂商冲量带来的年末高峰,二是春节所在月份的低谷。这两个周期叠加在长期增长趋势上,让原始序列看起来“有规律但不规则”。ARIMA 本身不感知周期,所以先分解、看清周期存在的证据,再决定要不要升级为 SARIMA。
from statsmodels.tsa.seasonal import seasonal_decompose result = seasonal_decompose(x, model="multiplicative", period=12) result.trend.plot() result.seasonal.plot() result.resid.plot()逻辑说明:seasonal_decompose把序列拆成趋势、季节、残差三项。model="multiplicative"适合乘性季节效应,也就是旺季波动幅度和销量绝对值成正比,新能源汽车月度数据就是这种形态;如果波动幅度不随水平变化,改用"additive"。period=12表示以 12 个月为完整周期。
如果分解出的季节分量在最近两年明显变形,比如新车型放量把季节性节奏打乱了,说明季节模式不稳定,用 SARIMA 的固定周期假设反而危险。这种情况下直接用差分加 ARIMA,让模型自己去捕捉残留下来的短期相关性,比强行上 SARIMA 更稳。
4.2 训练窗口长度:不是数据越多越好
很多拿到手的新能源销量序列只有三四年,因为 2015 年前后才有规模。这时候把全部历史都塞进训练集,早期“补贴驱动”的数据反而会污染模型。我一般先看趋势图,选销量进入稳定增长期的起点,再确定训练窗口,通常 24 到 36 个月就够 ARIMA 估计参数了。
这个参数没有硬性公式,但有一个验证方法:把窗口从 12 个月逐步加到 48 个月,每个窗口都做一次滚动回测,画出“窗口长度对回测 RMSE”的曲线,取最低点。这就是超参数选择的基本功,用验证误差说话,而不是凭直觉拍脑袋。
4.3 预测步长与置信区间:汇报时用 80% 还是 95%
forecast_res = best_model.get_forecast(steps=6) mean = forecast_res.predicted_mean conf_80 = forecast_res.conf_int(alpha=0.2) conf_95 = forecast_res.conf_int(alpha=0.05)get_forecast返回一个PredictionResults对象,predicted_mean是点预测,conf_int(alpha)给出置信区间。alpha 是显著性水平,0.2 对应 80% 置信区间,0.05 对应 95%。向业务方汇报时,80% 区间更实用,因为区间窄,能直接支撑排产上下限;95% 区间宽到经常“等于没说”,只用来做极端风险情景测试。
这里有一个很多人会犯的错:把置信区间当成业务承诺。ARIMA 的区间只反映模型不确定性,不反映政策突变、芯片短缺这类结构性风险。所以不管选哪个 alpha,汇报时都要补一句:这是基于历史规律的外推,外部冲击不在区间之内。
4.4 残差诊断:模型好不好,先看残差是不是白噪声
模型拟合完之后,残差序列应该看起来像白噪声,也就是没有任何可被利用的信息残留。检验方法有 Ljung-Box 检验和 QQ 图。
from statsmodels.stats.diagnostic import acorr_ljungbox resid = best_model.resid lb_test = acorr_ljungbox(resid, lags=[6, 12], return_df=True) print(lb_test)acorr_ljungbox的 p 值大于 0.05,说明残差没有显著自相关性,模型已经把信息榨干了;p 值很小,说明还有模式没抓到。看到 p 值小于 0.05,先别急着换 LSTM,很多情况下只是 p、q 阶数偏低。把网格搜索的范围扩大一点再跑一轮,成本远低于换模型。
残差诊断还有一个容易被忽略的点:best_model.resid在训练集上计算,拟合阶段的残差异常并不代表预测阶段的误差。所以残差诊断只能证明模型没有遗漏模式,不能证明预测精度高,精度评估还是要靠第 3 章里的验证集切分。
4.5 差分阶数上限与模型复杂度控制
ARIMA 的阶数不是越复杂越好。低频月度数据上,p+q 超过 5 之后,参数估计方差会快速变大,样本量只有 30 到 50 个月根本支撑不起复杂模型。网格搜索的小技巧是先跑一个小范围,拿到基线 AIC,再在基线附近逐个加阶数。如果增加一阶只让 AIC 降低了不到 2,说明模型复杂度提升没有实质意义。AIC 差值 2 以下通常视作无显著改进,这是信息准则的经验阈值。
5. 避坑指南:新能源汽车销量数据特有的 4 个翻车现场
5.1 现象一:政策补贴退坡让序列整体突变,长期预测偏高
现象:2019 年补贴大幅退坡后,下半年销量环比下滑,模型基于上半年趋势外推,预测值明显高估。这类翻车在每次补贴政策调整周期都会出现。
原因:ARIMA 没有外部变量通道,不知道“政策”这个外生冲击。序列在某个时间点发生了水平位移,但自回归项仍按旧均值外推,模型自己调整不过来。
解决:把训练数据截断在政策变化之后重新建模,或者升级为 ARIMAX,把补贴系数作为外生回归变量。更务实的做法是:政策密集期不做长期预测,只做未来 1 到 2 个月的短期滚动预测,让模型在滚动中逐步吸收新水平。排产计划本身就适合短周期滚动,不需要强行预测半年。
5.2 现象二:季度末冲量与春节带来的“假周期”
现象:每年 3 月、6 月、9 月、12 月销量明显抬高,季节分解跑出一个“4 个月周期”,但真实原因其实是渠道压库和季度末冲量。ARIMA 把这个当固定周期学进去,下一年淡季会被预测成旺季。
原因:季节分解只能识别周期性,识别不了周期的成因。促销、冲量、节假日错位形成的周期,在年份之间不是严格等间隔的。春节每年日期不同,低谷月未必永远是 2 月,固定周期假设自然失效。
解决:把春节按农历对齐到同一个月,或者用节假日哑变量做回归项。对季度末冲量,要判断这种冲量是不是年年存在;如果只是个别年份的渠道策略造成的,宁可当异常值修掉,也不要让模型去学一个不稳定的周期。这个判断需要业务侧确认,不能只看统计图表。
5.3 现象三:新车型上市导致结构突变,训练集和测试集不是同一个分布
现象:某品牌 2023 年上市一款爆款车型,月销量从 2 万台跳到 6 万台。旧数据训练的 ARIMA 在新水平上预测,误差动辄 30% 以上,模型基本报废。
原因:ARIMA 建模的前提是序列统计性质相对稳定。新车型是一个典型的 regime shift,均值、方差、自相关结构全变了,旧历史对新未来没有信息量,模型自然失效。
解决:以车型迭代为界截断训练集。如果不想丢失早期信息,用分段建模,把突变前和突变后各建一个模型;或者用 ARIMAX 把车型数量、上市时间作为外生变量。但最踏实的做法是承认模型失效,缩短预测步长,等突变后积累 3 到 5 个月新数据再重建基准。硬撑着用旧模型,不如直接告诉管理层“这个节点预测不可靠”。
5.4 现象四:差分两次后拟合极好但预测泛化差
现象:网格搜索跑出 d=2、AIC 极低,训练集拟合误差很小,验证集预测一塌糊涂。这是典型的“训练场上的表演”。
原因:过度差分放大了高频噪声,模型把噪声的短期相关性也学了进去。AIC 在二阶差分序列上偏低,不等于真实预测表现好,因为它衡量的是拟合优度,不是验证集误差。
解决:用前向验证作为最终评估准则,而不是只看 AIC。把 d=1 和 d=2 的模型各跑一次滚动回测,比较验证集 RMSE,选验证集上更好的那一个。这是整个 ARIMA 调参过程里最值得记住的一条:模型选择看测试误差,不看过拟合出来的好看曲线。
6. 让预测真正进排产计划:滚动回测与基线对比的验证方法
一套模型定下来之前,我会先做两件笨事:跑一个滚动回测,再和两个简单基线对比。滚动回测比一次性切分更接近生产环境,因为生产里你每个月都要用最新数据重新拟合,再预测未来几个月。
def rolling_backtest(data, order, step=6, train_min=24): errors = [] for start in range(train_min, len(data) - step + 1): train = data.iloc[:start] test = data.iloc[start:start + step] model = ARIMA(train, order=order).fit() pred = model.forecast(step) errors.append(np.mean(np.abs(test.values - pred))) return np.mean(errors)这段代码每次用起点之前的所有数据训练,预测接下来固定长度的窗口,然后逐步推进。train_min=24保证最早几轮也有足够样本。把所有窗口的平均绝对误差汇总,才是一个可靠的精度估计。
基线对比也是必须做的:朴素预测直接用最近一个月的值外推,季节朴素预测用去年同期的值,移动平均用最近 3 个月均值。如果 ARIMA 在这些基线面前没有明显优势,就不要上生产。我过去花三天调 SARIMA 的季节参数,回测一看只比“去年同期乘一个系数”好 0.3%,果断放弃。后来把精力转到数据清洗和异常值修正上,预测精度反而提升了 8%。这件事让我养成了习惯:任何时间序列项目,先做两个笨基线,再让 ARIMA 去拼,拼过了才继续深挖。
滚动回测还有个额外价值:它能把不同窗口下的误差分布画出来,让管理层看到“误差不是固定的,而是随市场波动变化”。排产会上一张误差分布图,比单点预测值更有说服力。希望这套从定阶到回测的流程,能帮你在排产会上拿出有依据的数字,而不是靠感觉拍板,希望帮到你。
本文还有配套的精品资源,点击获取