简介:基于ARIMA模型的新能源汽车销量预测PDF,是一份面向汽车行业数据分析人员、高校研究者和市场预测从业者的时间序列建模参考。资源为单个PDF文档,大小约1.11MB,收录了完整的期刊论文内容,详细展示ARIMA模型应用流程:选取2014年1月至2019年5月新能源汽车月度销量数据,通过差分处理消除非平稳性,利用ACF/PACF分析确定p、q阶数,确定ARIMA(1,0,0)(1,1,0)模型,并借助SPSS完成参数估计与显著性检验。文中使用2019年3-5月数据验证模型,预测相对误差在5%-9%,说明该模型对短期销量预测有较好效果。同时讨论了新能源汽车销量的季节性波动特征、极端值对预测误差的影响以及模型仅适合短期预测的局限,有利于读者全面评估ARIMA方法在实际场景中的适用边界。已有479人学习下载,适合想学习时间序列预测、把握新能源汽车市场趋势的读者参考,也可为相关研究及车企决策提供借鉴。
1. ARIMA模型预测新能源汽车销量:先解决排产和备货的短期需求
月度销量预测在车企排产、渠道备货里一直是刚需。新能源汽车销量序列通常只有几十到一两百个月度数据点,样本不长却有明显的季节性、季末冲量和行业政策扰动,这种数据特征恰好落在ARIMA模型最擅长也最容易翻车的区间。很多人以为ARIMA是统计学教材里的老古董,实际在中小规模单变量序列上,它比深度学习更稳——某车企分析师用ARIMA给出下三个月销量区间,在排产会上的误差控制比复杂模型更好。这篇文章把这条路线从数据清洗讲到滚动验证,重点放在参数怎么选、坑在哪、结果怎么让业务方信服。
2. 为什么是ARIMA:销量序列的三个结构特征与选型边界
ARIMA能在销量预测里站住脚,不是因为数学形式更漂亮,而是因为月度销量本身可以被拆成「沿用上期惯性 + 修正前期误差 + 随机冲击」三段结构。新能源车月度数据里,这三个成分都很明显:整体渗透率提升带来长期向上趋势;季度末冲量带来周期性凸起;某个月上市热门车型或者价格调整,销量跳增后需要用随机项吸收。ARIMA的 p、d、q 三个参数恰好对应这三件事,模型解释起来也直接。
2.1 ARIMA三个参数在销量序列里各管什么
p 是自回归阶数,描述当前月销量和前面 p 个月销量的线性关系。对新能源汽车来说,p 通常不会太大,因为消费者的购车决策不完全是惯性行为——上个月销了五万辆不代表这个月还销五万辆,但前两三个月的趋势会延续,所以 p 取 1 到 3 是常见范围。
d 是差分次数,用来把非平稳序列变成平稳序列。销量序列有长期上升趋势,一阶差分后,就能把「绝对销量」变成「环比增量」,模型学的是一段时间内每个月相比上个月多卖还是少卖。这个参数最容易被忽略,它定错了后面全错。行业内常用的经验是先做 ADF 检验,但也要结合目视判断。
q 是移动平均阶数,描述用过去的预测误差来修正当前预测。对应到销量场景里,比如某个月因为突发政策变动销量低于预期,下个月模型需要把这种误差记忆进去。q 取 0 到 2 就能覆盖最常见的短期冲击。三个参数的具体选择不是越复杂越好,销量数据本来就只有一百个点左右,参数太多会导致过拟合,预测区间反而变得不可信。
2.2 ARIMA和Prophet、LSTM在销量预测上的边界
这三个工具在时间序列预测里经常被拿来对比,但它们的适用场景差异很大。用一张对比表来收拢边界:
| 工具 | 数据量要求 | 季节性处理 | 可解释性 | 落地成本 |
|---|---|---|---|---|
| ARIMA | 50~200个点最合适 | 需手动识别季节性,必要时扩展为SARIMA | 参数含义明确,能解释给业务方 | 代码量小,运行快 |
| Prophet | 对数据量要求弹性大 | 内置年/周季节性,适合节假日多的人 | 可解释性中等,趋势加季节性拆解 | 调参项多,需要反复试 |
| LSTM | 通常需要数百上千个点 | 需要自行构造窗口和特征 | 黑匣子,难向业务方解释 | 训练时间长,部署成本高 |
对于新能源汽车月度销量,样本量往往不够喂饱 LSTM,强行训练只会得到拟合得漂亮、预测得离谱的结果。Prophet 擅长处理节假日效应,但新能源销量受车企冲量和促销节奏影响远大于受法定节假日影响,Prophet 的优势发挥不出来。ARIMA 的单变量线性结构和较低的数据需求正好匹配这个场景。需要注意的是,这里的适用边界是「月度数据」——如果你拿到的是周度销量,波动大、周期不整,ARIMA 的表现会明显变差,这时候要考虑换工具。
3. 数据准备:把销量原始明细整理成ARIMA能吃的月度序列
这一步决定了模型的天花板。ARIMA 本身不吃外部特征,它只信任你喂进去的历史序列长什么样。如果你拿到的原始数据是每日开票明细、经销商报备记录或者联网上牌清单,直接按月汇总之前,必须先想清楚按什么口径汇总,因为不同口径给出的序列结构完全不同。
3.1 先定口径:批发量、零售量、上牌量预测结果差别很大
行业内最常见的三种口径是批发量、零售量和上牌量。批发量是厂商卖给经销商的数量,它包含渠道库存变化,季度末经常出现暴涨,因为厂商要把货压到经销商手里冲业绩。零售量更接近真实市场需求,但统计回来有延迟。上牌量最真实,但是用户买车到上牌存在一两个月的时间差,滞后问题反而干扰预测。
如果直接用批发量训练 ARIMA,模型会把「每季度末暴涨」当成规律,下个季度末要是渠道库存策略变化,预测就会翻车。我一般建议优先用零售量或者上牌量做预测基准,把批发量作为对照序列去观察渠道库存压力。如果只能拿到批发量,也要先把季末冲量识别出来,单独标记或者在后续评估里排除。口径换一次,整个序列的结构就变了,模型的参数也需要重新定阶,不要拿着一套参数换数据直接用。
3.2 缺失值、季末冲量和节假日的处理策略
月度序列最常见的缺失是某个月没有回传数据,或者数据源在切换统计口径后中间断档。缺失月份不能简单用全局均值填充,因为销量有明显的趋势,均值填充会把上升趋势拉平。连续空缺用线性插值,单点空缺用最近邻,这是最稳妥的基础策略。
季末冲量不是噪声,是真实存在的业务行为。不要为了「让数据更干净」把每年 3 月、6 月、9 月、12 月的异常高点抹平,ARIMA 需要看到这些规律才能学习它。真正要处理的是那些因为促销活动或者市场突发事件造成的异常点,比如某个月的销量偏离正常水平超过 3 倍标准差,这时候可以用前后三个月的中位数做平滑,防止模型把一次性事件当成长期规律。
节假日对月度数据的影响集中在春节。春节在 1 月和 2 月之间漂移,导致这两个月的销量经常错位:有的年份春节在 1 月,1 月销量就明显低,2 月回补;有的年份反过来。如果数据周期够长,建议单独加一个「春节偏移」标志,但 ARIMA 不支持外部哑变量,所以常见的做法是直接把春节影响的相邻两个月做合并统计,或者把这两个月作为异常点处理。做训练集和测试集切分时,也要保证这两个月在两边都有分布。
3.3 用pandas构建月度序列的最小代码
我一般会先把原始的日度明细按月聚合,用 pandas 重采样,再补全月份索引,这样 ARIMA 拿到的是一个连续无断裂的月度序列。
import pandas as pd import numpy as np # 假设有日粒度数据,字段:trade_date、sales_qty、channel_type df = pd.read_csv("ev_daily_sales.csv", parse_dates=["trade_date"]) # 只保留零售口径,避免批发冲量干扰 df = df[df["channel_type"] == "retail"] # 按月份聚合销量 monthly = ( df.groupby(df["trade_date"].dt.to_period("M"))["sales_qty"] .sum() .reset_index() ) monthly.columns = ["month", "sales"] # 转成时间索引,并规整到月初频率 monthly["month"] = monthly["month"].dt.to_timestamp() monthly = monthly.set_index("month").asfreq("MS") # 缺失月份补一个 NaN,再用线性插值填 monthly["sales"] = monthly["sales"].interpolate(method="linear") # 识别季末月份,后续分析用 monthly["is_qeom"] = (monthly.index.month % 3 == 0).astype(int) print(monthly.tail(12))这段代码有两个关键点。asfreq("MS")会把索引规整到每个自然月的第一天,如果某个月没有数据,对应位置就是 NaN。interpolate(method="linear")是按时间顺序做线性插值,适合销量这种有明显趋势的序列,不建议用均值填充。is_qeom这个标志现在还不喂给 ARIMA,但是做数据可视化、评估季度末预测误差的时候很有用。到这里,数据基本可以进模型了。
4. 建模落地:用statsmodels把ARIMA跑通并输出预测区间
数据准备好之后,建模流程并不复杂,关键是每一步都要有判断,不要靠参数搜索一把梭。完整流程是:先检验平稳性并确定 d,再通过 ACF/PACF 图初定 p 和 q,拟合后检查残差,最后输出带置信区间的预测。每一步都有对应的可执行代码,下面按顺序拆开。
4.1 平稳性检验和差分阶数d怎么定
ARIMA 的 I 部分要求序列平稳,意思是没有明显的趋势和周期漂移。用 ADF 检验看 p 值,大于 0.05 通常认为非平稳,需要差分。
from statsmodels.tsa.stattools import adfuller # 对原始销量做 ADF 检验 result_raw = adfuller(monthly["sales"].dropna()) print("原始序列 ADF p 值:", result_raw[1]) # 如果 p 值 > 0.05,做一阶差分后再检验 if result_raw[1] > 0.05: sales_diff = monthly["sales"].diff().dropna() result_diff = adfuller(sales_diff) print("一阶差分后 ADF p 值:", result_diff[1])ADF 检验的 p 值小于 0.05 才能拒绝「序列存在单位根」的原假设,也就是序列平稳。新能源汽车销量通常一阶差分就够,极少需要二阶差分。如果一阶差分后 p 值仍然大于 0.05,不要急着做二阶差分,先检查序列里是否存在明显的结构性断裂——比如某一年销量因为政策调整突然跳升,这时候需要裁掉早期数据,而不是加差分。
4.2 ACF/PACF看图定阶和AIC辅助判断
确定 d 之后,用 ACF 和 PACF 图来初选 p 和 q。PACF 在 lag k 之后突然截尾,p 可能取 k;ACF 在 lag k 之后截尾,q 可能取 k。实际操作里序列往往没有那么干净的截尾,所以我会同时跑几组候选 order 的 AIC,取最小值。
import matplotlib.pyplot as plt from statsmodels.graphics.tsaplots import plot_acf, plot_pacf fig, axes = plt.subplots(2, 2, figsize=(14, 8)) # 差分序列的 ACF/PACF plot_acf(sales_diff, ax=axes[0][0], lags=20) plot_pacf(sales_diff, ax=axes[0][1], lags=20, method="ywm") # 原始序列的 ACF/PACF,对比差异 plot_acf(monthly["sales"].dropna(), ax=axes[1][0], lags=20) plot_pacf(monthly["sales"].dropna(), ax=axes[1][1], lags=20, method="ywm") plt.show()method="ywm"是 Yule-Walker 的修正算法,对样本量不大的月度序列更稳定。看图时关注 PACF 的正负尖刺,比如 lag 1 显著为正、lag 2 显著为负,这通常提示 p 取 2。ACF 如果衰减缓慢,说明差分可能不够,需要回到上一步检查 d。为保险起见,我会把候选参数组合跑一遍 AIC:
import itertools from statsmodels.tsa.arima.model import ARIMA best_aic = float("inf") best_order = None for p in range(0, 4): for q in range(0, 4): try: model = ARIMA(monthly["sales"], order=(p, 1, q)) fit = model.fit() if fit.aic < best_aic: best_aic = fit.aic best_order = (p, 1, q) except Exception: continue print("AIC 最优 order:", best_order, "AIC:", best_aic)这段循环把 p 和 q 从 0 到 3 都试一遍,用 AIC 做准则。AIC 越小越好,但不能拿着这个结果直接当最终模型——后续还要看残差是不是白噪声。
4.3 残差白噪声检验和向前6个月的预测代码
选定 order 后拟合最终模型,并用 Ljung-Box 检验残差是否还有自相关。如果残差还有明显的模式,说明模型没把序列信息学干净,需要重新定阶。
from statsmodels.stats.diagnostic import acorr_ljungbox final_order = best_order final_model = ARIMA(monthly["sales"], order=final_order) final_fit = final_model.fit() # Ljung-Box 检验残差的自相关性 ljung_result = acorr_ljungbox(final_fit.resid, lags=[6, 12], return_df=True) print(ljung_result) # 向前预测6个月,输出均值和80%置信区间 forecast = final_fit.get_forecast(steps=6) forecast_mean = forecast.predicted_mean forecast_ci = forecast.conf_int(alpha=0.2) print("预测均值:\n", forecast_mean) print("80%置信区间:\n", forecast_ci)Ljung-Box 的 p 值要大于 0.05,表示残差没有显著自相关,模型合格。置信区间的 alpha 参数这里取了 0.2,对应 80% 置信区间。为什么不用 95%?因为销量预测本身误差大,95% 区间宽到业务方没法做库存决策,80% 区间在「给出范围」和「范围窄到有用」之间更平衡。实际业务里,我会把预测均值给到排产部门,把区间上沿给到备货部门,两个动作需要不同的边界。
5. ARIMA销量预测避坑清单:5个高频翻车点
这套流程走完不难,真正让项目翻车的是细节。以下五条是做了多次销量预测后攒下来的血泪经验,每条都按「现象 → 原因 → 解决」写,你在自己数据上大概率会遇到其中至少两条。
5.1 差分过度:预测曲线变成一条直的趋势线
现象:模型拟合效果很好,但一个季度之后预测销量几乎变成一条直线,完全没有波动,业务方看到直接质疑模型是摆设。原因:一阶差分后序列已经平稳,你为了保险又做了二阶差分,过度差分把序列里真实的短期波动也抹掉了,模型只剩下长期均值回归,预测自然趋平。解决:差分次数最多到一阶,除非 ADF 检验明确提示一阶差分后仍不平稳。同时看差分后序列的 ACF 图,如果 lag 1 附近出现明显负尖峰,就要怀疑是不是差分多了,这时候可以改用原序列直接建模,让模型自己处理趋势。
5.2 行业补贴周期调整:模型偏离拐点完全失灵
现象:某一年某个季度销量突然大幅低于模型上季度给出的预测区间下沿,检查代码没有 bug,残差却在那个时间点出现连续低值。原因:ARIMA 是单变量模型,不知道外部政策变化。补贴标准调整、免税政策到期这类事件会造成序列的水平位移,模型还按之前的惯性外推,预测自然偏高。解决:人工识别水平位移点后两种情况处理。数据量够的话,直接用位移点之后的数据重新训练;数据量不够的话,先按当前数据建模,预测输出时手动附加一个位移量修正,同时标注这个修正的依据。不要指望 ARIMA 自己学会政策拐点,它不是被设计来做这件事的。
5.3 用全量数据调参,上线后验证误差翻倍
现象:训练时模型对历史数据的拟合度非常高,AIC 也很低,但发布后第一个月的预测和实际值差了 20% 以上。原因:用整段历史数据调参,再用同一段数据计算误差,这叫测试集泄漏。ARIMA 调参过程本身会利用序列的全部信息,模型记住了数据里偶然的波动,而不是真实规律。解决:把序列时间切分,比如前 80% 做训练,后 20% 做验证,调参时只用训练段。更严格的做法是滚动回测,下一章会给出完整代码。记住一个原则:预测是拿「过去的过去」来预测「过去」,不是拿「全部过去」来预测未来。
5.4 残差白噪声检验不通过还交差
现象:Ljung-Box 的 p 值小于 0.05,但你看残差图觉得波动不大,于是直接输出预测结果。原因:残差里还有周期性或者自相关结构,最常见是月度季节性没被 ARIMA 捕获。比如每年 12 月销量冲高,模型没有专门的季节性分量,12 月的误差就会系统性为正。解决:先检查残差序列是不是周期性的,如果是,升级为 SARIMA 模型或者在建模前对序列做季节性差分。标题里写的是 ARIMA,但在实际生产中遇到强季节性,我不会硬扛,会用 SARIMA 替换。不要为了贴合标题牺牲预测质量。
5.5 置信区间过窄,业务方误以为模型很准
现象:80% 置信区间上下界只有几千台,业务方看到后把预测当成精确值去排产,结果实际值落在区间外面,导致备货不足。原因:ARIMA 的置信区间只反映估计误差,不包含外部冲击和数据结构变化。新能源销量受到政策、新车发布、价格竞争等多方面影响,这些都不会进入模型的区间计算。解决:把置信区间看作「模型内部不确定性」,而不是「业务全场景不确定性」。在输出预测前,用历史残差的标准差对区间做一定比例的放大,比如放大 1.5 倍;同时在汇报材料里写明这个区间不包含突发事件影响。这样做会让预测看起来没那么「准」,但能挡住一次排产事故。
6. 让预测可信的验证手段:滚动回测和对数变换
最后一个环节不是写代码就完事,而是要让业务方愿意用你的数。我常用的两个技巧:滚动回测验证稳定性,和对数变换修正增长率偏差。
滚动回测比一次性切分训练集、测试集更能反映真实表现,因为它模拟了「每个月用过去数据预测未来」的过程。
import numpy as np from statsmodels.tsa.arima.model import ARIMA def walk_forward_eval(series, train_size, horizon=6): """滚动预测评估:每回合只用训练窗口内的数据,向前预测horizon步。""" preds = [] actuals = [] start = train_size history = series[:start] for i in range(start, len(series) - horizon + 1): model = ARIMA(history, order=(2, 1, 1)) fit = model.fit() fc = fit.get_forecast(steps=horizon).predicted_mean preds.extend(fc) actuals.extend(series[i:i + horizon]) history = series[:i + 1] return np.array(preds), np.array(actuals) # 用前60个月做初始训练窗口,滚动到序列末尾 preds, actuals = walk_forward_eval(monthly["sales"], train_size=60) mape = np.mean(np.abs((actuals - preds) / actuals)) * 100 print("滚动6步预测 MAPE: %.1f%%" % mape)这个函数的核心是每次只使用截至当月的序列重新拟合模型,没有使用未来信息。train_size=60是因为月度序列至少需要五六十个点才能稳定估计 ARIMA 参数,太短会导致参数漂移。如果你预测目标从 6 个月改成 12 个月,horizon相应调整,但误差大概率会上升,因为多步预测的误差会累积,这是正常的,提前和业务方讲清楚。
第二个技巧是对数变换。新能源汽车早期的月度销量基数小,比如一两万台,后来涨到几十万台,线性 ARIMA 预测时对早期低基数区的增长率估计会偏大,对近期高基数区的增长估计又偏小。取对数后再建模,模型学的是「增长率」而不是「绝对量」,预测结果还原后更贴合实际。
# 对数变换建模 monthly["log_sales"] = np.log(monthly["sales"]) model_log = ARIMA(monthly["log_sales"], order=(2, 1, 1)) fit_log = model_log.fit() # 预测结果还原为销量 log_forecast = fit_log.get_forecast(steps=6) sales_forecast = np.exp(log_forecast.predicted_mean) print(sales_forecast)注意还原后的预测是几何均值方向,通常会低于算数均值,但这在销量预测里是合理的——它表达的是趋势的中间水平,而不是被季末冲量抬高后的平均。我现在的习惯是:每次报数前先跑一遍滚动回测,看六个季度的 MAPE,再用对数变换模型对比线性模型输出,取误差更小的那个提交。这两个动作花不了几分钟,但挡住了至少三次排产事故。这套流程从数据清洗到滚动验证并不复杂,难的是每个环节都保持警惕,不做超出模型能力范围的假设。希望帮到你。
本文还有配套的精品资源,点击获取