☰
基于Python的轨道交通客流预测系统源码:毕业设计实战与LightGBM模型
2026/10/3 10:20:48 网站建设 项目流程

简介:这份资源是面向高校计算机相关专业学生的轨道交通客流预测系统完整源码包,适用于毕业设计、期末大作业与课程设计等场景,难度适中,评审得分达到98分,并经过助教老师审定,可直接运行。压缩包共1707个文件,约37.25MB,以808个TypeScript与790个JavaScript文件构成前端主体,另有23个Python文件承担预测算法与数据处理,辅以json、xml、html、vue等配置与页面文件,以及xls数据表、sqlite3数据库和pkl模型文件,整体结构清晰、模块划分明确。目前已有360人学习下载。读者可从中获得一套完整的客流预测实现方案,涵盖数据读取、模型训练、结果展示与前后端联调思路,便于快速理解项目架构、复用关键代码并完成自己的设计任务,也适合作为二次开发与功能扩展的参考基础。

1. 从一份轨道交通客流预测系统源码说起:它到底能解决什么问题

早高峰的换乘站,站台屏幕上的到站倒计时还没跳完,下一班车的车厢已经挤到门都关不上。调度员想知道:如果现在加开一列区间车,十五分钟后换乘通道的滞留人数会降多少?这个问题靠经验拍脑袋,误差很大;靠仿真软件,建模周期又太长。基于Python的轨道交通客流预测系统要做的,就是把历史刷卡数据、列车时刻表和天气日历这些零散信息,变成一个能在分钟级给出短时客流预测值的工具。它适合两类人:一类是正在做计算机毕业设计、需要一套能跑通、能讲清楚、能写进论文的系统;另一类是刚接触python、想找一个数据量适中、业务逻辑完整的项目练手的人。这份源码的价值不在于算法多前沿,而在于它把数据清洗、特征构造、模型训练和可视化界面串成了一条完整的链路,你拿到手之后能改、能调、能换成自己城市的数据。

2. 客流预测系统的技术选型:为什么是Python而不是别的

2.1 数据层选型:Pandas加SQLite的轻量组合

轨道交通客流数据的原始形态通常是AFC(自动售检票)系统导出的交易流水,字段包括卡号、进站时间、出站时间、进站站点、出站站点。单日数据量在几十万到几百万条之间,用MySQL当然可以,但毕业设计场景下,部署一个数据库服务会增加答辩演示的复杂度。我一般会建议用SQLite,单文件存储,拷到U盘就能带走,换台电脑照样跑。

import pandas as pd import sqlite3 # 读取AFC原始流水,假设是CSV格式 raw = pd.read_csv('afc_records.csv', parse_dates=['进站时间', '出站时间']) # 只保留需要的列,减少内存占用 raw = raw[['进站时间', '进站站点', '出站站点', '卡号']] # 按15分钟粒度聚合进站客流 raw['时间片'] = raw['进站时间'].dt.floor('15min') flow = raw.groupby(['进站站点', '时间片']).size().reset_index(name='进站人数') # 写入SQLite,方便后续反复读取 conn = sqlite3.connect('metro_flow.db') flow.to_sql('station_flow_15min', conn, if_exists='replace', index=False) conn.close()

这段代码做了三件事:解析时间字段、按15分钟切片聚合、落库。参数上唯一需要根据实际情况调整的是floor('15min')里的粒度,如果站点客流稀疏,可以放宽到30分钟;如果要做精细化调度,可以缩到5分钟,但数据稀疏性会明显上升,后面建模时要做平滑处理。parse_dates这一步不能省,否则时间字段是字符串,dt.floor会直接报错。

2.2 模型层选型:从ARIMA到LightGBM的取舍

短时客流预测的常见做法有三类:统计方法(ARIMA、SARIMA)、机器学习(随机森林、XGBoost、LightGBM)、深度学习(LSTM、GRU)。毕业设计的时间预算通常在两到三个月,深度学习调参周期长,训练一次要等很久,答辩时如果老师让你现场改个参数重新跑,LSTM可能来不及。LightGBM在这类表格数据上表现稳定,训练速度快,特征重要性还能直接画图写进论文。

import lightgbm as lgb from sklearn.model_selection import train_test_split from sklearn.metrics import mean_absolute_error # 构造特征:历史同期客流、前几个时间片客流、是否早晚高峰 flow['昨日同期'] = flow.groupby('进站站点')['进站人数'].shift(96) # 96个15分钟=24小时 flow['前1片'] = flow.groupby('进站站点')['进站人数'].shift(1) flow['前2片'] = flow.groupby('进站站点')['进站人数'].shift(2) flow['小时'] = flow['时间片'].dt.hour flow['是否高峰'] = flow['小时'].apply(lambda x: 1 if x in [7,8,9,17,18,19] else 0) # 去掉因shift产生的空值 flow = flow.dropna() features = ['昨日同期', '前1片', '前2片', '小时', '是否高峰'] X = flow[features] y = flow['进站人数'] X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, shuffle=False) model = lgb.LGBMRegressor(n_estimators=300, learning_rate=0.05, num_leaves=31) model.fit(X_train, y_train) pred = model.predict(X_test) print('MAE:', mean_absolute_error(y_test, pred))

这里有几个参数值得说清楚。shift(96)对应的是前一天同一时间片,前提是你的数据是连续的15分钟粒度,中间没有缺失整天。如果数据有断档,这个特征会错位,需要先做时间序列的补全。shuffle=False在时间序列任务里是必须的,否则测试集里混入了未来信息,评估结果会虚高。num_leaves=31是LightGBM的默认值,如果过拟合明显,可以降到15;如果欠拟合,可以加到63,但不要超过127,否则单棵树太复杂,训练时间会明显拉长。

2.3 展示层选型:Streamlit还是PyQt

源码里如果带一个可视化界面,答辩演示会顺畅很多。PyQt功能强,但布局代码写起来繁琐,改一个控件位置要调半天。Streamlit几行代码就能出一个带下拉框和折线图的页面,适合快速展示。

import streamlit as st import pandas as pd import matplotlib.pyplot as plt st.title('轨道交通客流预测展示') station = st.selectbox('选择站点', df['进站站点'].unique()) date = st.date_input('选择日期') subset = df[(df['进站站点'] == station) & (df['时间片'].dt.date == date)] fig, ax = plt.subplots() ax.plot(subset['时间片'], subset['进站人数'], label='实际') ax.plot(subset['时间片'], subset['预测人数'], label='预测') ax.legend() st.pyplot(fig)

st.selectbox的选项来自数据里的站点列表,这样换城市数据时不用改代码。st.date_input返回的是日期对象,和dt.date比较时要注意类型一致。如果源码里用的是PyQt,也不用推翻,把绘图部分换成matplotlib嵌入即可,核心逻辑不变。

3. 从零跑通这套源码:环境配置与数据替换的完整步骤

3.1 环境配置:避开版本冲突的三个关键点

python安装教程满天飞,但真正跑项目时卡住的地方往往不是安装本身,而是包版本冲突。这套源码依赖的库包括pandas、numpy、lightgbm、streamlit、matplotlib。我一般会建议用conda建一个独立环境,而不是在base环境里直接pip install。

conda create -n metro python=3.9 conda activate metro pip install pandas numpy lightgbm streamlit matplotlib scikit-learn

python版本选3.9而不是3.11,是因为lightgbm在某些3.11环境下编译会报错,3.9的兼容性最稳。如果不用conda,用venv也可以,但vscode python环境配置时要注意选对解释器路径,否则终端里装好了包,编辑器里还是提示找不到模块。在VSCode里按Ctrl+Shift+P,输入Python: Select Interpreter,选中刚才建的环境即可。

3.2 数据替换:把你自己的AFC数据接进去

源码里通常带一份示例数据,但答辩时用自己城市的数据更有说服力。替换数据时要注意字段名映射。不同城市的AFC系统字段名不一样,有的叫进站时间,有的叫刷卡时间,有的叫交易时间。

# 字段映射配置,换数据时只改这里 column_mapping = { '交易时间': '进站时间', '进站编号': '进站站点', '出站编号': '出站站点', '卡号': '卡号' } raw = pd.read_csv('your_city_afc.csv') raw = raw.rename(columns=column_mapping)

把映射关系单独抽成一个字典,而不是散落在代码各处,这样换数据时只改一个地方。如果原始数据里没有出站站点,只做进站客流预测也可以,把出站站点相关的列删掉即可,不影响核心流程。

3.3 模型训练与评估:怎么判断结果能不能写进论文

训练完之后,不能只看一个MAE数值就完事。论文里通常需要对比基线模型,证明你的方法有提升。最简单的基线是「昨日同期值」,也就是直接用前一天同一时间片的客流作为预测值。

# 基线模型:直接用昨日同期 baseline_pred = X_test['昨日同期'] baseline_mae = mean_absolute_error(y_test, baseline_pred) print('基线MAE:', baseline_mae) print('LightGBM MAE:', mean_absolute_error(y_test, pred)) print('提升幅度:', (baseline_mae - mean_absolute_error(y_test, pred)) / baseline_mae * 100, '%')

如果LightGBM的MAE比基线还高,说明特征构造有问题,最常见的原因是昨日同期这个特征本身太强,模型学不到额外信息。这时候可以加入天气特征(如果数据里有)、节假日标记、站点属性(换乘站/非换乘站)来增加区分度。提升幅度在10%到25%之间是比较合理的区间,超过30%要检查是否有数据泄漏。

4. 避坑与排查:跑这套源码时最容易翻车的五个地方

4.1 时间片聚合后出现大量零值

现象:按15分钟聚合后,很多站点在凌晨时段客流为0,模型训练时这些零值会拉低整体预测精度。

原因:轨道交通运营时间通常从早上6点到晚上11点,其余时段没有客流,但聚合时仍然生成了时间片。

解决:在聚合后过滤掉运营时间之外的数据,或者把运营时间作为特征让模型自己学。我一般会直接过滤,减少无效样本。

flow = flow[(flow['时间片'].dt.hour >= 6) & (flow['时间片'].dt.hour <= 23)]

4.2 LightGBM训练时报「特征名不一致」

现象:训练时用了features列表,预测时传入的DataFrame列顺序或列名不一致,直接报错。

原因:LightGBM在fit时会记录特征名,predict时如果列名对不上就拒绝执行。

解决:把特征列的顺序固定下来,预测时用X_test[features]而不是直接传整个DataFrame。这个坑在源码二次修改时特别常见,因为改着改着就多加了一列。

4.3 Streamlit页面刷新后图表消失

现象:每次调整下拉框,图表重新加载,但之前选的站点和日期被重置。

原因:Streamlit的交互控件默认不保持状态,每次交互都会重新运行整个脚本。

解决:用st.session_state保存用户选择,或者在控件里加key参数。最简单的做法是把数据加载部分用@st.cache_data装饰,避免重复读取数据库。

@st.cache_data def load_data(): conn = sqlite3.connect('metro_flow.db') df = pd.read_sql('SELECT * FROM station_flow_15min', conn) conn.close() return df

4.4 换城市数据后站点名乱码

现象:读取CSV时站点名显示为乱码,比如「车ç«」。

原因:CSV文件的编码不是UTF-8,可能是GBK或GB2312。

解决:读取时指定编码,pd.read_csv('file.csv', encoding='gbk')。如果还是乱码,用chardet检测一下实际编码。这个坑在毕业设计里很常见,因为很多城市的AFC导出默认用GBK。

4.5 模型在测试集上表现很好但实际预测偏差大

现象:MAE很低,但把模型部署到实际系统里,预测值和实际值差距明显。

原因:训练时用了随机划分或者shuffle=True,导致测试集里混入了未来信息。时间序列任务必须用时间顺序划分。

解决:确保train_test_split里shuffle=False,并且训练集的时间早于测试集。如果源码里默认是随机划分,一定要改过来,否则论文里的评估结果站不住脚。

5. 进阶技巧:用残差分析和滚动预测把系统做得更扎实

5.1 残差分析:找出模型在哪些时段系统性偏高或偏低

训练完模型后,不要只看一个MAE就结束。把残差(实际值减预测值)按小时画出来,能看出模型在哪些时段有系统性偏差。比如早高峰残差为正,说明模型低估了早高峰客流;晚高峰残差为负,说明高估了。

flow['残差'] = y_test - pred residual_by_hour = flow.groupby(flow['时间片'].dt.hour)['残差'].mean() import matplotlib.pyplot as plt residual_by_hour.plot(kind='bar') plt.xlabel('小时') plt.ylabel('平均残差') plt.title('分时段残差分布') plt.show()

如果发现早高峰残差持续为正,可以在特征里加入「是否早高峰」的交互项,或者对早高峰样本单独训练一个模型。这个分析写进论文里,比单纯报一个MAE有说服力得多。

5.2 滚动预测:用前一天的数据预测第二天

实际调度场景里,你不可能等到当天数据全部收集完再预测。更合理的做法是用前一天的数据训练,预测第二天的客流。这种滚动预测的评估方式更接近真实使用场景。

# 按天滚动:用第1天到第N天训练,预测第N+1天 dates = sorted(flow['时间片'].dt.date.unique()) results = [] for i in range(1, len(dates)): train = flow[flow['时间片'].dt.date < dates[i]] test = flow[flow['时间片'].dt.date == dates[i]] model = lgb.LGBMRegressor(n_estimators=300, learning_rate=0.05) model.fit(train[features], train['进站人数']) pred = model.predict(test[features]) mae = mean_absolute_error(test['进站人数'], pred) results.append({'日期': dates[i], 'MAE': mae}) result_df = pd.DataFrame(results) print(result_df.describe())

滚动预测的MAE通常会比单次划分高一些,因为每天的数据分布都有波动。如果滚动预测的MAE在可接受范围内,说明模型泛化能力不错。我一般会看MAE的均值和标准差,均值低且标准差小,才说明模型稳定。

5.3 一个容易被忽略的细节:站点相似性特征

轨道交通网络里,相邻站点的客流往往有相关性。换乘站的客流变化会传导到相邻站点。如果源码里没有用到这个信息,可以手动构造一个「相邻站点前一时段客流均值」特征。

# 假设有一个站点邻接表 adjacent = { '站点A': ['站点B', '站点C'], '站点B': ['站点A', '站点D'], # ... } def neighbor_flow(row, flow_df): neighbors = adjacent.get(row['进站站点'], []) if not neighbors: return 0 prev_time = row['时间片'] - pd.Timedelta(minutes=15) neighbor_data = flow_df[(flow_df['进站站点'].isin(neighbors)) & (flow_df['时间片'] == prev_time)] return neighbor_data['进站人数'].mean() if len(neighbor_data) > 0 else 0 flow['邻站前一时段均值'] = flow.apply(lambda r: neighbor_flow(r, flow), axis=1)

这个特征构造起来麻烦一点,但在换乘站密集的线路上,对预测精度的提升比较明显。如果时间紧张,可以先不做,把前面的基础特征调好再说。

5.4 论文里怎么描述这套系统

写论文时,不要只贴代码和结果。把「数据预处理→特征工程→模型训练→评估对比→可视化展示」这条链路画成一张流程图,每个环节写清楚输入输出和关键参数。模型对比部分至少要有两个基线:昨日同期和移动平均。如果LightGBM比两个基线都好,结论就站得住。最后加一节「系统局限性」,写清楚数据量、时间跨度、站点覆盖方面的不足,答辩时老师反而会觉得你思考全面。

我自己做这类项目时,最大的教训是:不要一上来就追求复杂模型。先把数据清洗和特征构造做扎实,用LightGBM跑出一个能解释的结果,再考虑要不要换深度学习。很多时候,特征工程带来的提升比换模型大得多。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询