☰
Python交通数据分析:从数据清洗到可视化的完整实践
2026/9/28 13:39:19 网站建设 项目流程

几十万条车辆轨迹数据摆在面前,你能从中看出什么?这是我做这个 Python 交通数据分析项目时最真实的开场。数据表里混着时间戳、车速、经纬度、设备编号,乱七八糟什么都有,但唯独没有现成的结论。我花了差不多两周时间,把采集、清洗、统计、可视化、简单部署整套流程全部跑通,核心工具就是 Python。这篇文章是完整复盘,重点讲清楚每个环节为什么这么做,以及我会避开哪些坑。适合正在学 Python 数据分析的入门者,也适合已经接触过一些数据工具、想快速搭一套交通数据报表的人。

Python 在交通数据分析这个领域有个很实在的优势:生态太全了。pandas 处理表格,numpy 做数值计算,matplotlib 和 plotly 画图,scikit-learn 做预测,folium 做地图可视化,一条链路全包。你不需要在 Excel、数据库、BI 工具之间来回折腾。我这次做的东西也不复杂,就是拿真实的路口流量、路段速度数据,算清楚几个关键指标,再用图表把“什么时候堵、哪里堵、是什么程度的堵”直观展示出来。下面我从项目整体设计开始,按实际开发顺序把每一步遇到的问题和解决办法都聊透。

1. 需求拆解与方案选型:这个项目到底要解决什么问题

1.1 交通数据分析的典型场景

很多人一想到交通数据分析就下意识觉得要做“人工智能预测交通流”,其实大部分实际需求远没有这么玄乎。拿我这次的场景来说,甲方给了一批路口监测数据,核心问题就三个:全天流量分布长什么样,高峰期出现在几点,哪几个方向最堵。这三个问题拆开后,本质是统计聚合加排序,连机器学习都不一定需要用上。

我梳理出交通数据分析里最常见的四类场景,你可以对号入座:

  • 交通流量统计:统计某个断面、路口、路段在单位时间内通过的车辆数,这是最基础的需求。常用指标有小时流量、日流量、车种比例。
  • 运行速度分析:利用浮动车数据或路侧设备数据,计算路段平均行程速度、平均行驶速度,用来判断交通拥堵程度。
  • 拥堵识别与排名:通过速度阈值或拥堵持续时间,把路段状态划分成畅通、缓行、拥堵、严重拥堵等级,再做排名。
  • 设施规划支撑:分析某片区历史流量变化趋势,为新建立交、调整信号配时提供数据依据。

对新手来说最容易犯的错,是一上来就钻进建模,想着搞个十层八层的深度学习。真到落地时你会发现,第一步永远是数据整理。数据不干净,模型再花哨也是空中楼阁。我建议先用最简单的分组聚合把业务指标跑出来,让数据先“说话”,再去评估是否真的需要预测模型。这样分析链路短、容易验证,给业务方看也直白。

1.2 为什么选 Python 而不是其他工具

先说结论:如果你的目标是快速交付一套可复用的交通数据分析应用,Python 是目前综合成本最低的选择。

我知道有人会提 R 语言,统计功能确实强,但工程化能力偏弱,写接口、做定时任务、对接数据库都比较别扭。也有人提 Power BI、Tableau 这类 BI 工具,拖拽确实爽,可一旦数据量大、逻辑复杂,交互式操作反而成了瓶颈,而且不好做自动化更新。Python 的优势在于“代码即文档”,分析过程可以完全复现。你改了参数重新跑一遍,结果差异在哪一眼就能看出来,这在交通这种对准确性敏感的场景里非常关键。

再说性能问题,很多人的顾虑是“Python 慢”。实际上在交通数据分析这个量级,大部分单表数据在几十万到几百万行,pandas 用向量化操作处理这类数据完全在几秒钟就能完成。真遇到上亿条轨迹需要做分布式,也有 PySpark 这样成熟方案可以切换。你不需要一开始就规划大数据平台,往往一台普通电脑加 Python 就够了,先把核心逻辑跑通更重要。

1.3 整体功能架构

我这个应用的整体数据流其实很清晰:数据接入 → 数据清洗 → 指标计算 → 可视化 → 结果输出。用代码结构表达的话,大致是这样的模块划分:

模块主要职责需要用到的库
数据接入层读取 CSV、数据库或 API 数据pandas, requests
数据清洗层处理缺失值、重复值、异常值pandas, numpy
指标计算层流量汇总、车速计算、拥堵指数pandas, numpy
可视化层图表与地图展示matplotlib, folium
服务输出层简单 Web 接口,供前端调用Flask

这个结构不是拍脑袋定的,而是按“每一层只干一件事”的原则拆的。比如数据清洗层我单独抽成函数,后面接 API 数据或者接爬虫数据时,输入格式变但只要逻辑不变就能复用。做项目最忌讳的是把所有代码堆在一个脚本里,改一个地方牵一发动全身,后续维护成本会高到你不想再打开它。

2. 数据从哪来、怎么洗干净:完整数据预处理流程

2.1 数据源的获取方式

交通数据的来源五花八门。我这次用到的数据来自三个方向,你可以根据自己的实际情况选择:

官方开放数据平台。很多城市会把交通流量、公交线路、出租车 GPS 轨迹数据脱敏后放到公共数据平台,格式一般有 CSV、JSON 或者提供 API 接口。这类数据质量相对高,字段完整,适合做项目起点。但缺点是指标口径经常变,你最好下载后第一时间把字段说明文档也存一份。

自有设备批量导出。如果你有条件接触路侧设备、信号机后台,导出来的通常是比较原始的报文记录。这类数据字段繁杂,量很大,但分析价值也最直接。我当时拿到的是每 30 秒一条的设备心跳记录,里面包含车牌识别结果、车道号、时间和速度信息。

爬虫采集公开页面数据。用 requests 写爬虫从网页获取堵车指数、路段拥堵排行这类公开数据,也可以。比如某些地图平台的公开接口,返回 JSON 格式数据,很规整。但要注意爬取频率、robots 协议和使用边界,别给人家服务器造成压力,更不能用采集的数据做违反规则的事情。

如果一时找不到合适数据,我建议你直接用代码生成一份模拟数据先练手。模拟数据不算“假练”,因为清洗和分析逻辑完全一致。你可以用随机数生成时间、车速、经纬度,再人为掺入缺失值、重复值,这样反而能更好地测试自己的清洗逻辑是否健壮。等逻辑调通,再换真实数据,基本能无缝切换。

2.2 第一步:读取数据并做基础探查

拿到数据文件后,我习惯先跑一段简短的探查代码,搞清楚表结构长什么样,而不是直接开始清洗。探查一般包含三个动作:看前几行、看列的信息、统计缺失情况。

import pandas as pd df = pd.read_csv("traffic_records.csv", parse_dates=["timestamp"]) print(df.head()) print(df.info()) print(df.isnull().sum())

这里的parse_dates很关键。交通数据的时间戳几乎都是字符串,不转成 datetime 类型,后面按小时、按天聚合就会非常痛苦。转换之后,pandas 会把时间列识别成专门的日期时间类型,可以直接.dt.hour、.dt.dayofweek这样提取时间特征。

info()可以看到每列的非空个数和数据类型,如果某列显示 object 但你心里预期是数值,那多半是里面混了脏数据,比如空字符串、字母单位这种。这时候不能强行转换,得先把脏值找出来处理掉。

探查完数据,我最大的感受是:真实的交通数据从来都不是干净的。你会碰到时间戳格式不统一、同一辆车同一个时刻被识别两次、车速出现负数、经纬度跑到海里等等问题。这些都是正常现象,也是分析流程里必须处理的环节。

2.3 清洗三件套:重复、缺失、异常

整个清洗过程中,我重点做了三类处理:

去重。交通数据里最容易出现重复记录,原因可能是设备重复上报或者数据合并时叠加。去重的逻辑要看场景。如果同一时刻同一设备同一辆车出现了两条一模一样的数据,直接删除多余记录。但如果只是部分字段重复,就要谨慎,比如同一辆车在多个断面被拍到,这不算严格意义上的重复,不能删。

df = df.drop_duplicates(subset=["device_id", "timestamp", "plate_no"])

这行代码的意思是:以设备号、时间、车牌三个字段作为联合判断依据,保留第一条。实际使用时要根据你的业务逻辑定义“什么算重复”,不能机械操作。

缺失值。缺失值的策略分两种。如果缺失比例很小,比如某列缺失不到 5%,而且该字段不是关键字段,可以直接删除缺失行。但对于时间序列里的数值字段,比如车速、流量,直接删除会造成序列断裂,更推荐用插值法。pandas 的interpolate()方法就很好用,它会根据缺失点前后值进行线性插值,对于短时间窗口的数据缺失,效果非常自然。

df["speed"] = df["speed"].interpolate(method="linear", limit_direction="both")

如果某个设备连续几小时不回传数据,插值就没有意义了,说明设备本身可能离线,这段数据应该从分析中剔除,否则会把一个“无数据”的时段误判成“零流量”时段,导致后面高峰识别完全偏掉。

异常值。交通数据里最常见的异常值就是不合理车速。速度小于 0,直接是数据错误;速度大于一个物理上限,比如超过 180 公里每小时,大部分场景下也是异常。我用的是极值过滤加业务规则两层方式:

df = df[(df["speed"] >= 1) & (df["speed"] <= 150)]

这里有个细节:过滤即删除,所以执行前最好先打印一下被过滤掉的数据量,做到心里有数。如果删掉的比例超过 10%,说明数据质量很差,或者你的阈值设置得不合理,要回头检查。

2.4 时间序列重采样与特征衍生

清洗后的数据是一秒一条还是三十秒一条,往往不统一。做流量分析时,我通常会统一重采样到固定时间粒度,比如 5 分钟或 15 分钟。这个动作相当于把散乱记录“规整化”,后面所有分析都能对齐。

df["time_bin"] = df["timestamp"].dt.floor("5min") flow = df.groupby(["time_bin", "direction"]).size().reset_index(name="count")

.dt.floor("5min")是个很常用的小技巧,它把每条记录自动落到最近的 5 分钟区间,然后我按区间分组统计车辆数,冲掉原始零散时间带来的噪声。

除了时间分箱,我还习惯额外生成几个特征列:小时、星期几、是否工作日、是否高峰时段。这些特征在后面做拥堵分析时,直接分组就能用,不用到时候再重复筛选:

df["hour"] = df["timestamp"].dt.hour df["dayofweek"] = df["timestamp"].dt.dayofweek df["is_workday"] = df["dayofweek"] < 5 df["is_peak"] = df["hour"].isin([7, 8, 9, 17, 18, 19])

这些衍生字段看着简单,但对业务方展示时非常直观。对方问“早高峰到底有多堵”,你直接按is_peak分组求平均速度,一句话就能解答。

3. 核心分析模块:流量、车速、拥堵指数怎么算

3.1 我使用的核心指标体系

数据清洗完,分析才有意义。我这次定义了三类核心指标,也是交通行业里最通用的三类指标:

流量。流量是指单位时间内通过某一断面的车辆数。最小时粒度通常取 5 分钟或 15 分钟,再聚合到小时。流量能回答“这条路忙不忙”,但没法回答“堵不堵”——有时候流量低但车速也非常慢,说明已经堵到车都进不来了。

平均车速。平均车速有两种算法,一种是简单的算术平均,把所有车速度加起来除以车辆数;另一种是更科学的时间平均速度,把每辆车通过某段路的行程时间加权平均。如果手里有原始车牌识别数据,我推荐按旅行时间算,因为更能反映驾驶人实际感受。

拥堵指数。拥堵指数我采用行业里常用的方法:把实际平均车速与自由流速度(畅通时的参考车速,比如 60 km/h)做比值,用一个百分数表达。值越大代表越接近自由流,越小代表越拥堵。

free_flow_speed = 60 df["congestion_ratio"] = df["avg_speed"] / free_flow_speed df["congestion_level"] = pd.cut( df["congestion_ratio"], bins=[0, 0.3, 0.5, 0.8, 1.0], labels=["严重拥堵", "拥堵", "缓行", "畅通"], )

这里用pd.cut把连续比例切成等级,四个档位对应不同颜色,后面可视化阶段直接映射成红色、橙色、黄色、绿色,展示效果非常清楚。阈值可以结合实际城市快速路情况调整,没有一个放之四海而皆准的标准。

3.2 早晚高峰识别实战

高峰识别是交通分析里最有“获得感”的一块。我把清洗后的数据按小时汇总流量,然后截取早上六点到晚上十点的曲线,用简单的pivot_table就能做出来:

flow_by_hour = df.pivot_table( index="hour", columns="direction", values="record_id", aggfunc="count", )

结果是一个表格,行为小时,列为方向,值为车流量。直接打印出来,哪几个小时数值明显高,早晚高峰一眼就出来了。想要更自动化,可以设置阈值:流量超过全天平均水平一定倍数的小时视为高峰。但实际业务中,人工看图确认往往更高效。

有一个细节要特别提醒:高峰识别一定要分方向。同一个路段,早高峰进城方向流量大,晚高峰出城方向流量大,混在一起统计会把结论完全盖住。所以分组聚合时,方向字段必须参与分组。

3.3 拥堵路段排名与区域分析

排名的思路也很直白。把每条路段在每天高峰时段内的平均车速求出来,再算拥堵指数,排序取倒数前十,就得到“最堵路段榜单”。

peak_df = df[df["is_peak"]] result = ( peak_df.groupby(["road_name", "direction"]) .agg(avg_speed=("speed", "mean"), volume=("record_id", "count")) .reset_index() ) result["congestion_ratio"] = result["avg_speed"] / free_flow_speed result = result.sort_values("congestion_ratio") print(result.head(10))

这里agg同时计算多个聚合字段,类似于 Excel 的多重透视。这条路平均速度是多少、流量多大,两条信息放同一个表里,分析效率很高。按拥堵指数升序排列后,最堵的十条路就在最上面。

区域分析则更偏空间维度。我把地图按网格切块,比如 500 米乘 500 米一个格子,把经纬度落进格子,统计每个格子高峰时段的车流速度。速度最低的格子就是拥堵热点,后续做热力地图时数据基础就来自于这个网格聚合。

3.4 做一个小型车速预测模型

虽然我前面说不要一上来就做预测,但当我拿到两周完整数据后,还是忍不住加了一个简单的预测模块,用的是 scikit-learn 里的随机森林回归。特征只用几个和时间、空间有关的字段:小时、星期几、是否工作日、方向编号。目标值是预测下一个小时该路段的平均车速。

from sklearn.ensemble import RandomForestRegressor from sklearn.model_selection import train_test_split features = ["hour", "dayofweek", "is_workday", "direction_code"] X = df[features] y = df["avg_speed"] X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) model = RandomForestRegressor(n_estimators=200, max_depth=10, random_state=42) model.fit(X_train, y_train) print("R2 score:", model.score(X_test, y_test))

这个模型的 R2 我实测在 0.75 左右。对于一个只用时间特征、不用上下游实时数据的模型来说,已经很有解释力了。拿不到 0.9 以上很正常,交通系统本身随机性强,天气、事故这些因素没进来。我建议不要为了刷指标强行加复杂模型,能用简单模型解释 70% 的规律,就值得先上线。

4. 让结果看得见:可视化与轻量落地

4.1 Matplotlib 绘制流量时间曲线

可视化这部分,我做的最多是画时间曲线。横轴是时间,纵轴是流量或平均车速,多方向多颜色叠加,直观展示全天变化。

import matplotlib.pyplot as plt plt.figure(figsize=(12, 5)) for direction, group in flow_by_hour.items(): plt.plot(group.index, group.values, label=direction) plt.xlabel("小时") plt.ylabel("车流量") plt.legend() plt.grid(alpha=0.4) plt.tight_layout() plt.savefig("hourly_flow.png", dpi=150)

经验之谈:出图时dpi一定要调高,至少 150。默认的 72 dpi 在屏幕上看着还行,放进报告里一放大就糊得没法看。另外中文显示是个老坑,Windows 下要设置字体,否则坐标轴会出现方块字。我一般用这样的方式临时设置:

plt.rcParams["font.sans-serif"] = ["SimHei"] plt.rcParams["axes.unicode_minus"] = False

4.2 Folium 制作拥堵热力地图

地图可视化我用 Folium 库,它底层调用 Leaflet,生成的是 HTML 文件,浏览器直接打开就能交互缩放。我先把网格聚合后的数据转成热力点,再用 HeatMap 插件叠加。

import folium from folium.plugins import HeatMap center = [lat_mean, lng_mean] map_obj = folium.Map(location=center, zoom_start=12) heat_data = df[["lat", "lng", "avg_speed"]].values.tolist() HeatMap(heat_data, radius=15, blur=10, min_opacity=0.4).add_to(map_obj) map_obj.save("traffic_heatmap.html")

生成 HTML 地图这个方案,最大的好处是不需要额外部署服务,直接把文件发给同事,双击打开就能用。我做项目汇报时经常先导出地图文件,再配合几张大图,效果比口头描述一百遍都强。如果数据量很大,热力点的数量建议做降采样,不然 HTML 文件会很大,浏览器打开卡得厉害。

4.3 用 Flask 搭一个最简数据查询接口

项目最后我顺手用 Flask 搭了一个极简查询接口,实现“输入路段名和日期范围,返回该路段平均车速和拥堵等级”的功能。接口本身不长,核心代码几十行就能写完。

from flask import Flask, jsonify, request app = Flask(__name__) @app.route("/api/congestion", methods=["GET"]) def get_congestion(): road = request.args.get("road") date = request.args.get("date") subset = df[(df["road_name"] == road) & (df["date"] == date)] avg_speed = subset["speed"].mean() level = "畅通" if avg_speed > 40 else "拥堵" return jsonify({"road": road, "date": date, "avg_speed": avg_speed, "level": level}) if __name__ == "__main__": app.run(host="0.0.0.0", port=8000)

这里host="0.0.0.0"表示允许局域网内其他机器访问,自己测试时用默认的 127.0.0.1 就够了。接口跑起来之后,前端或者报表工具通过 URL 直接调用,比如访问http://localhost:8000/api/congestion?road=人民路&date=2025-11-18,就能拿到 JSON 结果。整个应用就从“分析脚本”升级成了“小型分析服务”。

5. 实战中踩过的坑与排查清单

5.1 时间戳与时区的坑

交通数据里的时间戳,最容易出问题,我再强调一遍。第一次分析时,我直接对字符串时间排序、聚合,结果发现排序是字母序而不是时间序,天然少了一个小时。原因就是没转 datetime。

时区问题更隐蔽。设备端记录的是北京时间,但服务器存储用了 UTC,直接混用,导致我画出来的流量曲线整体偏移了 8 个小时,早高峰跑到了下午。排查半天才发现是时区没有统一。我的教训是:项目第一天就全局约定统一使用北京时间字符串,内部处理统一用 pandas datetime,到输出报表时再格式化成目标时区。时区问题一旦混入,后期找 bug 的成本极高。

5.2 内存与性能优化

几十万行的交通数据,pandas 处理起来其实毫无压力。但如果你做网格聚合,把经纬度、时间段全部笛卡尔积展开,数据量瞬间膨胀到几千万行,内存就开始吃紧。

我的解决套路有几个:读数据时用usecols只选取需要的列,别把没用的超大字符串列读进来;中间变量及时用del删除,或者用gc.collect()手动触发垃圾回收;如果连续做多次聚合,先把数据按关键字段排序,再用groupby,性能会比反复过滤高不少。实在顶不住,用dtype参数把数字列指定成"int32"或"float32",内存占用直接减半。这个优化技巧很土但真的很有效。

5.3 经纬度出界的排查

地图热力图上,我发现有几个高亮热点落在海里,明显不合理。排查后发现是设备上报的经纬度偶尔会出现零值或者错误值。这种异常我用一个简单的范围过滤就能拦住:

df = df[(df["lat"] > 20) & (df["lat"] < 50)] df = df[(df["lng"] > 100) & (df["lng"] < 125)]

按国内绝大多数城市的经纬度范围收紧区间,经验值直接写进去,比用“是否在海陆边界”的复杂判断更省事。这类过滤逻辑建议放在清洗模块里跟车速过滤并列,这样每次重跑数据都能自动生效。

5.4 环境配置问题

最后说一个容易被忽视的坑:Python 环境。很多同事在自己电脑上跑这些代码时,报错都出在环境上,而代码本身没问题。常见的有几个:Python 装到了带中文或空格的路径下,导致一些底层依赖编译失败;直接全局装包,版本冲突后项目跑不起来;用 VS Code 写代码但没选中正确解释器。

我的建议是,项目一开始就用python -m venv venv创建独立虚拟环境,然后pip install pandas matplotlib scikit-learn folium flask,所有依赖锁进一个 requirements.txt。别嫌这个步骤啰嗦,交通数据分析这种项目,数据量一大,依赖版本稍微不一致,结果都对不上,排查起来才是最麻烦的。虚拟环境能帮你把问题范围缩到最小。

这份代码之外,我更想分享的一点体会

项目做完之后,我复盘了一下,真正让我觉得有价值的不是画出了好看的图,也不是接口能跑通,而是我建立了一套“先清洗、再指标、后可视化”的分析习惯。交通数据的特点是脏、乱、但是量大、规律性强,只要前 70% 的数据整理功夫做扎实,后面出活非常快。我强烈建议你在自己电脑上把整套流程跑一遍,哪怕先用模拟数据。刚开始可能觉得清洗很枯燥,但等你拿到真实数据,发现清洗逻辑能直接复用的时候,你会回来感谢自己当初没有跳过这一步。如果做的时候碰到奇怪的问题,欢迎按我上面列的那些坑去排查,大概率你遇到的就是其中之一。

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

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

立即咨询