Python城市轨道交通数据可视化:从数据清洗到交互看板实战
简介这份资源是面向计算机相关专业在校学生、教师及企业员工的Python数据可视化课程大作业与毕业设计参考项目围绕中国城市轨道交通数据展开分析适合作为入门进阶练习或课程设计、初期项目立项的演示素材。压缩包共34个文件约3.43MB包含3个Python脚本、4个HTML可视化页面、20张PNG图表、2个CSV数据文件、2张JPG图片及1个SQLite数据库文件覆盖数据爬取、清洗、分析与图表呈现的完整链路。项目已通过功能验证可稳定运行内容涉及各城市地铁站点数量分布、线路数量饼图、换乘站点统计、大学数量与站点数量关系、地铁站命名用字与词云等分析维度并配有GUI界面截图与项目说明文档。目前已有2325人学习下载读者可据此快速理解可视化分析项目的目录组织与实现思路并在此基础上进行二次开发与拓展。1. 从一份 zip 说起城市轨道交通数据可视化到底在做什么很多同学拿到「Python数据可视化大作业基于Python的中国城市轨道交通数据可视化分析源码.zip」这类压缩包时第一反应是解压、找 main.py、pip install、python main.py然后对着满屏报错发呆。我当年也一样血泪经验告诉我轨道交通数据可视化不是「把数据丢进 matplotlib 画条线」这么简单它本质上是把线路、站点、客流、换乘这些带地理属性的结构化数据用 Python 清洗后转成能讲故事的图表。这份源码通常包含数据文件CSV/Excel/JSON、绘图脚本、可能还有 Flask 或 Streamlit 的展示层。它适合两类人一是课程设计需要交作业的学生二是想用真实城市数据练手 pandas pyecharts 的入门者。核心链路就四步拿到城市轨道数据、清洗成规整表、选对图表类型、输出可交互或静态图。下面我按这个链路拆开讲每一步都给能直接抄的代码和参数。2. 数据从哪来、长什么样轨道数据源与字段结构拆解2.1 常见数据来源与获取方式城市轨道交通数据不像电商数据那样随手可得常见来源有三类。第一类是各城市地铁官网或开放数据平台发布的线路站点表通常是 Excel 或 CSV字段包括线路名称、站点名称、站点序号、换乘线路。第二类是统计年鉴里的客运量数据按月度或年度给出各城市运营里程和客运量。第三类是地图服务商提供的 POI 数据能拿到站点经纬度但需要自己申请接口权限。我一般会先用一份公开的线路站点表打底再补经纬度。如果你拿到的源码里已经带了 data 文件夹先别急着跑脚本用下面这段代码把每个文件的结构摸清楚这一步能省掉后面 80% 的调试时间。import pandas as pd import os data_dir ./data for fname in os.listdir(data_dir): fpath os.path.join(data_dir, fname) if fname.endswith(.csv): df pd.read_csv(fpath, encodingutf-8) elif fname.endswith((.xlsx, .xls)): df pd.read_excel(fpath) else: continue print(f文件: {fname}) print(f形状: {df.shape}) print(f列名: {list(df.columns)}) print(df.head(3)) print(- * 50)这段代码遍历 data 目录按扩展名分别读取打印形状、列名和前 3 行。参数上注意 encoding国内数据常见 utf-8 和 gbk 两种读出来乱码就换 gbk 再试。形状里的行数就是站点或记录数列名决定你后面能画什么图。如果列名是英文缩写比如 line_name、station_name建议先重命名成中文后面写图表标题时不用再映射。2.2 字段清洗与标准化原始数据最烦人的是同一含义不同写法线路名有的写「1号线」有的写「地铁1号线」站点名带「站」和不带「站」混着来。不统一后面按线路分组统计就会裂成好几组。我通常写一个清洗函数把线路名和站点名都做一次规范化。下面这段是可直接复用的模板。import re def clean_line_name(name): if pd.isna(name): return name name str(name).strip() name re.sub(r地铁|轻轨|轨道, , name) name name.replace(线, 号线) if 号线 not in name else name return name.strip() def clean_station_name(name): if pd.isna(name): return name name str(name).strip() name re.sub(r\s, , name) if not name.endswith(站): name name 站 return name df[线路] df[线路].apply(clean_line_name) df[站点] df[站点].apply(clean_station_name)clean_line_name 先用正则去掉「地铁」「轻轨」这类前缀再统一「线」和「号线」的写法。clean_station_name 去掉所有空白字符并给没有「站」字的站点补上。参数上正则里的模式要根据你实际数据调整比如有的数据写「XX路站」就不能盲目补「站」。清洗完一定要用 df[线路].value_counts() 看一眼分组数量如果从 20 条线路变成 5 条说明清洗过度了得回退。2.3 经纬度补全与坐标系选择要画地图站点必须有经纬度。源码里如果没带可以用高德或百度的地理编码接口批量查但注意接口有频率限制一次别超过 50 个。拿到经纬度后坐标系要统一成 WGS84 或 GCJ02混用会导致站点偏移几百米。下面是一个补经纬度的思路代码实际调用需要替换成你自己的接口 key。import requests import time def get_lng_lat(station, city, key): url https://restapi.amap.com/v3/geocode/geo params {address: f{city}{station}, key: key} resp requests.get(url, paramsparams, timeout5) data resp.json() if data.get(geocodes): loc data[geocodes][0][location] lng, lat loc.split(,) return float(lng), float(lat) return None, None for idx, row in df.iterrows(): if pd.isna(row.get(经度)): lng, lat get_lng_lat(row[站点], row[城市], 你的key) df.at[idx, 经度] lng df.at[idx, 纬度] lat time.sleep(0.2)这段代码逐行检查经度是否为空为空才调接口避免重复请求。time.sleep(0.2) 是给接口留间隔防止被限流。参数上address 拼成「城市站点」能提高命中率key 要换成你自己申请的。返回的 location 是「经度,纬度」字符串注意拆分顺序别搞反。补完后把结果存一份 CSV下次直接读别每次都调接口。3. 用 pandas 做轨道数据聚合线路、站点、客流的三种统计口径3.1 按线路聚合站点数与换乘数数据清洗完第一步统计是每条线路有多少站、其中多少是换乘站。换乘站的判断依据是站点名在多条线路中出现。下面这段代码用 groupby 和 nunique 一次算出线路站点数和换乘站数。line_stats df.groupby(线路).agg( 站点数(站点, nunique), 换乘站数(站点, lambda x: (df[df[站点].isin(x)][线路].nunique() 1).sum()) ).reset_index() line_stats line_stats.sort_values(站点数, ascendingFalse) print(line_stats)groupby(线路) 把数据按线路分组agg 里站点数用 nunique 去重计数换乘站数用了一个 lambda先取出该线路的所有站点再在全表里找这些站点出现在几条线路中大于 1 的就是换乘站。参数上nunique 比 count 更准因为同一站点可能重复出现。排序后能一眼看出哪条线路最长。注意 lambda 里引用了外部 df数据量大时会慢可以先把站点到线路的映射建成字典再查。3.2 客流数据的月度透视如果你拿到的数据带月度客流用 pivot_table 做透视最方便。下面把「线路-月份-客流」三列转成以线路为行、月份为列的矩阵方便后面画热力图或堆叠柱状图。pivot pd.pivot_table( df_flow, values客流量, index线路, columns月份, aggfuncsum, fill_value0 ) pivot[全年合计] pivot.sum(axis1) pivot pivot.sort_values(全年合计, ascendingFalse) print(pivot.head(10))pivot_table 的 values 是数值列index 是行分组columns 是列分组aggfunc 用 sum 把同线路同月份的多条记录合并。fill_value0 把缺失月份补零避免后面画图出现断点。加一列全年合计再排序能快速找出客流最大的线路。参数上如果客流列是字符串带「万」字要先替换成数字再转 float否则 aggfunc 会报错。3.3 站点共现矩阵与换乘网络想分析哪些站点是核心换乘节点可以构建站点共现矩阵两个站点出现在同一线路的次数。下面用 pandas 的 crosstab 加矩阵乘法实现。import numpy as np station_line pd.crosstab(df[站点], df[线路]) co_matrix station_line.dot(station_line.T) np.fill_diagonal(co_matrix.values, 0) top_pairs co_matrix.stack().sort_values(ascendingFalse).head(20) print(top_pairs)crosstab 生成站点-线路的 0/1 矩阵dot 做矩阵乘法得到站点两两共现次数对角线是站点自身要置零。stack 把矩阵拉成 Series 再排序取前 20 对就是换乘关联最强的站点组合。参数上如果站点数超过几千这个矩阵会很大建议先按线路筛选再算。这个结果可以直接喂给网络图库画换乘关系图。4. 可视化落地pyecharts 与 matplotlib 的分工和参数调优4.1 为什么我优先选 pyecharts 做轨道图matplotlib 画静态图没问题但轨道数据天然带地理和交互需求pyecharts 的 Geo、Map、Graph 三种图型几乎覆盖所有场景。Geo 画站点散点Map 画城市客流热力Graph 画换乘网络。而且 pyecharts 输出 HTML作业提交时直接打开就能演示不用配环境。下面是一个用 Geo 画某城市轨道站点分布的最小示例。from pyecharts import options as opts from pyecharts.charts import Geo geo Geo(init_optsopts.InitOpts(width900px, height600px)) geo.add_schema(maptype北京) for _, row in df.iterrows(): geo.add_coordinate(row[站点], row[经度], row[纬度]) geo.add( 站点, [(row[站点], 1) for _, row in df.iterrows()], type_scatter, symbol_size6, colorblue ) geo.set_global_opts( title_optsopts.TitleOpts(title城市轨道交通站点分布), visualmap_optsopts.VisualMapOpts(is_showFalse) ) geo.render(station_map.html)add_schema 的 maptype 要和你城市匹配写错地图出不来。add_coordinate 逐个注册站点经纬度这一步必须在 add 之前。add 里数据格式是「站点名, 值」的列表type_ 选 scatter 画散点symbol_size 控制点大小。set_global_opts 里关掉 visualmap 是因为散点不需要颜色映射。render 输出 HTML浏览器打开即可。参数上如果站点太多点重叠把 symbol_size 调到 3 以下或者改用 heatmap 类型。4.2 matplotlib 画线路客流对比的四个必调参数有些作业要求交 PNG那就用 matplotlib。画多条线路的月度客流折线最容易翻车的是中文乱码、图例遮挡、横轴太密。下面这段代码把四个关键参数都设好了。import matplotlib.pyplot as plt import matplotlib matplotlib.rcParams[font.sans-serif] [SimHei] matplotlib.rcParams[axes.unicode_minus] False fig, ax plt.subplots(figsize(12, 6)) for line in pivot.index[:5]: ax.plot(pivot.columns[:-1], pivot.loc[line, pivot.columns[:-1]], markero, labelline) ax.set_xlabel(月份, fontsize12) ax.set_ylabel(客流量, fontsize12) ax.set_title(主要线路月度客流对比, fontsize14) ax.legend(locupper right, fontsize10) ax.tick_params(axisx, rotation45) plt.tight_layout() plt.savefig(flow_compare.png, dpi150) plt.show()font.sans-serif 设成 SimHei 解决中文乱码axes.unicode_minus 设 False 解决负号显示。figsize 的 12x6 适合横向展示月份。循环只取前 5 条线路避免图例爆炸。tick_params 的 rotation45 让月份标签斜着放解决横轴太密。tight_layout 自动调整边距savefig 的 dpi150 保证清晰度。参数上如果 SimHei 不可用换成 Microsoft YaHei 或 WenQuanYi Micro Hei。4.3 换乘网络图的数据准备与节点权重用 pyecharts 的 Graph 画换乘网络节点大小按换乘次数加权边粗细按共现次数加权。数据准备分两步先算每个站点的换乘线路数作为节点权重再用第 3.3 节的共现矩阵取前 N 对作为边。from pyecharts.charts import Graph nodes [] for station in station_line.index: weight station_line.loc[station].sum() nodes.append({ name: station, symbolSize: 8 weight * 4, value: int(weight) }) links [] for (s1, s2), cnt in top_pairs.items(): if cnt 0: links.append({source: s1, target: s2, value: int(cnt)}) graph Graph() graph.add( , nodes, links, repulsion2000, layoutforce, edge_labelopts.LabelOpts(is_showFalse), linestyle_optsopts.LineStyleOpts(width1, curve0.2) ) graph.set_global_opts(title_optsopts.TitleOpts(title换乘网络)) graph.render(transfer_network.html)nodes 里 symbolSize 用 8 加权重乘 4让换乘多的站点明显更大。links 只保留共现次数大于 0 的对。Graph 的 repulsion 控制节点斥力2000 适合几十个节点的图节点多了要调大。layout 选 force 是力导向布局curve 让边稍微弯曲避免重叠。参数上如果图太乱减少 links 数量或调大 repulsion。5. 避坑与排查轨道可视化里最容易翻车的五件事5.1 地图空白或站点全挤在一角现象pyecharts 的 Geo 图打开后地图不显示或者所有点堆在左下角。原因通常是 maptype 写错或者经纬度顺序反了。解决先确认 maptype 是「北京」「上海」这种城市名不是「北京市」再检查 add_coordinate 传的是「经度, 纬度」很多人习惯写纬度在前。用 print 打一个站点的经纬度和地图上实际位置对一下。5.2 中文标签变成方块现象matplotlib 图里标题和图例的中文全是方框。原因默认字体不含中文。解决在绘图前设置 rcParams 的 font.sans-serif并确保系统里有该字体。Linux 服务器上常见没有 SimHei装一个 fonts-wqy-microhei 包然后把字体名改成 WenQuanYi Micro Hei。改完记得重启 Python 进程rcParams 是全局的。5.3 groupby 后数据量对不上现象清洗后按线路分组发现线路数比预期少或者站点数明显偏小。原因清洗函数把不同线路误合并了比如把「1号线」和「11号线」都处理成了「1号线」。解决清洗函数里不要用简单的字符串替换改用正则精确匹配。跑完清洗先 value_counts 看分布和原始数据对比差异超过 10% 就回退检查。5.4 客流数据求和结果异常大现象pivot_table 算出来的客流量是几亿明显不对。原因原始数据里客流单位是「万人次」但数值列被当成字符串或者同一月份有多条重复记录被重复求和。解决先 df[客流量].dtype 看类型是 object 就用 astype(float) 转再去重 df.drop_duplicates(subset[线路, 月份]) 再透视。单位问题在图表标题里注明「万人次」别在数值上再乘。5.5 渲染 HTML 后交互卡顿现象站点超过 500 个时pyecharts 生成的 HTML 拖动卡顿。原因每个点都单独 add_coordinate 和 addDOM 节点太多。解决改用 add_coordinate_json 批量注册或者把散点图换成热力图减少节点数。另一个办法是分线路渲染多个小图而不是一张大图塞所有线路。参数上symbol_size 调小也能减轻渲染压力。6. 进阶技巧把静态源码改造成可交互的轨道数据看板如果你不满足于交一份静态图想把这份源码升级成能筛选线路、切换月份的看板最省力的路径是 Streamlit 加 pyecharts。Streamlit 负责交互控件pyecharts 负责出图两者结合不用写前端。下面是一个最小可运行看板的骨架。import streamlit as st import pandas as pd from pyecharts import options as opts from pyecharts.charts import Bar from streamlit_echarts import st_pyecharts df pd.read_csv(./data/flow.csv) line_options [全部] sorted(df[线路].unique().tolist()) selected_line st.sidebar.selectbox(选择线路, line_options) if selected_line 全部: plot_df df.groupby(月份)[客流量].sum().reset_index() else: plot_df df[df[线路] selected_line].groupby(月份)[客流量].sum().reset_index() bar Bar() bar.add_xaxis(plot_df[月份].tolist()) bar.add_yaxis(客流量, plot_df[客流量].tolist()) bar.set_global_opts( title_optsopts.TitleOpts(titlef{selected_line} 月度客流), xaxis_optsopts.AxisOpts(name月份), yaxis_optsopts.AxisOpts(name客流量) ) st_pyecharts(bar, height500px)这段代码用 st.sidebar.selectbox 做线路筛选选「全部」时按月份汇总所有线路选具体线路时只汇总该线路。Bar 图接收 x 轴和 y 轴数据set_global_opts 设标题和轴名。st_pyecharts 是 streamlit-echarts 包提供的渲染函数height 控制图高。参数上selectbox 的 options 列表要保证「全部」在第一位后面用 sorted 排序。运行命令是 streamlit run app.py浏览器自动打开。验证看板是否正常我一般做三个检查切换线路时图表数据是否跟着变、月份顺序是否按 1 到 12 排列、客流总量和原始数据对得上。如果切换后图不变多半是 plot_df 没重新计算检查 if 分支是否覆盖了所有情况。月份顺序乱的话在 groupby 后加 sort_values(月份)。这套改造的价值在于它把一份只能看的源码变成了能演示、能交互的作业答辩时老师随便点几下就能看出你理解了数据。我自己的习惯是每次拿到新数据先跑一遍第 2 章的探查代码再按第 3 章做聚合最后才动可视化。顺序反了后面全是返工。希望帮到你。本文还有配套的精品资源点击获取