
简介基于Python与Django开发的地铁客流预测系统完整源代码与项目文档以地铁ACC系统用户行程及站点数据为基础面向轨道交通客流量分析与智慧交通方向的学生、研究者及开发人员。项目采用B/S架构后端由Django实现前端集成Bootstrap、jQuery和Echarts支持线路级、站点级客流统计、预测与预警用户可调整相关因子查看指定时间与位置的客流变化。压缩包共26个文件包含22个Python源码文件覆盖配置、模型、路由、视图、表单、接口API等关键模块另有2个Markdown文档和2个gitignore辅助文件提供接口说明与项目设计说明便于快速上手和部署。整体遵循Django标准工程结构含settings、wsgi、asgi等部署配置可直接用于学习或二次开发。资源整体约30KB已有71人浏览学习适合作为课程设计、毕业设计或比赛项目的基础方案也可用于学习Django全栈开发与Echarts可视化实践。1. 地铁客流预测系统解决什么问题从ACC刷卡流水到能用的预测结果地铁调度员最头疼的不是运力不够而是不知道下一小时哪个站点会突然涌入三倍客流。基于Python实现的地铁客流预测系统就是拿地铁 ACC 系统自动售检票系统积累的用户行程数据和站点数据通过清洗、特征工程和时序模型把未来15分钟到2小时的进站量、出站量、断面客流量算出来。这套系统的核心价值不在模型多高级而在对原始刷卡流水的处理深度——真实项目里数据预处理和特征构造要占七成工作量模型训练反而是最顺的一段。如果你手上有地铁 ACC 数据或者正在做轨道交通方向的毕业设计、课题申报又或者要给调度、运营部门搭一套短时客流预测工具这篇从数据字段、模型选型、Python完整代码到踩坑记录的内容可以直接拿来做落地参考。你不需要系统掌握深度学习但有Python基础和pandas的基本操作读起来会比较顺。2. 读懂ACC用户行程数据字段拆解与预处理方案这套系统最底层的数据全部来自ACC系统。ACC是自动售检票系统里负责交易清分和客流统计的核心模块每天汇总全线网的进出站刷卡流水一条记录代表一次完整行程。预测客流的第一步不是建模而是先把这条流水读懂。很多人一上来就写模型结果聚合口径错了后面所有站点曲线都是歪的。2.1 ACC行程数据的核心字段与业务含义从ACC系统导出的文件通常是一张很大的刷卡流水表单日行数从几十万到上千万不等。字段结构各城市可能略有差异但常见做法是至少包含这些列字段典型含义预测中的用途交易流水号单次行程唯一ID去重依据卡号脱敏用户唯一标识识别通勤常旅客卡类型普通卡/学生卡/老年卡分析人群结构进站站点编码起点站进站量聚合对象进站时间起点刷卡时刻进站时序依据出站站点编码终点站出站量聚合对象出站时间终点刷卡时刻出站时序依据线路号/运行方向线路归属断面客流计算这张表里最容易被忽略的是“时间”和“站点”是分开的两列。预测进站量要看进站时间预测出站量看出站时间。如果聚合时选错时间字段模型输出会整体偏移一个时段后面做对比分析时很难查出来。另外卡号在ACC系统里一般已经脱敏不需要担心个人隐私问题但要注意脱敏后的卡号仍然可以做去重和通勤识别不要轻易删除。我一般拿到数据后的第一段代码就是确认字段类型避免后面的聚合操作踩到大数读取的坑import pandas as pd # 从ACC系统导出的原始流水列名已做脱敏统一 df pd.read_csv(acc_trip_20250310.csv, dtype{ card_id: string, entry_station: string, exit_station: string }, parse_dates[entry_time, exit_time]) print(df.shape) print(df.dtypes) print(df.head())逻辑说明dtype里把卡号和站点先固定成字符串类型避免站点编码这类大整数在读取时被转成科学计数法或丢掉前导零parse_dates让pandas直接解析时间列后续按小时重采样就不用再手动转格式。先看shape和dtypes确认数据行数在预期范围、时间列确实被解析成datetime再做任何聚合。这一步能省掉后面大量排查问题的时间。参数说明如果原始文件里站点编码是“0101”这种带前导零的格式不指定dtype为字符串就会被读成数字101parse_dates对几百MB的小文件够用如果文件很大建议先用nrows10000读前一万行跑通流程再全量读入避免内存打满。2.2 原始刷卡记录到标准客流量表清洗与聚合清洗是第一道关卡我一般按四步走去重、剔除缺失、过滤运营时段、修正异常行程。这四步做完才敢把数据喂给预测模型。第一步是去重。ACC数据在传输链路里偶尔会出现同一笔交易被上传两次的情况直接用卡号加进出站时间去重即可。第二步是剔除缺失字段的记录。预测对象是站点级客流量如果一个行程没有进站站点或进站时间它就无法归入任何站点时段留着反而会在聚合表里产生误差。直接dropna但不建议所有列一起drop而是按预测口径分别处理。# 同卡同一进出站时间视为重复记录保留第一条 df df.drop_duplicates( subset[card_id, entry_station, entry_time, exit_station, exit_time] ) # 进站预测只关心进站站点和进站时间缺失则剔除 df df.dropna(subset[entry_station, entry_time]) # 地铁夜间有停运窗口凌晨刷卡量极少按运营时段过滤 df df[(df[entry_time].dt.hour 6) (df[entry_time].dt.hour 23)] # 单次行程进出站时间差超过4小时视为异常剔除 df[travel_minutes] (df[exit_time] - df[entry_time]).dt.total_seconds() / 60 df df[(df[travel_minutes] 240) | (df[travel_minutes].isna())]逻辑说明去重不能只按交易流水号因为同一个流水号可能在补传时出现两次加卡号和时间一起判断更稳。过滤运营时段用hour字段做范围筛选把凌晨的噪声全部去掉。单日行程超过四小时基本不可能是正常地铁出行要么是跨日数据要么是刷卡异常剔除后必须再做一次索引重置避免后面groupby出现空行索引干扰。洗完数据后按“站点15分钟时段”聚合出进站量表这是后面所有模型的主干数据# 按进站站点和15分钟时段统计进站量 df[time_bin] df[entry_time].dt.floor(15min) ridership ( df.groupby([entry_station, time_bin]) .size() .reset_index(nameinflow) ) print(ridership.head())逻辑说明dt.floor(15min)把进站时间向下取整到15分钟粒度比如08:07变成08:0008:16变成08:15。这样就能把一个自然日切成96个时段刚好对接地铁调度里“配车计划按刻钟划分”的惯例。groupby后size()统计每个站点每个时段的记录条数也就是该站该时段的进站量。参数说明粒度改成30min或60min也容易但结合ACC数据的实际分布15分钟在短时预测里信息量最足再细到5分钟会引入大量0值模型很难学。站点数多的时候这张表会很大建议先只跑两三个核心站点验证流程再全站展开代码逻辑不变只是数据量变大。另外进站量和出站量要分别聚合。很多第一次做的人只做了进站表等模型上线后调度室要出站数据来排疏散方案又得回来补一遍。对称地做df[exit_bin] df[exit_time].dt.floor(15min) exit_flow ( df.groupby([exit_station, exit_bin]) .size() .reset_index(nameoutflow) )这说明系统的输入不是单文件而是多视图进站视图、出站视图。再进一步按“进站站点出站站点”配对聚合就能得到OD矩阵断面客流预测需要OD矩阵再按线路方向累加。预测任务不同聚合键就不同这一点要在项目文档里写清楚后面换人维护才不会搞混。2.3 站点数据对齐静态表与动态表的joinACC流水里的站点编码是线路AFC系统的内部编码和运营部门常用的站点名称、线路线别不一定一致。拿到手的第一件事是确认手边有没有一张站点主数据表至少包括站点编码、站点名称、线路号、换乘标识、行政区和经纬度。没有就先找运营方要不要自己猜编码否则后面画到图上全是乱码。站点表join的主要意义有两个一是验证流水里的站点编码是否有不在主表里的孤儿编码二是在进站量表上补充站点属性特征。站点属性在预测里是很强的信号比如换乘站的客流波形和普通站完全不同早晚高峰的双峰更陡。代码上就是一次普通merge但重点在于查merge之后的对不齐记录station_info pd.read_csv(station_info.csv) station_info station_info.rename(columns{ station_code: entry_station, is_transfer: transfer_flag }) ridership ridership.merge(station_info, onentry_station, howleft) # 查没有匹配上主表的站点 orphan ridership[ridership[transfer_flag].isna()] print(orphan[entry_station].unique())逻辑说明用howleft保留客流表的全部行然后用transfer_flag是否为空来筛掉没匹配上的记录。如果orphan非空两种可能一是新开通线路的站点还没同步进主表二是源数据里有乱码。前者要补充主表再重跑一遍merge后者要回到ACC系统上游排查不能直接把空值填0。这里最怕的是把孤儿站点悄悄丢掉上线时你会发现某个站在预测结果里永远消失。到这一步预测所需的最核心表已经成型站点、时段、进站量、站点属性。数据量上一条中型线路一天大约百万级记录全网十几条线日流水几千万。15分钟粒度聚合后一天行数是站点数乘以96三百个站约三万行一个月约九十万行pandas处理完全没问题。如果做到分钟级就必须上polars或Spark对新手不友好。这也是我优先选15分钟聚合的另一个原因在信息量和算力之间最平衡。3. 特征工程与模型选型短时客流预测的核心环节数据表在手接下来是建模。客流是强周期的时间序列核心思路是把时间、历史、站点等维度的信号转化为模型能直接吃的特征。这一章讲清楚三件事特征怎么构造、模型为什么选LightGBM、评估指标怎么定。3.1 特征怎么构造时间、滞后、站点属性三件套我把特征分成三大类时间属性、历史滞后值、站点属性。缺一类模型都会明显掉精度。时间属性是基础时段编号、星期几、是否工作日、是否节假日。其中节假日对客流影响非常大尤其五一、国庆前面一天和当天客流波形几乎完全变形。这里我用一个简单的isin操作把节假日列表套进去holiday_dates set(pd.read_csv(holidays.csv)[date].astype(datetime64[ns])) df[hour] df[time_bin].dt.hour df[minute] df[time_bin].dt.minute df[time_idx] df[hour] * 4 df[minute] // 15 df[weekday] df[time_bin].dt.weekday 1 df[is_holiday] df[time_bin].dt.date.isin(holiday_dates) df[is_prev_holiday] (df[time_bin] pd.Timedelta(days1)).dt.date.isin(holiday_dates) df[is_next_holiday] (df[time_bin] - pd.Timedelta(days1)).dt.date.isin(holiday_dates)逻辑说明time_idx把一天映射到0到95相当于告诉模型“现在是当天的第几个15分钟”模型可以学到早高峰和晚高峰的固定位置。is_holiday用日期集合做匹配避免了手动写几十个if-else。is_prev_holiday和is_next_holiday是节假日的前一天和后一天这两个特征对假期前后的客流突变很有用很多项目漏掉这两列导致假期前后一天预测值明显偏低。滞后特征是客流预测里贡献最大的部分。相邻时段、昨天同时段、上周同一天同时段这三列加进去后模型精度通常能提升一大截df df.sort_values([entry_station, time_bin]).reset_index(dropTrue) df[lag_prev] df.groupby(entry_station)[inflow].shift(1) # 上一个15分钟 df[lag_d1] df.groupby(entry_station)[inflow].shift(96) # 同站点昨天的同一时段 df[lag_w1] df.groupby(entry_station)[inflow].shift(672) # 同站点上周同一时段逻辑说明shift必须在sort_values之后调用否则时间乱序滞后值全是错的。shift(96)表示同一站点往前数96行因为一天96个15分钟shift(672)是七天前。这两个特征能抓住“当前时段客流和昨天、上周同一时段高度相关”的规律。lag_prev抓的是短时惯性比如早高峰开始的前15分钟客流往往是逐步爬坡而不是突变。站点属性特征包含换乘站标志、站点历史平均进站量。其中换乘站是0/1布尔站点历史均值用全量数据算一个稳定基线。这个站点均值相当于给模型一个尺度参考比如大型枢纽站和社区站平均客流差一个数量级模型会优先按它切分往往加上这一列比调参划算得多。3.2 模型选型对比为什么先上LightGBM而不是LSTM客流预测的模型选型业内常见的路径有四条统计时序、Prophet、树模型、深度学习。我在实际项目里习惯先上LightGBM原因是它兼顾训练速度、特征灵活性和可解释性。全量站点一起建模时LightGBM用一个模型吃下所有站点的样本站点特征作为输入列天然做信息互补。比如某个换乘站突发大客流时模型可以从同类站点学到的特征迁移过来。如果用LSTM给每个站单独建模型三百个站就要训练三百个模型数据量小还容易过拟合。模型适用场景明显优势明显劣势ARIMA单站单序列数据平稳参数少、可解释节假日脉冲容易失效Prophet强周期、多站点批量自动处理节假日对突变点拟合不足LightGBM多站点跨序列建模特征灵活、训练快需要特征工程到位LSTM长序列依赖自动学时序模式数据量小容易过拟合表格看一下就清楚ARIMA做单站没问题但全网几百个站分别调参不现实Prophet对节假日有内置处理但对特殊事件的突变拟合不足LSTM效果好但成本高。LightGBM是性价比最稳的选择尤其适合作为系统第一版上线。只有当你手上积累了半年以上的15分钟粒度数据并且明显感觉树模型在长周期节假日模式上不够用时再考虑LSTM或Transformer加进来做模型融合。3.3 时间序列切分与评估指标别用随机训练集建模型前必须先把训练集和验证集切对。很多新手拿sklearn的train_test_split直接随机切这在时序预测里是严重错误。随机切会让模型在训练时“偷看”未来数据验证集上表现虚高一上线就翻车。客流预测的切分必须按时间顺序前70%做训练最后30%做验证。更进一步可以用滚动预测的方式模拟线上环境后面落地章节细说。from sklearn.model_selection import TimeSeriesSplit # 时序切分不随机打乱按时间顺序依次为训练、验证 split_date df[time_bin].max() - pd.Timedelta(days30) train df[df[time_bin] split_date] val df[df[time_bin] split_date]评估指标方面短时客流预测惯例用MAE、MAPE、RMSE。但MAPE有个大坑夜间很多站点的真实进站量是00作为分母会让MAPE变成无穷大。我一般只对真实值大于某个阈值的时段计算MAPE比如只统计早6点到晚23点或者过滤掉真实值低于5人次的时段。同时RMSE能突出大误差站点适合发现那些在高峰时段预测严重偏低的站。简单地说MAE看整体水平RMSE看极端误差两个指标配合着用。from sklearn.metrics import mean_absolute_error, mean_squared_error # 过滤掉夜间低客流时段再算MAPE避免除零问题 mask y_val 5 mape (abs(y_val - pred)[mask] / y_val[mask]).mean() * 100 mae mean_absolute_error(y_val, pred) rmse mean_squared_error(y_val, pred, squaredFalse) print(fMAE: {mae:.2f} 人次/15min MAPE: {mape:.2f}% RMSE: {rmse:.2f})逻辑说明评估时先过滤低客流时段是客气做法更严格的做法是全天都评估但把夜间0值单独列出因为夜间波动对调度决策没有实际意义。阈值5是根据站点粒度定的如果做全线网聚合阈值可以提高到20甚至50。阈值不要拍脑袋应结合业务上“低于多少人次不需要额外加车”来定。到这一步特征、模型、评估口径都清楚了可以进入真正的代码实现阶段。4. Python实现一套可运行的客流预测框架从加载到预测输出这一章给出一套最精简但能跑通全流程的代码。你可以直接复制改造成自己的项目也可以拿它当作项目文档里的技术基线。我用LightGBM做主干模型整套流程按“加载合并、特征增强、切分、训练、评估、输出”六个模块组织。4.1 数据加载与特征增强把两类文件合并成模型输入假设你已经有了客流表ridership_15min.csv和站点表station_info.csv第一步是读取并套用上一章的特征函数import pandas as pd import numpy as np import lightgbm as lgb # 读取聚合后的客流表和站点表 ridership pd.read_csv(ridership_15min.csv, parse_dates[time_bin]) station pd.read_csv(station_info.csv) # 站点表字段重命名保持主键一致 station station.rename(columns{station_code: entry_station}) # 合并站点属性 data ridership.merge(station, onentry_station, howleft) # 在站点维度上按时间排序这一步决定滞后特征是否正确 data data.sort_values([entry_station, time_bin]).reset_index(dropTrue)逻辑说明merge时统一用entry_station做主键如果流水里的站点编码和站点表对不上合并后会出现空值前面章节已经讲过如何检查。reset_index(dropTrue)很重要因为后续groupby和shift依赖索引顺序乱索引会导致shift取错行。特征增强沿用第三章的特征函数整理成一个独立函数方便项目文档复用def build_features(data, holidaysNone): df data.copy() df[hour] df[time_bin].dt.hour df[minute] df[time_bin].dt.minute df[time_idx] df[hour] * 4 df[minute] // 15 df[weekday] df[time_bin].dt.weekday 1 if holidays is not None: holiday_dates set(pd.to_datetime(holidays[date])) df[is_holiday] df[time_bin].dt.date.isin(holiday_dates) df[is_prev_holiday] (df[time_bin] pd.Timedelta(days1)).dt.date.isin(holiday_dates) df[is_next_holiday] (df[time_bin] - pd.Timedelta(days1)).dt.date.isin(holiday_dates) else: df[is_holiday] 0 df[lag_prev] df.groupby(entry_station)[inflow].shift(1) df[lag_d1] df.groupby(entry_station)[inflow].shift(96) df[lag_w1] df.groupby(entry_station)[inflow].shift(672) df[station_avg] df.groupby(entry_station)[inflow].transform(mean) return df data build_features(data, holidayspd.read_csv(holidays.csv))逻辑说明holidays参数做成可选代码换个城市没有节假日表也能跑这是项目文档里“可控依赖”的常见做法。station_avg用groupby transform返回与原表等长的均值列避免后续merge步骤。4.2 时序切分与训练集构造把漏斗建好构造完特征后先去尾部的空值行因为前七天没有滞后值。然后按时间顺序切分# 前7天因滞后值缺失被剔除这是正常现象 data data.dropna(subset[lag_d1, lag_w1, lag_prev]) # 最后30天做验证其余做训练 split_date data[time_bin].max() - pd.Timedelta(days30) train data[data[time_bin] split_date] val data[data[time_bin] split_date] feature_cols [ time_idx, weekday, is_holiday, is_prev_holiday, is_next_holiday, lag_prev, lag_d1, lag_w1, transfer_flag, station_avg ] X_train, y_train train[feature_cols], train[inflow] X_val, y_val val[feature_cols], val[inflow]逻辑说明feature_cols不要包含time_bin和entry_station因为它们是索引字段而不是特征。transfer_flag如果是字符串需要先转成0/1或者在读取时用dtype指定。X_train是普通DataFrameLightGBM会自动处理数值和布尔混合类型但字符串类型会报错。4.3 LightGBM训练参数、早停与迭代轮数这里直接用一个标准参数集适合站点客流量级在几十到几千人次的场景model lgb.LGBMRegressor( objectiveregression, num_leaves63, # 每棵树叶子数63对96时段粒度够用 learning_rate0.05, n_estimators2000, subsample0.8, # 行采样比例防过拟合 colsample_bytree0.8, # 列采样比例增加随机性 random_state42 ) model.fit( X_train, y_train, eval_set[(X_val, y_val)], eval_metricmae, callbacks[lgb.early_stopping(100), lgb.log_evaluation(100)] ) print(f最佳迭代轮数: {model.best_iteration_})逻辑说明num_leaves63对应一棵树最多63个叶节点对站点客流这种一天96个时段的信号足够表达时段交互数值再大容易记住站点特例噪声。subsample和colsamplebytree都是0.8让每棵树用不同的行和列子集训练减少过拟合。early_stopping(100)表示验证集MAE连续100轮不下降就停止配合n_estimators2000的上限避免训练到过拟合点。参数说明learning_rate0.05偏保守如果训练数据量大可以提高到0.1加快收敛如果发现验证集MAE震荡先降低learning_rate同时把early_stopping轮数加大到200。num_leaves在客流预测里不要设到127以上我测试过多次过大的叶子只会在换乘站或枢纽站上产生过拟合峰值。4.4 预测、还原与可视化让结果能直接给运营看模型训练完预测并转成业务可读的表格pred model.predict(X_val, num_iterationmodel.best_iteration_) # 把站点编码、时段时间、真实值、预测值放在一起输出 result X_val[[entry_station, time_bin]].copy() result[actual] y_val.values result[pred] np.round(pred, 1) result result.sort_values([entry_station, time_bin]) result.to_csv(forecast_result.csv, indexFalse)逻辑说明用best_iteration_指定使用早停时的最佳模型而不是默认的最后一轮。round到一位小数是保留精度同时方便阅读站点客流预测结果是连续值round到整数也可以但建议保留一位小数方便评估。输出成CSV后运营可以直接在Excel里看各站点未来几个时段进站量这是一个可用的交付格式。画一张单站一周的真实值与预测值对比检查曲线是否跟得上波形import matplotlib.pyplot as plt # 任选一个站点画最近7天对比 one_station result[result[entry_station] station_07].tail(7 * 96) plt.figure(figsize(12, 5)) plt.plot(one_station[time_bin], one_station[actual], labelactual) plt.plot(one_station[time_bin], one_station[pred], labelpred, linestyle--) plt.legend() plt.title(Station 07 Flow Prediction vs Actual (Last 7 Days)) plt.xticks(rotation45) plt.tight_layout() plt.savefig(station07_check.png, dpi150)逻辑说明tail(7*96)取最近7天共672个点plt.plot直接按时间画折线。真实值用实线预测值用虚线。肉眼检查重点看早晚高峰两个峰是否对齐、峰值高度是否接近。如果预测曲线比真实值滞后一个时段说明lag_prev权重过高或时间字段用错了如果峰值明显偏低说明节假日特征或站点均值特征没有被充分利用。这套代码跑通后你已经有了一台能输入ACC流水、输出未来客流预测的Python系统雏形。但离真正靠谱还有一段距离下一章说说我在真实数据上踩过最深的几个坑。5. 避坑指南ACC数据做客流预测的常见问题与排查这一章是血泪经验汇总。以下几个问题都是真实项目里翻车频率最高的地方每一条都按现象、原因、解决来写照着排查能省下大量熬夜时间。5.1 滞后特征泄漏验证集漂亮上线就翻车现象是模型在验证集上MAE只有十几但实际部署后次日预测误差翻了两三倍。原因是我曾在特征构造时用了shift(-1)本意是构造“未来15分钟作为预测目标”结果把未来值当特征传给了模型。验证集切分虽然按时间顺序切了但特征列里已经包含未来信息模型在验证集上等于看到了部分答案。这类泄漏不会报错只能靠检查代码发现。解决方法是检查所有shift方向预测特征只允许正数shiftshift(1)、shift(96)、shift(672)任何shift负数都是在偷看未来。我后来在代码里加了一行断言assert not any(col.endswith(_future) for col in feature_cols), 检测到未来特征这行在训练前拦截明显泄漏虽然拦不住所有情况但至少能挡住手误。5.2 夜间零值把MAPE拉到无穷大现象是评估时MAPE输出inf或者某些站点误差大得离谱。原因是夜间0到6点的客流真实值是0预测值哪怕只有0.1MAPE的百分比也会变成无穷大。很多评估代码直接拿全量数据算MAPE没有过滤低值时段。解决方法是评估时分两段白天重点时段用MAPE全天用MAE和RMSE。前面已经给出过滤代码。业务上调度室真正关心的是早6点到晚23点夜间预测值唯一用途是确定首班车时间误差大一点无伤大雅。项目文档里对评估口径应当有明确描述建议这么写MAPE只计算进站量大于等于5人次的时段。5.3 节假日前后一天模型完全失灵现象是平时MAE值很好但一到清明、五一、国庆这种连假预测值明显偏低尤其是放假前一天下午和假期第一天上午。原因是训练数据里节假日样本太少模型学不到节假日的波形规律。如果只用最近三个月的数据训练可能只经历过一个节假日样本量根本不够。解决方法是三管齐下第一把is_prev_holiday和is_next_holiday特征加进模型第二训练数据拉长到包含至少两个同类型节假日第三对节假日样本做加权提高其在损失函数中的占比。LightGBM可以通过sample_weight参数传入对节假日时段样本乘以1.5或2的权重。sample_weight np.where(train[is_holiday] 1, 2.0, 1.0) model.fit(X_train, y_train, sample_weightsample_weight)这样做之后节假日预测偏差能缩小不少但完全消除很难。我现在的习惯是重大节假日前手动核对一次预测曲线看到明显异常就调参重训而不是等当天才发现。5.4 站点编码对不上新线开通后预测结果缺一堆站点现象是某天开始预测结果表里突然少了几个站或者某个站的客流从几百骤降到个位数。原因大概率是ACC系统里新增了站点编码但站点主数据表没有同步更新。merge时用howleft主表缺失的站点全部变成NaN如果没做孤儿检查就直接填充这些站就悄悄被丢掉了。解决方法是上线前跑一遍孤儿站点检查merge后筛一次transfer_flag为空的记录把missing站点列表发给数据负责人。同时也可能是老站编码调整组织了一次全线编码映射更新但历史数据没有回刷。这两类问题都会导致某段时间窗口内数据对不上最稳的做法是在主表里维护一份站点编码变更日志并按日期做有效性关联。5.5 新站滞后特征为空上线当天预测值直接偏0现象是某条新线开通当天新站点的预测值全部是0或者极小的数。原因是新站没有历史数据lag_d1和lag_w1全为空。训练时dropna把这些行删掉了但上线预测时新站数据进来特征为空LightGBM内部把空值交给默认方向处理常常直接预测到低频区间。解决方法是针对新站做冷启动填充。常见做法是找同一线路里客流规模相似的既有站点用其同期均值填充新站点的滞后特征。我在代码里实现是维护一张“站点客流等级表”对新站按其站点类型比如是否换乘站、是否终点站匹配同类站均值if new_station_lag.isna(): new_station_lag similar_station_avg这个方案不完美但至少能让新站点在开通首日有个合理基线上线后再逐步用真实数据替换。6. 把预测系统落地到真实业务回溯验证、参数微调与部署习惯前面五章解决了“能跑”和“避坑”的问题这一章讲怎么让预测系统在真实业务里稳定运行给出三个可以立即用起来的技巧。第一个技巧是每周固定做一次回溯测试。我习惯在每周一早上用上周数据重新评估模型把上周真实客流和预测值对比看MAE是否超出警戒线。具体做法是用滚动窗口复现线上预测节奏在T时刻只用T之前的数据做特征预测T1到T2小时内的客流然后滑动时间窗口重复这一步。这段逻辑可以做成一个backtest.py脚本每周跑一次输出一份误差报告。如果MAE超过日常基线15%优先去查前两周是否有新线开通、站点编码变更或节假日配置遗漏。第二个技巧是参数微调聚焦两到三个关键参数不要一圈一圈地调。参数调整方向场景num_leaves63 → 95数据量变大波形更复杂learning_rate0.05 → 0.03验证集MAE震荡不收敛n_estimators2000 → 3000早停轮数一直触发但未过拟合调参时每次只动一个参数用上一章的回溯脚本验证再决定是否保留。不要同时改三个参数否则翻车后你根本不知道是谁导致误差变大。在客流预测里调参带来的收益往往不如多做一个特征比如把“临近体育赛事的比赛日开赛时段”做成一个特殊事件标记对比赛场馆周边站的预测提升非常明显。第三个技巧是部署时的最小可靠结构。日常业务系统不建议在代码里直接跑预测脚本而是用定时任务每天早上拉取昨日ACC流水、重新训练一个短周期模型、生成当天各站点的时段预测表写入数据库或CSV供调度室看板读取。模型文件用joblib持久化训练和预测分开流程今天的预测不要让昨天的bug影响到。另外务必在预测表里附上“数据截止时间”和“模型训练时间”调度室看到昨天的模型预测今天上午的客流时会知道该打多少折扣。我现在的习惯是每次改完特征或参数先跑一遍回溯脚本并保留那天的误差快照等到下次改完再对比这比凭记忆判断有效得多。前期的滞后特征泄漏、节假日失灵这些坑都是靠这个习惯一条条排出来的。只要你也把数据清洗和避坑顺序放对位置这套基于ACC数据和Python的客流预测系统一定能从论文和demo变成调度室每天真正会打开的工具。希望帮到你。本文还有配套的精品资源点击获取