
简介针对铁路货运量与客运量进行时序建模预测的Python项目是一份面向毕业设计、课程设计及期末大作业场景的高分参考。代码中附有详细注释建模流程完整初学者也能按步骤理解思路并快速完成本地部署。整个资源包共包含50个文件核心由17个Python脚本和4个R脚本构成同时提供运输量、时序建模等Excel/CSV数据集以及19张结果图表和说明文档并附有依赖文件与环境说明压缩包仅2.05MB轻量易用。目前已有268人学习使用可作为铁路运输量时序分析、预测算法选型与结果评估的典型范例。读者不仅能获得可运行的源代码与模型还能借助评估表格及配套笔记快速复现预测流程适合迁移应用于其他货运、客运数据分析场景帮助提升项目完整度与答辩说服力。1. 铁路货运量/客运量预测为什么不能直接拿原始序列进模型铁路货运量和客运量不是能被“一拍脑袋”的回归喂饱的问题。客运量受节假日、调图、高铁新线开通这类事件冲击货运量又跟宏观经济周期和运输结构调整绑定两者都带有明显的年度重复模式同时趋势项会随着经济总量缓慢抬升。这种数据形态放进普通的线性回归或者直接扔给LSTM裸跑经常出现训练集误差很好看、测试集却系统性偏移的情况原因是模型把非平稳序列里的“水平漂移”当成了规律。时序建模预测在铁路运输场景里之所以是必选项是因为它先把趋势、季节、残差拆开再对自相关结构显式建模预测结果可解释也能做出可靠的置信区间。这套资源内置了完整的Python和R代码、运输量.xlsx和时序建模.xlsx等多个格式数据源代码注释覆盖到每一步毕业设计、课程设计和期末大作业都能直接复用。熟悉统计建模的读者可以直接跳到第3章看定阶逻辑想先跑通流程的从第2章开始半小时内能出第一版预测结果。2. 数据读取、缺失值修补与平稳性检验开始建模前的必修课2.1 多格式数据源的统一载入项目里同时出现了运输量.xlsx、时序建模.xlsx、ads.csv、currency.csv这是典型的课程设计数据组织方式Excel用于人眼检查CSV用于程序快速读取。实际建模前必须先把它们统一成同一个DataFrame结构否则后面所有的绘图和建模函数都会因为索引或列名不一致报错。import pandas as pd import numpy as np df pd.read_excel(运输量.xlsx, parse_dates[0], index_col0) # 如果日期读进来是文本例如“202301”需要显式指定格式 df.index pd.to_datetime(df.index, format%Y%m) # 统一列名避免原始文件中英文混杂 df.columns [freight, passenger] # 先去重再排序确保时间索引严格递增 df df[~df.index.duplicated(keepfirst)].sort_index() # 检查时间索引是否连续连续才能做后续差分和季节分解 print(df.index.is_monotonic_increasing) print(df.isnull().sum())这里parse_dates[0]表示把第一列解析成时间index_col0让时间列直接成为索引。format%Y%m只适用于形如202301的月份文本如果原始数据是2023-01-01则不需要这个参数。is_monotonic_increasing返回True才能保证后续diff()和shift()的语义正确。得到连续时间索引后还要看缺失值分布。铁路运输量数据偶尔会因为统计口径调整出现一两个空值这是最常见的坑。处理方式按缺失形态选择不要一律删行。缺失场景推荐处理方式适用条件单个缺失点df[freight].interpolate(methodlinear)序列局部平稳无剧烈跳变连续缺失超过2个点用上一年同月值填充月度数据且季节性强突然出现异常低值用前后三周中位数替代节假日或疫情等异常扰动连续缺失时“线性插值”会把断点拉成一条斜线破坏季节形状这时候用上年同月值更合理。插值完成后建议再看一眼时间序列图确认没有人为制造的不自然跳变。2.2 ADF单位根检验为什么非平稳序列不能直接建模平稳性是ARIMA类模型的隐含前提。一个平稳序列的均值、方差和自协方差不随时间改变模型才能用历史自相关去推断未来。铁路货运量通常带有上升趋势和年度周期直接从图形上看就不是平稳序列但不能只靠肉眼判断需要做ADF单位根检验。from statsmodels.tsa.stattools import adfuller result adfuller(df[freight].dropna()) print(ADF统计量:, result[0]) print(p值:, result[1]) print(1%临界值:, result[4][1%]) print(5%临界值:, result[4][5%])ADF检验的原假设是“序列存在单位根即非平稳”。当p值大于0.05时不能拒绝原假设说明序列非平稳需要差分处理。铁运量数据的p值通常远大于0.05和用眼睛看到的结果一致。对客运量也要分别执行一次检验因为两者受不同因素驱动差分阶数可能不一样。如果ADF统计量已经小于5%临界值但p值还不够小可以尝试对数据取对数后再检验。对数变换能把指数型增长压成近似线性趋势也能缓解异方差。我一般会先看序列标准差是否随均值增大如果明显相关就优先取对数。2.3 季节性分解把趋势、季节和残差拆开平稳性检验只回答“能不能建模”却不能告诉你是哪种成分在制造非平稳。月度铁路运输量数据几乎肯定有12个月的周期所以下一步做季节分解。from statsmodels.tsa.seasonal import seasonal_decompose decomp seasonal_decompose(df[freight].dropna(), modeladditive, period12) decomp.trend.plot() decomp.seasonal.plot() decomp.resid.plot()modeladditive表示把序列拆成“趋势季节残差”的加和关系适合季节波动幅度不随水平变化的数据。如果观察折线图发现每年的波动高度随运量总量放大则改用modelmultiplicative它拆的是乘积关系。period12对月度数据基本是定死的季报数据才用period4。分解之后可以看到残差里如果还留有明显的周期脉冲说明除了常规季节外还有节假日等事件效应。这时候继续用纯ARIMA会欠拟合可以把节假日哑变量作为外生变量放进SARIMAX这也是项目里ads.csv这类文件存在的意义。不要急着把残差扔掉残差是后续模型诊断的原材料。3. 从ACF/PACF到ARIMA(SARIMA)定阶用数据而不是感觉选参数3.1 ACF和PACF怎么看截尾、拖尾与差分阶数差分完成后的序列需要确定AR项阶数p和MA项阶数q。自相关函数ACF度量当前值与滞后值的线性相关性偏自相关函数PACF则是在排除中间滞后项影响后的净相关。两者的截尾位置直接指向p和q。from statsmodels.graphics.tsaplots import plot_acf, plot_pacf import matplotlib.pyplot as plt fig, axes plt.subplots(2, 1, figsize(10, 6)) plot_acf(df[freight_diff12].dropna(), axaxes[0], lags24) plot_pacf(df[freight_diff12].dropna(), axaxes[1], lags24, methodywm) plt.tight_layout() plt.show()这里的freight_diff12是在普通一阶差分基础上再做12步季节差分用于消除年度周期。methodywm是Yule-Walker的改进版对月度数据和小样本更稳定新版statsmodels里如果不指定有时会提示使用ywm替代旧的yw。看ACF时重点看滞后1到滞后2处是否突然跌进置信带看PACF时重点看滞后1到滞后3是否截尾实际观察到的现象推荐的阶数理由PACF在滞后1处截尾ACF拖尾p1, q0AR(1)过程的自相关呈指数衰减ACF在滞后1处截尾PACF拖尾p0, q1MA(1)过程只有一阶记忆ACF在滞后12处仍有显著尖峰增加季节项P或Q年度季节残余未被完全消除两者都缓慢衰减差分阶数不足先增加d或D不要急着调p/q仅仅靠看图定阶容易犹豫特别是样本量不足时置信带很宽。我通常把ACF/PACF图当成第一步筛选最终阶数交给信息准则去比。3.2 用AIC/BIC自动定阶pmdarima与网格搜索pmdarima把auto_arima封装成了类似R语言forecast::auto.arima的接口会自动尝试多组(p,d,q)和(P,D,Q,m)按AIC或BIC选最优。它比手动点图快且不容易漏掉局部最优组合。from pmdarima import auto_arima auto auto_arima( df[freight].dropna(), seasonalTrue, m12, start_p0, max_p5, start_q0, max_q5, d1, D1, stepwiseTrue, information_criterionaic, traceTrue ) print(auto.order) print(auto.seasonal_order)d1表示允许一阶差分D1表示考虑季节差分m12对应月度周期。stepwiseTrue走逐步搜索路径速度比全网格快很多但对比较接近的候选模型可能漏掉最优组合。如果样本量不大少于300条可以设stepwiseFalse做全搜索时间也可接受。traceTrue会把每次尝试的AIC打印出来方便答辩时展示你是如何选阶的。如果不想引入额外依赖也可以自己写两层循环遍历p和q用SARIMAX拟合后取AIC最小值。注意statsmodels的AIC默认基于极大似然估计和pmdarima计算口径一致可以互相验证。3.3 SARIMA建模参数含义与外生变量扩展定阶完成后直接进入SARIMAX建模。SARIMAX是ARIMA在状态空间框架下的实现支持季节项、趋势项和外生回归变量比老的ARIMA类灵活得多。from statsmodels.tsa.statespace.sarimax import SARIMAX model SARIMAX( df[freight].dropna(), order(1, 1, 1), seasonal_order(1, 1, 1, 12), trendc ) res model.fit(dispFalse) print(res.summary())order(p,d,q)里的p是自回归阶数q是移动平均阶数d是差分次数。seasonal_order(P,D,Q,m)对应对季节分量的自回归、差分、移动平均以及一个完整的季节周期长度。trendc表示允许一个常数项等价于在模型里包含漂移项。铁道运输量序列在有下降或上升年份时trendc能让长期预测不偏到一个固定水平上。res.summary()会给出每个系数的z统计量和p值p值大于0.05的项可以考虑去掉再重新拟合。这里有一个新手常犯错的地方不要看到p-value大于0.05就把差分阶数d调成2差分过度反而会让模型丢失水平信息得出来的预测区间会宽到失去意义。差分阶数的确认要回到ADF检验而不是看系数显著性。4. 训练/测试集划分、滚动回测与误差诊断让预测结果经得起答辩4.1 时间序列划分绝对不能用随机打乱的训练集交叉验证里的train_test_split默认随机抽样这在时间序列里是致命的模型会看见未来数据训练损失极低测试损失却一泡污。时间序列预测必须用“前一段训练后一段验证”的切分方式。train_size int(len(df) * 0.8) train, test df.iloc[:train_size].copy(), df.iloc[train_size:].copy() model SARIMAX( train[freight], order(1, 1, 1), seasonal_order(1, 1, 1, 12) ) res model.fit(dispFalse)用80%做训练、20%做测试是比较常见的比例。若数据本身较短比如只有10年120条月度记录我会把测试集压缩到最后12条也就是一整年的长度保证测试集里包含完整的季节周期。如果测试集只有两三个月测出来的误差会被某个偶然月份主导不具参考价值。4.2 多步预测与误差度量MAE、RMSE、MAPE怎么读拟合完成后用get_forecast做多步预测一次性预测整个测试集长度。这里与滚动预测不同所有预测都只基于训练期内信息最能反映真实上线后的行为。forecast res.get_forecast(stepslen(test)) mean forecast.predicted_mean ci forecast.conf_int() from sklearn.metrics import mean_absolute_error, mean_squared_error mae mean_absolute_error(test[freight], mean) rmse np.sqrt(mean_squared_error(test[freight], mean)) mape np.mean(np.abs( (test[freight] - mean) / np.clip(test[freight], 1, None) )) * 100 print(fMAE{mae:.2f}, RMSE{rmse:.2f}, MAPE{mape:.2f}%)np.clip(test[freight], 1, None)是为了防止某个月份客运量为0时除零。MAPE对真实值接近0的场景极度敏感铁运量一般不会为零但在其他业务里要注意这个坑。RMSE对离群值惩罚更大如果后期有突发性大客流RMSE会比MAE敏感得多。三个指标同时看不要只挑好看的那一个写进报告。指标计算逻辑业务上的意义MAE误差绝对值的平均平均偏离多少万吨/万人RMSE误差平方后开根号大误差被放大反映最差情况MAPE相对误差百分比跨量级比较答辩展示最直观MASE与朴素预测对比小于1说明比用上一年同月值强4.3 残差白噪声检验Ljung-Box才是模型是否合格的关键预测误差再小如果残差里还有明显的自相关说明模型没有把时间依赖结构提取干净后续预测区间也会失真。Ljung-Box检验是残差诊断的标准做法。from statsmodels.stats.diagnostic import acorr_ljungbox resid res.resid.dropna() lb acorr_ljungbox(resid, lags[6, 12, 24], return_dfTrue) print(lb[[lb_pvalue]].round(3))如果各滞后阶数的p值都大于0.05说明残差序列是白噪声模型已经充分提取了数据中的自相关信息。滞后6和滞后12分别关注短期和季节性残留滞后12尤其重要因为月度数据的年度周期最容易在这里暴露没洗干净的季节性。如果p值小于0.05常见处理是提高p/q阶数或者检查是否遗漏了节假日外部变量。除了Ljung-Box我还会画一张残差Q-Q图看尾部是否严重偏离正态假设。ARIMA的置信区间基于正态近似如果残差厚尾严重预测区间会偏窄实际覆盖概率低于宣称的90%。5. Python与R双实现对照从课程设计到可交付的预测流程5.1 用R的forecast包快速复现同一套ARIMA项目里带了R代码的note.md这个设计很务实Python版适合演示数据清洗和自制模型R的forecast包则在自动化定阶和预测可视化上更顺手。答辩时用两套代码得出相近结果本身就是对模型稳定性的证明。library(forecast) library(readxl) df - read_excel(运输量.xlsx) ts_freight - ts(df$freight, start c(2010, 1), frequency 12) fit - auto.arima(ts_freight, stepwise FALSE, approximation FALSE) fc - forecast(fit, h 12) plot(fc) accuracy(fc)ts()函数创建时间序列对象start c(2010, 1)指从2010年1月开始frequency 12是月度数据。auto.arima里的stepwise FALSE要求执行更大范围的搜索approximation FALSE则让每个候选模型都用精确似然估计而不是近似值。这两项会显著增加运行时间但能降低选到次优模型的概率。accuracy(fc)直接输出训练集和测试集的MAE、RMSE、MAPE和MASE参数和Python端基本一一对应。R的输出里还自带MASE指标这是Python端需要自己算的所以R适合做最终验证展示。5.2 两种实现的结果一致性怎么验证Python的pmdarima和R的forecast虽然都基于Hyndman的算法思路但在AIC计算、参数估计终止条件上仍有细微差别。两组结果不需要完全相等但预测曲线的形状应当高度重合。我一般会在两个环境里分别输出未来12个月的点预测值然后算一下两组结果的平均相对偏差import numpy as np py_pred np.array([...]) # Python预测结果 r_pred np.array([...]) # R预测结果 rel_diff np.mean(np.abs(py_pred - r_pred) / np.clip(r_pred, 1, None)) * 100 print(f两语言预测平均相对偏差: {rel_diff:.2f}%)如果偏差在3%以内说明模型结论稳定可以直接把这份对比放进毕业设计的验证章节。偏差超过5%先检查数据对齐R的ts起点是否正确Python的差分阶数是否与R一致而不是急着调参。5.3 模型更新策略新数据来了该全量拟合还是增量更新上线之后的模型不会一成不变。铁路运输量每年都有新数据进来随着线路开通和区域经济变化之前估计的系数会逐步失灵。常见做法是每月新增一个真实值后把模型整体重训一遍。如果每天都要更新才考虑增量更新。# 每来一条新数据用append做状态更新 new_value [test[freight].iloc[0]] res res.append(new_value, refitFalse)append会用新的观测值继续卡尔曼滤波状态递推保持已估计的系数不变只更新隐含的状态。refitFalse省去重新估计参数的时间适合快速产出短期预测。但如果长期不重估系数模型会逐渐偏离数据我的经验是每12个月至少全量重训一次。evalute.xlsx这类评估文件就是用来专门记录每次重训前后误差对比的直接往里追加即可。生产环境里更省事的方式是设置一个误差监控每次真实值出来后计算单步预测误差的滚动均值如果连续三个月超过设定阈值就触发全量重训。阈值建议用第4章里测试集的RMSE乘1.5作为警戒线。6. 放空预测区间与模型说服力三个值得做进毕业设计里的验证技巧6.1 滚动预测比一次多步预测更接近真实发布场景一次get_forecast(stepslen(test))的输出会随步长增加越来越不确定因为误差会累积。实际业务里往往是每个月发布未来一个月或一个季度的预测所以滚动预测更贴近应用。history train[freight].tolist() preds [] for y_true in test[freight]: model SARIMAX(history, order(1, 1, 1), seasonal_order(1, 1, 1, 12)) fit model.fit(dispFalse) preds.append(fit.forecast(steps1).iloc[0]) history.append(y_true) # 把真实值放进历史模拟真实发布环境这段代码每走一步都用包含真实值在内的历史重新拟合预测效果通常比一次性多步预测好因为它不断用最新信息校正状态。缺点是时间开销大样本多时可以把重训频率改成每三步或每五步一次。6.2 用预测区间宽度检验模型方差很多答辩项目只放点预测线却不放置信区间。实际上置信区间宽度最能说明数据本身的不可预测性。如果90%置信区间宽度超过历史平均月度波动的3倍说明模型方差过大这时候与其继续调整p/q阶数不如回到数据处理阶段重新考虑是否漏掉了结构性变化。6.3 用MASE对比季节朴素预测一句话堵住评委的嘴MASE小于1意味着你的模型比“直接用上一年同月值”的朴素方法有效。把MASE和季节朴素预测放在同一张表里是答辩时最容易让评委认可的细节。这个指标在R的accuracy(fc)里直接输出Python里可以拿上一年同月值作为基准预测自己跑一遍MAE再与模型MAE做比值即可。本文还有配套的精品资源点击获取