多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

ARIMA预测新能源汽车销量:从数据准备到滚动验证的完整指南

ARIMA预测新能源汽车销量:从数据准备到滚动验证的完整指南 简介基于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在销量预测上的边界这三个工具在时间序列预测里经常被拿来对比但它们的适用场景差异很大。用一张对比表来收拢边界工具数据量要求季节性处理可解释性落地成本ARIMA50~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(methodlinear) # 识别季末月份后续分析用 monthly[is_qeom] (monthly.index.month % 3 0).astype(int) print(monthly.tail(12))这段代码有两个关键点。asfreq(MS)会把索引规整到每个自然月的第一天如果某个月没有数据对应位置就是 NaN。interpolate(methodlinear)是按时间顺序做线性插值适合销量这种有明显趋势的序列不建议用均值填充。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 可能取 kACF 在 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, axaxes[0][0], lags20) plot_pacf(sales_diff, axaxes[0][1], lags20, methodywm) # 原始序列的 ACF/PACF对比差异 plot_acf(monthly[sales].dropna(), axaxes[1][0], lags20) plot_pacf(monthly[sales].dropna(), axaxes[1][1], lags20, methodywm) plt.show()methodywm是 Yule-Walker 的修正算法对样本量不大的月度序列更稳定。看图时关注 PACF 的正负尖刺比如 lag 1 显著为正、lag 2 显著为负这通常提示 p 取 2。ACF 如果衰减缓慢说明差分可能不够需要回到上一步检查 d。为保险起见我会把候选参数组合跑一遍 AICimport 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], orderfinal_order) final_fit final_model.fit() # Ljung-Box 检验残差的自相关性 ljung_result acorr_ljungbox(final_fit.resid, lags[6, 12], return_dfTrue) print(ljung_result) # 向前预测6个月输出均值和80%置信区间 forecast final_fit.get_forecast(steps6) forecast_mean forecast.predicted_mean forecast_ci forecast.conf_int(alpha0.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, horizon6): 滚动预测评估每回合只用训练窗口内的数据向前预测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(stepshorizon).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_size60) mape np.mean(np.abs((actuals - preds) / actuals)) * 100 print(滚动6步预测 MAPE: %.1f%% % mape)这个函数的核心是每次只使用截至当月的序列重新拟合模型没有使用未来信息。train_size60是因为月度序列至少需要五六十个点才能稳定估计 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(steps6) sales_forecast np.exp(log_forecast.predicted_mean) print(sales_forecast)注意还原后的预测是几何均值方向通常会低于算数均值但这在销量预测里是合理的——它表达的是趋势的中间水平而不是被季末冲量抬高后的平均。我现在的习惯是每次报数前先跑一遍滚动回测看六个季度的 MAPE再用对数变换模型对比线性模型输出取误差更小的那个提交。这两个动作花不了几分钟但挡住了至少三次排产事故。这套流程从数据清洗到滚动验证并不复杂难的是每个环节都保持警惕不做超出模型能力范围的假设。希望帮到你。本文还有配套的精品资源点击获取
返回列表