资讯动态

地铁客流预测实战:基于Jupyter Notebook的7天小时级预测

发布时间:2026/9/16 23:03:40 来源:尧图企业网站定制
接了地铁客流预测的活儿要把未来7天逐小时客流量给估出来。数据是刷卡记录工具是Jupyter Notebook模型方案后面会详细聊。这个项目很典型——城市轨道交通的排班、调度、车站人员安排、运力投放全都要靠客流预测支撑。我不止一次在地铁行业的朋友那儿听到调度最怕的不是普通早晚高峰而是节假日前后那种“说不准”的客流。这篇文章我把整个项目的思路、数据预处理、建模、评估和踩坑记录都摊开来说适合正在做客流预测、订单预测、时间序列项目或者刚想用Jupyter Notebook练手数据分析的同学参考。1. 拆解需求这个“7天小时客流量”到底有多难预测1.1 先把目标拆开看168个时间点不是一句话的事“预测地铁后7天小时客流量”听起来就一句话做起来是个挺具体的任务。拆开来看7天乘以24小时一共168个预测点。每个点都代表某个小时内通过闸机的乘客总数或者某个站点的进出站人数预测粒度不同难度和用途完全不同。我在项目里通常先问清楚要的是全网总客流还是分站客流全网总客流适合做宏观运力规划比如决定整条线路开多少列车、间隔多久发一班分站客流适合做站内疏导、人员排班和应急管理。如果目标站点很多建议先做全网级模型跑通流程再扩展到站点级。站点多的时候模型数量、训练时间、特征复杂度都会成倍增加一开始就把摊子铺太大容易把自己绕晕。时间粒度也是一个关键选择。15分钟粒度、小时粒度、日粒度预测难度完全不是一个量级。日粒度基本靠星期几和节假日就能预测得不错因为它的周期性非常稳定。小时粒度就麻烦多了它不仅有日周期和星期周期还有早晚高峰的陡峭突起、平峰的平缓过渡、夜间的低量段。我这次做的是小时粒度用的是全网加总客流所以只需要把数据按小时聚合工作量可控同时又能看出明显的“双驼峰”形态。1.2 为什么小时级预测比日级预测更容易翻车日级预测翻车多半是节假日或者突发事件导致。小时级预测除了这些还要面对一天内多个时段的不同规律。早高峰7点到9点客流量会在半个小时内突然冲到全天最高点晚高峰17点到19点再来一次而中午11点到13点、下午14点到16点这些平峰时段曲线又相对平缓。不同时段的波动特性不一样这意味着单一模型如果只学了平均值很容易把高峰“削平”。另一个坑是“星期效应”。周一早高峰和周三早高峰不一样周五晚高峰和周二晚高峰也不一样周五晚上和周六晚上的客流形态可能有明显差异。如果把星期特征做糙了模型就分不清“周五晚上”和“周一早上”的区别预测曲线会变得平庸。节假日对小时级预测影响更大。比如清明、五一、国庆这类假期一天的客流形态几乎完全是另一套模式早高峰消失白天出现漫游型客流晚间回落得晚。如果训练数据里节假日样本本身不多模型很难自动学会这套规律所以节假日通常需要单独标记甚至单独建模。这也是我在做特征工程时坚持加入节假日标记的原因。1.3 技术选型Jupyter跑数据分析模型怎么定路线这类的项目Jupyter Notebook是非常顺手的环境。它的交互式特性允许你一段一段地跑代码看完数据结果再决定下一步非常适合探索性数据分析。先画个图看看客流形态再验证一下周期假设再做特征工程整个流程都可以在同一个Notebook里完成每一步都有中间结果可以回看。相比传统IDEJupyter对数据分析这类“边看边做”的任务要友好得多。模型路线上我采用的是从简到繁的递进式策略先做统计基线再用机器学习模型最后按需尝试深度学习。统计基线用来确认预测难度的底线比如“上周同一天同一时刻的客流”作为预测值误差是多少。如果基线已经达到业务接受范围那就没必要上复杂模型。仿照这个思路机器学习的方案我选了LightGBM它对表格型时间特征处理得干净利落训练速度快调参空间也不大。深度学习的LSTM、Transformer这类方案我在数据量大、需要捕捉复杂非线性模式的时候才会考虑因为训练成本和对数据量的要求都更高一开始就上深度模型非常容易浪费时间。2. 数据准备与特征工程预测准不准根子在这2.1 原始数据长什么样地铁客流数据通常来自自动售检票系统AFC每条刷卡记录至少包含卡号、交易时间、进站站点、出站站点、交易类型等字段。做网络级客流预测时不需要关注具体线路和站点只需要把每笔交易按小时聚合。我拿到的数据是类似这样的结构import pandas as pd # 模拟查看原始数据 df pd.read_csv(metro_afc.csv, parse_dates[trans_time]) df.head()原始字段大致有字段说明用途card_id卡号去重统计trans_time交易时间按小时聚合station_id站点编号区分站点in_out_flag进站/出站标志区分进出站把进站和出站数据合并成小时级客流核心代码很简单# 按小时聚合进站、出站客流 df[trans_hour] df[trans_time].dt.floor(H) hourly df.groupby(trans_hour).agg( inflow(card_id, count) ) # 如果字段里带了in_out_flag可以分进出站统计这种聚合方式要注意一个问题以交易时间为准还是以闸机动作时间为准不同线路的AFC系统记录时间点可能不同有的记录的是刷闸瞬间有的记录的是交易落库时间两者可能有几十秒的偏差。小时粒度下这点偏差影响不大但如果做15分钟甚至5分钟粒度就可能导致客流曲线整体偏移。所以在做小时粒度时时间戳归一化到整点即可不要保留秒级精度避免无谓的噪声。2.2 数据清洗哪些脏数据会直接毁掉预测数据清洗在时序项目里经常不够重视但客流量预测里一个脏数据点就可能让模型学歪。最常见的几个问题。第一是缺失值。地铁运营不是24小时都有数据夜间停运时段有的数据源会记0有的直接缺行。0和缺行的含义不同缺行需要补全否则时间轴对不齐。我用的是重采样加前向填充hourly hourly.resample(H).asfreq() # 夜间无客流时段补0个别缺失用相邻均值填充 hourly[inflow] hourly[inflow].fillna(0)第二是异常值。比如某小时客流突然变成0大概率是数据上报故障。比如某小时客流冲高到平时的5倍要核实是不是车站搞活动或临时封站引起。异常值识别用简单粗暴的规则就够了比如“和上周同一天同一时刻相比偏差超过3倍”就标记出来逐个检查后再决定删掉还是保留。保留异常值会让模型去拟合噪声删掉异常值又可能丢掉真实信息这个平衡需要结合业务判断。第三是节假日和调休。中国的节假日安排有调休制度周末可能变成工作日工作日可能变成休息日。我的做法是维护一份节假日列表把“放假”“调休上班”“普通周末”“普通工作日”分别标记。如果只靠星期几这个特征模型会完全无法理解调休导致节假日预测崩盘。# 示例节假日数据手动维护 holidays pd.to_datetime([2024-05-01, 2024-05-02, 2024-05-03]) df[is_holiday] df.index.isin(holidays).astype(int)2.3 特征工程这三组特征一定要做特征工程是整个项目中最影响预测效果的部分。我在这个项目里主要做了三组特征。第一组是时间周期特征。小时、星期几、是否周末、是否节假日、一年中的第几周这些让模型能够区分不同时间点的周期性规律。这里有一个细节小时和星期几不要直接当数值特征输入否则模型会以为“小时23”和“小时0”之间的差距很大但实际上它们是连续的周期变量。更好的做法是用正弦/余弦编码或者在树模型里直接当类别特征处理。LightGBM可以原生处理类别特征所以我把hour、weekday都设置成category类型。第二组是滞后特征和滑动统计量。这是时序预测最关键的一类特征。滞后24小时的特征告诉模型“昨天同一时刻的客流量是多少”滞后168小时的特征告诉模型“上周同一时刻的客流量是多少”。因为小时级客流有极强的日周期和星期周期这两个滞后特征几乎决定了预测效果的基线水平。滑动平均和滑动标准差也很有用它们能刻画最近几小时客流的整体水平和波动程度比如“过去6小时平均客流”可以帮助模型判断当前是高峰还是平峰是稳步上升还是已经见顶。df[lag_24] df[inflow].shift(24) df[lag_168] df[inflow].shift(168) df[roll_mean_6h] df[inflow].rolling(6).mean() df[roll_std_6h] df[inflow].rolling(6).std()第三组是外部特征。天气数据在部分场景下有用但效果比较有限。我试过接入温度和降雨量发现对网络级总客流的影响不算大因为地下交通受天气影响小于地面交通。不过如果是某个地面站或者换乘大站的客流预测天气可能就有用了。这类特征别一上来就堆先做相关性分析或者看特征重要性效果好再留。2.4 数据集划分时间序列不能乱来训练集、验证集、测试集的划分是时序项目里最容易犯错的环节。普通机器学习任务可以随机打乱数据时序任务绝对不行因为时序数据一旦打乱就破坏了时间连续性。正确的做法是按时间顺序切分比如前12个月做训练接下来1个月做验证最后1个月做测试。更严格的做法是用滚动时间窗口比如训练集从1月到6月验证集用7月然后训练集变成2月到7月验证集用8月这样能够模拟模型在真实世界中的滚动更新方式。我用的是留出法加时间窗口train df.loc[2023-01-01:2023-11-30] val df.loc[2023-12-01:2023-12-31] test df.loc[2024-01-01:2024-01-31]另外要注意滞后特征在训练/验证/测试边界上的处理不要用未来信息去预测过去。构建特征时滞后特征都是基于历史窗口计算的所以不会泄漏但如果代码里不小心用到了当天的平均客流去预测当天的某个小时那就属于特征泄漏了验证效果会虚高上线就露馅。3. 从基线到模型预测7天客流量的实操流程3.1 先把baseline搭出来心里才有底很多新手一上来就撸LSTM结果调参调到怀疑人生。我的习惯是先搭一个简单的baseline。客流预测最常用的baseline是“上周同一天同一时刻的客流”也就是lag_168。这个baseline的预测效果通常不会太差因为它本身就捕捉了星期周期如果连这个简单baseline都比不过那复杂模型就是白搭。# 基线直接用上周同一时刻的值 baseline_pred df[inflow].shift(168)我跑完这个baseline之后整个测试集上的MAPE大概在10%左右。这个数字说明小时级客流预测的难度比较真实同时也给了后续模型一个明确的追赶目标。如果LightGBM的误差只比这个低一点点我就要考虑是不是特征工程没做到位而不是一味堆模型复杂度。3.2 用LightGBM做小时级预测LightGBM是目前做这类表格型时序预测性价比非常高的选择。它训练快、内存占用低、对特征缺失不敏感而且能自动处理类别特征和非线性关系。在这个项目里我用它预测“未来一小时全网总客流”本质是个回归任务特征就是上面构造的那些时间特征、滞后特征和滑动统计量。核心代码大致如下import lightgbm as lgb from sklearn.model_selection import TimeSeriesSplit features [hour, weekday, is_weekend, is_holiday, lag_24, lag_168, roll_mean_6h, roll_std_6h] X_train train[features] y_train train[inflow] X_val val[features] y_val val[inflow] model lgb.LGBMRegressor( n_estimators800, learning_rate0.05, num_leaves31, subsample0.8, colsample_bytree0.8, random_state42 ) model.fit( X_train, y_train, eval_set[(X_val, y_val)], callbacks[lgb.early_stopping(50)] )用LightGBM的时候有几个参数值得注意。n_estimators配early_stopping可以防过拟合不用自己纠结树的数量。learning_rate设0.05左右太小训练慢太大容易过拟合。num_leaves控制模型复杂度我一般从31开始调如果验证集误差还在下降就继续增大。还有一个容易被忽略的点把hour、weekday这些离散特征标记成category类型LightGBM会对它们做专门的分裂策略预测效果会更好。for col in [hour, weekday]: X_train[col] X_train[col].astype(category) X_val[col] X_val[col].astype(category)3.3 未来7天的预测策略一次性预测还是滚动预测模型训练好之后怎么预测未来7天的168个小时这个策略会影响最终效果。有两种常见做法。第一种是滚动预测。用已有的真实数据为起点预测下一个小时然后把预测值拼到历史序列末尾再预测再下一个小时循环168次。这种方式的优点是每一步都可以用到前面刚预测出来的值作为特征更接近真实业务场景但缺点是误差会累积越往后越不准。第二种是直接预测。只构造未来7天的时间特征和滞后特征一次性输出168个预测值。这种方式的滞后特征没法用真实的预测值填充只能用历史值填或者用滚动方式逐步填充。我在项目里用的是折中方案用滚动预测但只滚动滞后24小时的特征不要滚动太长的窗口。这样既模拟了真实部署时的行为又不会让误差无限累积。实际操作时我会写一个循环每次预测一个点然后把预测值放入历史数组更新滞后特征。初始的滞后值用的是真实历史数据随着预测步数增加滞后值逐渐变成模型自己的预测值这是必然的但通过调整特征窗口可以让误差累积控制在合理范围内。history list(train[inflow].tail(168)) preds [] for i in range(168): # 用当前history生成特征 current_time future_timestamps[i] feat make_features(current_time, history) pred model.predict([feat])[0] preds.append(pred) history.append(pred)这个循环里最关键的是make_features函数的一致性它必须和训练时用到的特征逻辑完全一样否则模型看到的特征分布和训练时不同预测效果会大幅下降。3.4 评估指标光看整体误差远远不够客流量预测的评估我一般同时看三个指标MAE、RMSE、MAPE。MAE是平均绝对误差直观、好解释比如“平均每个小时预测偏差1200人次”。RMSE对大幅误差更敏感可以用来检查模型有没有在高峰期犯大错。MAPE是百分比误差方便和业务方沟通但要注意零值问题夜间客流为0或者极小值会导致MAPE爆炸。我通常在计算MAPE时排除夜间停运时段只算运营时间。只看整体数字还不够我会额外切片评估工作日早高峰7-9点、晚高峰17-19点、平峰、周末、节假日分别算误差。原因很简单整体误差好看不代表高峰预测就准而高峰恰恰是业务上最重要的时段。我在实际工作中发现很多模型在整体误差上表现优异但一到早高峰就把峰值预测低了20%以上这在调度上完全不可接受。评估代码大概是这样from sklearn.metrics import mean_absolute_error, mean_squared_error mae mean_absolute_error(y_true, y_pred) rmse mean_squared_error(y_true, y_pred, squaredFalse) mask_peak (true_index.hour 7) (true_index.hour 9) mae_peak mean_absolute_error(y_true[mask_peak], y_pred[mask_peak])3.5 可视化预测曲线画出来比看数字直观得多数字评估是一方面把预测曲线和真实曲线画在同一张图上能快速发现一些数字看不出来的问题。比如峰值是否偏移、相位是否一致、低谷是否贴合。我用matplotlib把测试集最后一整周的预测结果画出来一眼就能看出模型预测的曲线是否保留了双驼峰形态。import matplotlib.pyplot as plt plt.figure(figsize(14, 6)) plt.plot(test_index, y_true, labelactual, linewidth1.5) plt.plot(test_index, y_pred, labelpredicted, linewidth1.5, linestyle--) plt.xlabel(time) plt.ylabel(hourly inflow) plt.legend() plt.show()如果预测曲线比真实曲线平滑很多说明模型没有学到足够的高频细节这时可以增加更多小时级特征或者减小正则化。如果曲线相位不对说明滞后特征可能取错了窗口需要检查lag_24和lag_168是否对齐。4. 跑项目时遇到的常见问题与排查经验4.1 Jupyter Notebook的使用问题先解决环境再谈建模Jupyter Notebook本身用起来顺手但环境问题确实会让人抓狂。热词里出现的“jupyter notebook打不开”“单元格执行代码没有任何反应”“jupyter正在连接服务器”“importerror: dll load failed while importing rpds”这些问题我都遇到过。网页打不开或者一直“正在连接服务器”最常见的根源是内核挂了。遇到这种情况我一般先到终端里重启jupyter服务再检查内核是否还能正常启动。如果内核起不来多半是Python环境出了故障conda环境或虚拟环境重装是最后手段但在此之前可以先试试在Notebook菜单里重启内核Kernel - Restart并用干净环境测试能节省不少时间。单元格执行没有反应除了内核问题还有可能是代码里写了死循环或者长时间运行的cell。排查方法很简单看单元格左侧的In[*]状态如果是星号说明代码还在运行中如果等了很久没变化就在菜单里Interrupt Kernel打断然后检查代码逻辑。至于“importerror: dll load failed while importing rpds”这是Windows上典型的动态链接库缺失或版本冲突问题通常发生在pandas或依赖包升级之后。我的处理套路是先把相关包重装一遍pip uninstall rpds-py -y pip install rpds-py --force-reinstall如果还不行就把conda环境整个重建一次。这种环境层面的事不要和它死磕太久干净环境往往几分钟就解决。4.2 预测结果偏“平”了怎么办模型预测出来的曲线比真实曲线平滑很多这几乎是时序预测最常见的问题。原因可能是滞后特征不够强模型没有抓住足够的近期波动信息也可能是模型正则化过强把所有预测都拉向均值还可能是数据在小时粒度下本身就包含大量随机波动模型学会了“不求有功但求无过”地接近均值。我处理这个问题的几个办法。第一增强滞后24小时和滞后168小时的作用可以在特征重要性里确认一下这两个特征是不是排在最前面。如果不是说明特征构造有问题。第二把预测目标从绝对客流量改成“相对上周同一时刻的变化量”让模型直接学习增量这样更容易捕捉到“今天比上周同期高还是低”的模式。第三分工作日和周末分别建模。工作日有明确的高峰形态周末的客流形态完全不同混在一起训练会让模型两头都学不专。还有一种情况是预测值特别平但没有明显高估或低估这说明模型学到了“平均客流”而没有学到“具体时刻的客流”。这时候优先检查时间特征是否完整尤其是hour和weekday是否作为类别特征参与训练。4.3 数据泄漏和时间对齐的坑数据泄漏在时序项目里很隐蔽。我踩过的一个坑是特征里包含了当天的总客流预测值或者当天的天气实况数据而这些数据在预测时点是拿不到的。比如在预测早上8点的客流时用了当天上午10点才更新的天气数据验证集上模型表现很好一上线就崩。解决办法很简单构建特征时只允许使用预测时点之前的数据任何未来信息都不能进入特征。时间对齐问题主要出现在滞后特征上。shift(24)的含义是“24小时之前”但如果原始数据的时间索引有缺失shift出来的值就会错位。比如某天少了两小时的数据shift(24)取到的就是26小时之前的值。所以做滞后特征之前一定要先把时间序列重采样对齐确保每个小时都有对应的索引即使数值是NaN也要先保留位置做完特征再填充。4.4 分站点预测时的扩展思路如果做完整网总客流之后还想下沉到站点级有几个点要提前想清楚。站点客流差异很大换乘站、写字楼站、居民区站、枢纽站的客流形态完全不同有的站早高峰突出有的站全天两头平有的站周末比工作日还高。单一模型很难同时适配所有站点。我建议先按站点聚类把客流形态相近的站分到一组每组训练一个模型或者按周客流量分档训练不同模型。站点级预测的数据量比全网大很多训练时间也会拉长。可以先用全网模型验证特征和流程跑通之后再扩展到站点。否则一次训练几十上百个模型特征没做对的话排查起来非常痛苦。做这类城市级时序预测项目我的体会是模型选型不是决定成败的关键数据和特征才是。先把baseline做扎实把特征工程做细致把评估切分做合理最后加一个适度的复杂模型效果通常不会差。而Jupyter在整个流程里扮演的角色其实就是一个稳定的交互式工作台让你随时回看数据、调整方案、快速迭代。如果顺着这个思路走不管是地铁客流、门店销售还是网站流量预测整个方法论都是通用的。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价