尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

地铁AFC客流量预测实战:从交易流水到时间序列的完整代码与调优指南

发布时间:2026/9/28 22:36:48

资讯中心
01
ARTICLE

地铁AFC客流量预测实战:从交易流水到时间序列的完整代码与调优指南

地铁AFC客流量预测实战:从交易流水到时间序列的完整代码与调优指南
简介这份资源面向城市交通、智能交通方向的研究人员与数据分析学习者围绕地铁站AFC客流量预测这一典型时间序列问题提供从数据处理到多模型对比的完整代码与实验数据。压缩包共61个文件约7.59MB包含10个Python脚本、41张png图表、5个txt指标文件以及xlsx、csv、html和pth模型权重等覆盖数据预处理、特征工程、模型训练与结果可视化全流程。代码中实现了LSTM、RNN、GRU、DNN、SVM、KNN等多种预测模型并配有统一的模型比较脚本可输出误差分析、散点矩阵、雷达图与对比仪表盘等图表便于直接用于论文或报告撰写。已有48人学习适合希望快速复现客流量预测实验、对比不同模型性能或搭建自身预测方案的中高级读者参考。1. 从闸机流水到客流曲线这套 AFC 预测代码到底能干什么早高峰的站厅里闸机每刷一次卡就吐出一条交易记录十几分钟里能堆出上万条。这些记录本身只是流水真正值钱的是把它们变成未来 15 分钟、30 分钟、1 小时的进出站客流曲线——排班、限流、备品备件、能耗调度全指望这条曲线。地铁站 AFC 客流量预测完整代码数据分享这套资源干的就是把原始 AFC 交易流水清洗成时间序列、再喂给预测模型输出未来客流这件事附带完整代码和实测数据拿到手就能跑通全流程。它适合三类人做轨道交通信息化、智慧车站的工程师需要一套能落地的预测基线做时序预测研究的学生想要真实场景数据而不是玩具数据集还有被临时派去做客流分析、手上只有一堆 CSV 的开发者。下面按「数据长什么样 → 怎么清洗建模 → 坑在哪 → 怎么调优」的顺序拆开讲每一步都落到能抄的代码和参数上。2. 拆开数据包AFC 交易字段与时间粒度怎么定拿到一份 AFC 数据第一件事不是建模是搞清楚每条记录代表什么。地铁 AFC 系统的原始交易一般包含卡号、交易类型进站/出站、交易时间、车站编号、设备编号、票卡类型等字段。预测任务真正用到的是「某车站在某个时间窗口内的进站人数和出站人数」所以核心动作是把逐条交易聚合成等间隔的时间序列。2.1 原始字段到建模字段的映射常见 AFC 交易表结构大致如下不同城市字段名会有差异但语义基本一致原始字段含义建模用途CARD_ID票卡编号去重、识别同一乘客TXN_TYPE交易类型区分进站/出站TXN_TIME交易时间戳时间切片依据STATION_ID车站编号分组维度DEVICE_ID闸机编号异常排查、设备粒度分析CARD_TYPE票卡类型可选特征通勤卡/单程票建模时通常只保留 STATION_ID、TXN_TIME、TXN_TYPE 三列做聚合其余字段要么做特征要么在清洗阶段丢弃。这里有个容易忽略的点同一张卡短时间内可能有多条记录比如进站刷卡失败重刷直接计数会把客流虚高需要按卡号和时间窗口去重。2.2 时间粒度选择15 分钟还是 1 小时粒度选多大取决于你要预测什么。做实时限流预警15 分钟粒度才有意义做全日客流预测或排班1 小时粒度足够且噪声更小。这套代码默认按 15 分钟切片因为地铁客流在早晚高峰的爬坡和回落往往发生在半小时内1 小时粒度会把峰值抹平。聚合逻辑用 pandas 的 resample 就能完成关键是先把时间戳设为索引并排序import pandas as pd # 读取原始 AFC 交易流水 df pd.read_csv(afc_raw.csv, parse_dates[TXN_TIME]) # 只保留进站记录做进站客流预测出站同理改 TXN_TYPE df_in df[df[TXN_TYPE] 进站].copy() # 按卡号时间窗口去重避免重刷导致客流虚高 df_in df_in.sort_values(TXN_TIME) df_in df_in.drop_duplicates(subset[CARD_ID, STATION_ID], keepfirst) # 按车站分组15 分钟粒度聚合计数 df_in df_in.set_index(TXN_TIME) flow (df_in.groupby(STATION_ID) .resample(15T)[CARD_ID] .count() .reset_index() .rename(columns{CARD_ID: inflow}))这段代码的逻辑是先筛进站、再去重、最后按车站和时间窗口计数。resample(15T)里的15T表示 15 分钟改成30T或1H就能切换粒度。drop_duplicates的 subset 用卡号加车站是因为同一张卡在不同车站的进站是两次独立出行不能跨站去重。聚合后如果某些时间窗口没有记录count 会得到 0这本身是有效信息深夜无客流不要急着删。注意不同城市的 AFC 时间戳可能是字符串格式且带时区parse_dates 解析失败时先检查原始格式必要时用pd.to_datetime(df[TXN_TIME], format%Y%m%d%H%M%S)显式指定。3. 特征工程与模型选型把时间序列喂给谁聚合出客流序列只是起点直接拿原始数值丢进模型效果通常一般。时间序列预测的胜负手在特征历史滞后、滑动统计、时间周期编码这三类特征决定了模型能不能抓住早晚高峰的规律。3.1 滞后特征与滑动窗口特征客流序列有强自相关性——当前 15 分钟的客流和上一个、上两个 15 分钟高度相关也和昨天同一时刻、上周同一天同一时刻相关。把这些滞后项显式构造成特征列模型才有抓手import numpy as np # 按车站分组后构造滞后与滑动特征 flow flow.sort_values([STATION_ID, TXN_TIME]) def build_features(g): g g.copy() # 滞后特征前1、2、4个时间窗口15min/30min/1h前 for lag in [1, 2, 4]: g[flag_{lag}] g[inflow].shift(lag) # 昨日同时刻、上周同时刻9624h/15min6727天/15min g[lag_yesterday] g[inflow].shift(96) g[lag_lastweek] g[inflow].shift(672) # 滑动统计过去1小时均值与标准差 g[roll_mean_4] g[inflow].shift(1).rolling(4).mean() g[roll_std_4] g[inflow].shift(1).rolling(4).std() return g flow flow.groupby(STATION_ID, group_keysFalse).apply(build_features) flow flow.dropna().reset_index(dropTrue)逻辑说明shift(1)表示取前一个时间点的值shift(96)是 24 小时前96 个 15 分钟shift(672)是一周前。滑动窗口用shift(1).rolling(4)是为了避免数据泄漏——计算当前时刻特征时只能用当前时刻之前的数据否则模型在训练集上表现虚高上线就翻车。dropna会丢掉序列开头没有足够历史的行这是正常代价。3.2 时间周期编码与模型选择时间本身要编码成模型能用的形式。小时、星期几这类周期变量用 sin/cos 编码比直接给整数好因为 23 点和 0 点在数值上应该接近而不是相差 23# 时间周期特征 flow[hour] flow[TXN_TIME].dt.hour flow[minute] flow[TXN_TIME].dt.minute flow[dow] flow[TXN_TIME].dt.dayofweek # 一天内的位置0~95做周期编码 flow[slot] flow[hour] * 4 flow[minute] // 15 flow[slot_sin] np.sin(2 * np.pi * flow[slot] / 96) flow[slot_cos] np.cos(2 * np.pi * flow[slot] / 96) flow[dow_sin] np.sin(2 * np.pi * flow[dow] / 7) flow[dow_cos] np.cos(2 * np.pi * flow[dow] / 7)模型选型上这套代码走的是「树模型基线 时序模型进阶」的路线。树模型LightGBM、XGBoost对特征工程友好、训练快、可解释适合先跑出基线时序模型LSTM、GRU、Transformer能自动学序列依赖但需要更多数据和调参。常见做法是先用 LightGBM 把特征重要性跑出来看看哪些滞后项真正有用再决定要不要上深度模型。如果数据量只有几个月、车站数不多树模型往往就够了别一上来就堆 LSTM。提示划分训练集和测试集时时间序列必须按时间顺序切不能随机打乱。用前 80% 时间做训练、后 20% 做测试否则未来信息泄漏进训练集评估结果没有参考价值。4. 避坑与排查AFC 客流预测里最容易翻车的五件事这套流程跑通不难难的是跑出来的结果能信。下面五条是我在实际项目里踩过的坑每条按现象、原因、解决写清楚。现象一模型在测试集上 MAPE 只有 5%上线后误差翻三倍。原因多半是数据泄漏——滑动窗口特征用了当前时刻的值或者随机打乱了时间顺序做交叉验证。解决所有统计类特征统一加shift(1)划分数据集严格按时间切交叉验证用 TimeSeriesSplit 而不是 KFold。现象二早晚高峰预测总是偏低平峰偏高。原因是损失函数用 MSE模型倾向于预测均值峰值被平滑掉。解决改用对峰值更敏感的损失比如 LightGBM 的objectiveregression_l1MAE或 Huber 损失也可以在训练时对高峰样本加权。现象三某些车站预测完全不准其他车站正常。原因通常是该车站数据缺失严重或者有大型活动、临时封站等异常事件没被剔除。解决先按车站统计缺失率和异常值缺失超过 30% 的车站单独处理或直接排除异常日客流突增突降超过 3 倍标准差标记后从训练集剔除。现象四换一批新数据重新训练特征列对不上报错。原因是 AFC 字段名或时间格式在不同批次间不一致。解决在数据加载层做字段标准化映射把各批次字段统一重命名后再进特征工程别让原始字段名渗透到建模代码里。现象五预测值出现负数。树模型一般不会但线性回归或某些神经网络会。原因是模型没有输出约束。解决预测后做clip(lower0)或者在模型层面用对数变换log1p训练、预测后再expm1还原。5. 调优与验证让预测曲线真正贴合高峰基线跑通之后真正拉开差距的是调优和验证方式。很多人模型训完看一眼 MAPE 就结束但客流预测的评估要看峰值时段误差、要看不同车站的稳定性还要看预测曲线和真实曲线的形状是否吻合。5.1 分时段评估与峰值误差整体 MAPE 会被大量平峰样本拉低掩盖高峰时段的糟糕表现。评估时按早高峰7:00-9:00、晚高峰17:00-19:00、平峰分别算误差from sklearn.metrics import mean_absolute_percentage_error def eval_by_period(y_true, y_pred, slots): # slots 为对应的时间槽用于区分高峰/平峰 peak_am (slots 28) (slots 36) # 7:00-9:00 peak_pm (slots 68) (slots 76) # 17:00-19:00 for name, mask in [(早高峰, peak_am), (晚高峰, peak_pm), (平峰, ~(peak_am | peak_pm))]: mape mean_absolute_percentage_error(y_true[mask], y_pred[mask]) print(f{name} MAPE: {mape:.4f})逻辑说明slot 是 0~95 的一天内时间槽28 对应 7:0036 对应 9:00。分时段算 MAPE 能暴露「整体好看、高峰拉胯」的问题。如果高峰 MAPE 明显高于平峰说明模型对峰值的学习不足回到上一章加高峰样本权重或换损失函数。5.2 关键参数与调优方向以 LightGBM 为例几个对客流预测影响最大的参数参数建议范围作用num_leaves31~127控制树复杂度太大易过拟合learning_rate0.01~0.1学习率配合 n_estimators 调min_data_in_leaf20~100叶子最小样本防止噪声拟合feature_fraction0.7~0.9特征采样比例objectiveregression_l1 / huber对峰值更敏感调参顺序建议先固定 learning_rate0.05、num_leaves63 跑基线再用早停确定 n_estimators最后微调 min_data_in_leaf 和 feature_fraction。别一上来就网格搜索所有参数客流数据量不大过拟合风险比欠拟合高。验证方法上除了时间序列切分建议留出一个完整周做「滚动预测」验证用前 N 天预测第 N1 天逐日滚动看误差是否稳定。如果某几天误差突然变大回去查那几天是不是节假日或异常事件。从那以后我每次做完客流预测都强制走一遍分时段评估加滚动验证再好看的总体指标也不直接信。希望这套代码和数据能帮你少走几个弯路把预测曲线真正用到排班和限流决策里。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。