Jupyter地铁数据分析预测地铁后7天小时客流量地铁客流量预测这事我一开始以为不算太难毕竟数据就摆在那里无非是历史流量加个时间序列模型。真正上手做了才发现坑比想象中多得多——数据要洗、特征要造、模型要调光是“后7天小时级客流量”这个目标就够折腾一阵子。最近我在 Jupyter Notebook 里完整跑了一遍“地铁客流数据分析与预测”的流程从原始数据清洗到 LSTM 模型训练再到输出未来7天逐小时客流曲线整个过程踩了不少坑也攒了不少心得今天整理出来分享给大家。这套流程适合谁如果你是刚接触时间序列预测的数据分析新手或者已经在用 pandas、matplotlib 做数据可视化、想进一步尝试 LSTM 或 Prophet 这类预测模型再或者你手头正好有一份地铁闸机刷卡数据但不知道从哪下手这篇内容基本可以给你一条能直接照着走的路径。我会把思路、代码、参数选择和踩坑经历都拆开讲尽量让小白也能跟着做下来。1. 项目整体设计与思路拆解1.1 预测目标到底该怎么定义先别急着写代码第一步要把任务定义清楚。“预测地铁后7天小时客流量”这句话里藏着好多个关键点预测对象是客流量统计粒度是小时预测长度是7天也就是未来168个小时这三者组合在一起意味着我们需要做的是一个多步时间序列预测任务。具体来说输入是过去一段时间比如近60天的地铁进出站客流数据输出是未来7天逐小时的总客流量。注意这里的“小时客流量”可以拆成每个小时段内的总进站人数、总出站人数也可以直接看总客流量。我在实际项目中把进站和出站分开建模因为早晚高峰的进出站模式差异很大合并在一起反而会抹掉细节模型学不到通勤规律。还有一个容易忽略的问题预测的是“总客流”还是“分线路客流”如果数据里有线路字段我更建议至少按线路分组建模因为不同线路的服务区域差异很大机场线和工作日通勤线路的曲线形状完全不一样。我这版先用的是全路网汇总数据后面会讨论如何扩展到分组预测。1.2 为什么用 Jupyter Notebook 作为主战场做这种探索式数据分析加预测建模Jupyter Notebook 几乎是首选。原因有这么几个第一数据清洗和特征工程是高度试错的过程你随时要查看中间结果、画分布图、调参数Notebook 的单元格交互模式天然适合这种“做一步看一步”的工作流。第二可视化很方便matplotlib 和 seaborn 的图直接嵌在单元格下方分析结论和代码可以一一对应后面写报告、调模型也好回溯。第三社区生态成熟无论是 pandas、statsmodels 还是 TensorFlow/Keras装好就能直接导入。我见过不少人在 VS Code 或者纯 PyCharm 里跑这种分析不是说不行但线段落比较长时每次跑全量脚本很浪费时间。Notebook 可以只重跑某一个单元格比如我只改了特征工程里一个参数那就只需要重新跑那个部分不用从头再来。1.3 技术选型LSTM、Prophet 还是 XGBoost模型选型上我做了三个方向的对比经典统计模型ARIMA、机器学习模型XGBoost/LightGBM、深度学习模型LSTM。ARIMA 的优势是简单可解释但处理这种带明显周期性每天24小时、每周7天的客流数据时需要手动做季节性差分而且对多步预测的支持比较笨拙。Prophet 对节假日、周期性有内置支持上手非常快但精度上限偏低。LSTM 这类循环神经网络可以自动从历史序列中学习长短期依赖比较适合小时级客流这种强周期性、强时间依赖的数据代价是训练时间更长、需要调的超参数更多。我的实验结论是如果只做3天以内的预测Prophet 和 XGBoost 完全够用但要做到7天168个小时的预测LSTM 在捕捉“工作日一到周五逐步爬坡、周末骤降”这种中期趋势上表现更好。所以最终方案选了 LSTM 为主模型XGBoost 作为对照基准后面会详细展开。2. 数据准备清洗、重采样与特征工程2.1 原始数据长什么样我们手头的地铁数据通常是这样的每一条记录是一次闸机刷卡事件字段包含刷卡时间、站点编号、进出站标志进站/出站、线路编号等。原始数据量很大一天可能上百万条直接拿来建模肯定不行必须先做聚合。第一步是重采样按“小时”为单位统计客流量。用 pandas 的 groupby resample 可以轻松实现import pandas as pd # 假设 df 是原始刷卡记录time 是刷卡时间type 是进出站标志 df[time] pd.to_datetime(df[time]) df.set_index(time, inplaceTrue) # 按小时聚合统计每小时的总刷卡量进站 出站 hourly df.resample(H).size().to_frame(nametotal_flow) # 如果想分开进站和出站 hourly_in df[df[type] 进站].resample(H).size() hourly_out df[df[type] 出站].resample(H).size() hourly[in_flow] hourly_in hourly[out_flow] hourly_out hourly[total_flow] hourly[in_flow] hourly[out_flow]注意 resample 的用法H表示按小时聚合默认右闭区间也就是说00:00-01:00的客流会被归到01:00这个时间戳上。这个细节在做训练集和测试集切分时一定要保持一致否则预测结果会整体偏移一个小时。我一开始没注意这点画图时发现预测曲线比真实曲线晚了一个小时排查了半天才发现是标签对齐出了问题。2.2 缺失值和异常值处理地铁数据里最常见的缺失出现在夜间停运时段。大部分城市地铁不是24小时运营夜间完全没有刷卡记录这部分如果直接 NaN 处理会破坏时间序列的连续性。我的做法是保留时间索引给停运的时段填0这样模型的输入长度是固定的24小时×7天不会出现“一天只有18个小时”的尴尬情况。异常值则主要出现在大型活动散场、天气恶劣等场景某个小时客流突然飙到平时的三四倍。这类点要不要剔除取决于你的预测目标如果是要做日常运营规划建议用中位数绝对偏差MAD筛掉极端峰值防止它们干扰模型学习正常周期但如果你把演唱会散场后的短时巨量客流也看作正常需求保留反而更真实。我在这版里选择用分位数裁剪把超过99.9分位数的值降到99.9分位数既保留了大客流趋势又削弱了个别极端点的影响。2.3 特征工程不只是“时间”那么简单时间序列预测里最重要的特征就是时间本身和它的滞后值。但光有原始流量还不够我构造了这么几类特征时间特征小时0-23、星期几0-6、是否周末、是否节假日历史特征过去24小时同时刻的客流量昨天下午3点的客流对今天下午3点有很强的参考意义、过去7天同时段均值、rolling mean如过去3小时均值、rolling std周期性特征用正弦/余弦编码小时和星期保留循环特性避免0点和23点之间被模型误认为距离很远这里重点说一下周期性编码。如果你直接把“小时”当作普通数值喂给模型模型会认为小时1离小时23很远但实际上它们在周期上是相邻的。解决办法是构造两个新特征import numpy as np hourly[hour_sin] np.sin(2 * np.pi * hourly[hour] / 24) hourly[hour_cos] np.cos(2 * np.pi * hourly[hour] / 24) hourly[week_sin] np.sin(2 * np.pi * hourly[weekday] / 7) hourly[week_cos] np.cos(2 * np.pi * hourly[weekday] / 7)这样处理后小时特征就变成了二维平面上的一个点23点和0点的距离被正确地拉近了。这个技巧不仅适合 LSTM对 XGBoost 和 Prophet 同样有效。节假日特征我一开始忽略了后来验证发现影响巨大——清明、国庆这类法定假期会直接改变客流模式地铁客流在节假日期间会从“通勤主导”切换成“出行主导”。如果你手头有节假日列表建议一定要加进特征里哪怕只是简单的0/1标志位也能显著提升模型预测精度。3. LSTM 模型构建从数据窗口到训练细节3.1 为什么要用滑动窗口构造样本LSTM 吃的是三维张量(样本数, 时间步长, 特征数)。时间步长表示我们让模型回顾多少步历史比如用过去48小时的数据预测接下来1小时那时间步长就是48。构造样本时要用滑动窗口切分数据。我采用的是“多步预测的一次生成”策略不采用递归预测把上一步预测结果当输入再预测下一步而是直接让模型一次输出168个值。这样做的好处是避免误差累积坏处是训练难度更高。如果你对精度要求没那么极端也可以采用折中的方法——先预测未来24小时然后用这24小时作为输入的一部分再预测下一个24小时这样每步只预测24个值模型压力小很多。def create_sequences(data, n_steps_in, n_steps_out): X, y [], [] for i in range(len(data) - n_steps_in - n_steps_out 1): X.append(data[i:i n_steps_in]) y.append(data[i n_steps_in:i n_steps_in n_steps_out, 0]) # 假设第0列是total_flow return np.array(X), np.array(y)注意这里data需要是已经做过标准化处理的数组。LSTM 对输入尺度敏感不归一化很容易导致训练不收敛。3.2 归一化的正确打开方式归一化要用训练集的统计量不能用全量数据的统计量。很多新手在这一步会犯数据泄露的错误——先对整个数据集做归一化再切分训练集和测试集这会让模型在训练时“偷看”测试集的信息导致测试误差虚低。正确做法是只对训练集 fit然后用训练集的均值和标准差去 transform 测试集。from sklearn.preprocessing import MinMaxScaler scaler MinMaxScaler(feature_range(0, 1)) scaled_train scaler.fit_transform(train_data) scaled_test scaler.transform(test_data)我用的是 MinMaxScaler 而不是 StandardScaler因为客流数据没有明显的长尾分布MinMax 在0到1的区间表现更好。如果你自己项目里的数据有大量异常峰值可以考虑 RobustScaler它对异常值更鲁棒。3.3 模型结构与超参数调优基础 LSTM 结构我这样搭的from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout from tensorflow.keras.callbacks import EarlyStopping model Sequential() model.add(LSTM(64, return_sequencesTrue, input_shape(n_steps_in, n_features))) model.add(Dropout(0.2)) model.add(LSTM(32, return_sequencesFalse)) model.add(Dropout(0.2)) model.add(Dense(128, activationrelu)) model.add(Dense(n_steps_out)) # 输出层维度是预测步长 model.compile(optimizeradam, lossmse, metrics[mae])超参数上时间步长n_steps_in我试过24、48、72、168最终48小时效果最好。太短24小时模型看不到“昨天”的完整周期太长168小时则引入了过多噪声训练时间也长。隐藏层单元数6432是经验值过大的模型在这个数据量上容易过拟合。EarlyStopping 回调很重要它能监测验证集 loss连续多个 epoch 不下降就自动停止训练省时又能防止过拟合early_stop EarlyStopping(monitorval_loss, patience10, restore_best_weightsTrue)另外我建议用 Adam 的默认学习率起步如果发现 loss 震荡厉害可以尝试把学习率降到 0.0001。我在实验中发现在长时间序列数据上过大的学习率会导致 LSTM 梯度爆炸预测结果直接变成 NaN。3.4 训练过程中的几个崩溃瞬间第一次跑 LSTM 时我遇到过 loss 一直不下降的情况。排查了一圈发现是因为时间步长 n_steps_in 设置成了168但训练数据量本身不够大模型复杂度又高导致欠拟合。后来把时间步长降到48把 LSTM 单元数从128降到64才顺畅起来。还有一个常见问题是训练集和验证集的切分方式。切分时间序列时不能用随机打乱——时间顺序一旦被打乱模型就失去了时间依赖关系。我采用的是按时间顺序前80%的数据做训练后20%做验证验证集就是预测效果的“彩排”。4. 模型评估预测得准不准先看这几张图4.1 评估指标选择预测任务离不开评估指标。客流预测我主要看 MAE平均绝对误差、RMSE均方根误差和 MAPE平均绝对百分比误差。MAE 最直观地铁客流量的量级一天几十万人次小时客流量大概在几千到几万之间MAE 如果控制在几百以内说明预测已经比较准了。MAPE 在早高峰低谷时段会被“0客流”放大所以我计算 MAPE 时排除了夜间停运时段客流为0的小时只统计5:00到23:00之间的数据这样更贴近实际业务评估。from sklearn.metrics import mean_absolute_error, mean_squared_error mae mean_absolute_error(y_true, y_pred) rmse np.sqrt(mean_squared_error(y_true, y_pred)) # 计算 MAPE 时排除夜间0客流 mask y_true 0 mape np.mean(np.abs((y_true[mask] - y_pred[mask]) / y_true[mask])) * 1004.2 按小时和按星期拆解误差整体误差只是一个平均数我更关心模型在不同场景下的表现。把误差按小时拆开后发现模型在凌晨的误差较大绝对值虽然不大但相对误差高白天的表现很稳定。按星期几拆解则是周一和周五的误差略高于周二到周四这很合理——周一早高峰、周五晚高峰都是客流波动的“关键时刻”模型对这种边界场景的学习难度更大。如果你做的不是地铁而是商场客流那周五周六晚上的预测误差可能会更明显。4.3 可视化分析与结论画预测值和真实值的对比图时前24小时拟合几乎完美第3天开始误差逐渐放大第5天到第7天曲线形状还在但相位开始漂移——高峰时段预测值比实际早出现或晚出现半小时。这说明模型对“日内周期”捕捉得不错但对“周间趋势”的长期记忆还不够强。如果后续要提升7天预测精度可以考虑引入更多外部特征天气、节假日安排、重大活动日历或者改用 Transformer 这种能更好捕捉长距离依赖的结构。下面是画图的核心代码把真实值和预测值放在同一张图里import matplotlib.pyplot as plt plt.figure(figsize(16, 6)) plt.plot(test_time, y_true, label真实客流量, linewidth1.5) plt.plot(test_time, y_pred, label预测客流量, linewidth1.5, alpha0.8) plt.xlabel(时间) plt.ylabel(客流量人次/小时) plt.title(未来7天地铁小时客流量 预测值与真实值对比) plt.legend() plt.grid(alpha0.3) plt.show()5. 常见问题与排查技巧实录5.1 问题一LSTM 预测结果是一条直线这个现象太经典了。模型学到了“躺平”策略无论输入是什么输出都接近训练集的均值。原因一般是模型欠拟合或者数据归一化后目标值范围过窄模型发现预测均值就能得到较低损失。解决办法加一点 Dropout 防过拟合的同时检查是不是模型容量不够或试着提高学习率让模型能更快地捕捉到波动。5.2 问题二预测曲线比真实曲线延迟一小时我自己的亲身经历是重采样时标签偏移问题导致的——resample(H)默认右闭区间0点到1点的流量被归到了1点的标签上而特征hour我提取的还是0点。修复方法重采样后用labelleft, closedleft参数明确区间语义保证特征和标签对齐。hourly df.resample(H, labelleft, closedleft).size().to_frame(nametotal_flow)5.3 问题三未来7天预测的前两天很准后面越来越差多步预测的“误差雪球”效应。模型每往后预测一步不确定性就会积累。降低这个问题的影响有一个实用技巧滚动预测时加入真实值修正至少在前几个时间窗口内用实际观测到的客流替换预测值继续滚动能在一定程度上延缓误差传播。如果你做的是“离线预测”预测未来7天中间不修正那就要接受误差随预测长度增加而放大的现实。5.4 问题四Jupyter 里跑起来很慢单元格执行没反应数据处理量上百万条时Notebook 偶尔会出现“无响应”的假象。不要急着杀掉进程先检查单元格左侧的In[*]标记只要还在星号状态说明代码还在执行。我通常会用 pandas 的info()确认数据量以及用%timeit测试单步操作的耗时定位瓶颈。顺便一提像read_csv大文件时可以指定dtype减少内存占用能明显提速。6. 多模型对比与业务落地建议6.1 XGBoost 与 LSTM 谁是最终赢家我同时用 XGBoost 做了同样的预测任务对比结果很有意思前48小时 XGBoost 的误差和 LSTM 相当部分时段甚至还更准从第3天开始LSTM 的优势逐渐展现第7天时 LSTM 的 MAPE 比 XGBoost 低了差不多10个百分点。原因也好理解XGBoost 构建的是“基于特征的非线性映射”它对特征工程质量很敏感而长周期的 168 步预测需要把 168 个输出目标都当作独立回归目标或首尾相连去预测这本身就比较吃亏。LSTM 则通过循环结构天然把时序依赖内化到了网络参数里对趋势外推更有一手。所以在业务落地中我的建议是预测1-2天内的客流用 XGBoost 搭配高超的特征工程效率高、可解释性强预测3天以上尤其是7天级LSTM 更值得投入训练成本。6.2 从预测到决策客流量数据能用来做什么预测结果不只是画一张好看的曲线图它的价值在于决策支持。调度排班可以按预测的峰值时段增派人手安检口开放数量也能根据小时级流量动态调整。如果你的数据是按地铁站分组的甚至可以提前识别哪些站点会出现客流拥挤从而提前部署限流预案。如果你对接的是运营系统建议把预测结果输出成 CSV 再送进排班系统而不是直接依赖 Notebook 本身。我一般会在流程最后加一段导出代码result pd.DataFrame({ time: test_time, real_flow: y_true, pred_flow: y_pred }) result.to_csv(predicted_subway_flow_7days.csv, indexFalse)这样运营同事拿到的是一份干净的表而不是一份 Notebook 文件流转起来会顺利很多。6.3 时间粒度还能再往下钻吗标题要求的是小时粒度但我试过用同样的框架预测15分钟粒度的客流效果也不错只是模型输出维度变成了 7×24×4672训练时间翻了好几倍精度提升也有限。我的建议是如果业务上没有15分钟粒度需求就别轻易尝试成本远大于收益。如果以后想进一步优化可以按线路分组训练多个模型而不是把全网数据合在一起。分线路预测的好处是误差来源更清晰但你需要确保每条线路的数据量足够训练 LSTM。有些冷门线路一天客流量可能还不到主流线路的十分之一模型很容易欠拟合这时候用 XGBoost 反而更稳。7. 实操中的一些个人体会整套流程跑下来我最想提醒大家的一点是不要一上来就追求模型复杂度先把数据清洗、特征工程和评估口径做扎实你会发现收益远比调参大得多。我自己的实验顺序是先拿一个简单的 Prophet 模型跑通全流程确认数据和切分逻辑没问题再换成 LSTM 精调。这样做的好处是如果代码有 bug能够在简单模型上更快暴露出来而不是等到 LSTM 训练完才一头雾水地猜哪里出错了。另外Jupyter Notebook 里的代码块顺序也值得刻意维护。我习惯把模型训练、评估、可视化分别放在不同标题下每次重新跑的时候直接Run All不用担心状态污染。如果你发现自己改了某一处参数后结果奇怪大概率是单元格执行顺序和变量覆盖问题——这种坑在 Notebook 里防不胜防。地铁客流量预测这件事数据量够大、周期性明显、应用场景清晰非常适合作为时间序列预测的练手项目。顺着这篇文章的路径从清洗到建模再到评估走一遍你不仅能掌握一套完整的预测流程还能积累不少处理真实数据的经验这对后续做电力负荷预测、道路交通预测都有迁移价值。