出租车GPS轨迹数据分析:用Python与transbigdata实现时空可视化

发布时间:2026/10/12 22:24:30
出租车GPS轨迹数据分析:用Python与transbigdata实现时空可视化 简介一套基于Python和transbigdata库的出租车轨迹数据可视化分析源码专门面向数据科学初学者、交通研究者及需要在Jupyter环境中处理GPS轨迹数据的开发者。资源将原始出租车GPS数据读取、清洗、网格化、OD分析和地图展示整合为一条可运行的完整流程帮助使用者快速掌握空间数据可视化分析方法。压缩包共26个文件以15个Python脚本和2个Jupyter笔记本为主分别承担数据处理、轨迹分析、可视化与交互展示另含2个CSV和2个JSON示例数据文件以及说明文档、许可证、图片等辅助资源整体大小约70.26MB目录结构清晰。目前已吸引471人浏览学习。借助该源码读者既能通过示例数据复现出租车热点区域、出行OD和轨迹路径等典型分析场景也可替换为自己的城市数据直接复用配合Jupyter环境的交互特性能极大降低搭建环境和编码调试的门槛适合学习与二次开发。1. 出租车 GPS 轨迹不只是画点transbigdata 如何把原始坐标变成可分析的时空数据很多人第一次拿到出租车轨迹数据第一反应就是直接把经纬度丢进 scatter 画一张散点图结果出来的是一坨密不透风的黑点什么规律都看不出来。这份基于 Python 和 transbigdata 的出租车轨迹可视化分析源码解决的就是这个问题它把上海、深圳两个城市的出租车 GPS 原始 CSV经过清洗、网格划分、OD 分析和轨迹重建最终输出能直接用于研判的热力图和轨迹图。适合交通数据分析师、城市规划研究者以及想用 Python 做时空数据可视化的从业者。它的核心价值不在于画图本身而在于把“数据能不能被分析模型识别”这件事前置处理好了。2. 数据准备与清洗从 CSV 到 transbigdata 能识别的 DataFrame2.1 先看懂两个城市的原始数据结构列名、坐标与采样频率拿到这份源码我建议你先别急着跑 Notebook先打开shanghai_taxi_gps.csv和shenzhen_taxi_gps.csv看一眼原始字段。出租车 GPS 数据最常见的字段是车牌号、时间戳、经度、纬度、载客状态、速度、方向角但不同城市的数据供应商给的列名完全不一样。transbigdata 这个库对 DataFrame 的列名是有隐含要求的它内部处理轨迹时默认按lng、lat、time这类列名去索引所以第一步往往不是分析而是把原始列名对齐。我一般会先做一次字段探查用 pandas 把两个 CSV 的列名、数据类型、缺失率打印出来确认哪些列是真正有用的。下面是这类数据的标准探查写法import pandas as pd df pd.read_csv(shanghai_taxi_gps.csv, nrows5000) print(df.columns.tolist()) print(df.dtypes) print(df.isnull().sum()) print(df.describe())这段代码干了三件事先看有哪些列再看每列类型最后看缺失情况和数值分布。nrows5000是防止原始文件过大导致内存暴涨先取 5000 行探路确认结构后再全量读取。describe()对于经纬度字段特别有用——如果经度最小值是 73 而不是 120 附近说明数据里混入了异常坐标后面必须过滤如果速度字段出现负数说明采集端有脏数据。2.2 preprocess.py 做了哪几步清洗去重、去异常、时间戳标准化源码里的preprocess.py是整套流程的起点它对应 transbigdata 在实际项目里最常被调用的一组操作清洗边界外点、剔除重复记录、过滤静止或漂移点。原始 GPS 数据里至少有四类脏数据同一秒内重复上报的轨迹点、经纬度在城市边界之外的野点、速度为 0 但位置在几十米范围内乱跳的漂移点、以及时间戳格式不统一的记录。transbigdata 提供了一个非常实用的函数叫clean_outofshape它需要你先准备好研究范围的边界。这份源码里有shanghai.json和sz.json这两个 JSON 就是上海和深圳的行政区划边界用 GeoJSON 格式存储。清洗逻辑是这样的import transbigdata as tbd import geopandas as gpd # 读取研究范围边界 shanghai_boundary gpd.read_file(shanghai.json) clean_df tbd.clean_outofshape(df, shanghai_boundary, col[lng, lat]) print(f清洗前: {len(df)} 条清洗后: {len(clean_df)} 条)clean_outofshape的作用是把落在上海行政区划外的 GPS 点直接剔除参数里col[lng, lat]指定了经纬度列名边界文件用 GeoPandas 读进来。这一步非常关键——出租车 GPS 数据里经常混入坐标漂移到几十公里外的记录如果不先按边界裁剪后面画热力图时会出现一片诡异的离岛热点。清洗后如果发现剔除比例超过 3%我一般会回头检查原始数据的坐标系是不是和边界文件不一致。2.3 taxigps.py 的加载套路把数据读进来并初步质检源码里的taxigps.py是出租车 GPS 数据的专属加载模块它做的事情比 pandas 直接read_csv多一层对经纬度做范围校验、对时间字段做解析、对载客状态做编码转换。实际项目中我发现不同城市的出租车数据对“是否载客”这个字段的编码完全不一样有的是 0/1有的是 1/2有的是布尔值直接拿去做 OD 分析会得出完全错误的结果。这里我给出一套通用的加载和质检模式你可以对照源码的taxigps.py看它是不是这么处理的import pandas as pd def load_taxi_gps(filepath): df pd.read_csv(filepath) # 统一列名原数据可能是 lng/lat 也可能是 lon/lat df.columns [c.lower() for c in df.columns] if lon in df.columns and lng not in df.columns: df df.rename(columns{lon: lng}) # 经纬度范围粗校验 df df[(df[lng] 73) (df[lng] 135)] df df[(df[lat] 18) (df[lat] 54)] # 去重同车同秒只保留一条 df df.drop_duplicates(subset[vehicle_id, time]) return df sh_df load_taxi_gps(shanghai_taxi_gps.csv) sz_df load_taxi_gps(shenzhen_taxi_gps.csv)这里有个细节值得注意drop_duplicates的subset我用了vehicle_id和time两列而不是对所有列去重。因为 GPS 数据里即使同一辆车同一秒上报两次两次记录的经纬度也可能有细微差别如果对所有列去重重复数据根本去不掉。去重之后上下车点的提取才有意义否则 OD 分析里会出现同一秒出发的两条一模一样的出行记录。3. 网格划分与研究范围用 grids_geohash 把城市切成可计算的单元3.1 为什么用 geohash 网格而不是直接画散点出租车 GPS 数据动辄几十万条直接把每个点渲染在地图上是灾难而且从散点上你看不出任何空间规律。transbigdata 的思路是把城市切分成规则的网格单元然后统计每个网格内的点数量、上下车次数、平均速度等指标再用网格热力图呈现。这份源码里的grids_geohash.py就是负责这件事的模块。选择 geohash 网格而不是普通矩形网格根源在于编码效率。geohash 把经纬度编码成一串字符串相同前缀的格子在地理上是相邻的这样在聚合统计时可以直接按字符串前缀分组比传统的矩形网格边界计算快得多。尤其当数据量达到百万级时矩形网格需要同时比较经纬度两个维度的边界而 geohash 只需要做一次字符串切片。源码里同时有grids.py和grids_geohash.py前者是通用网格逻辑后者是 geohash 专属优化实际调用时优先走后者。3.2 栅格大小与边界参数怎么定从 shanghai.json 和 shenzhen_taxi_gps.csv 出发网格大小的选择直接决定分析结论的可读性。网格太大比如 0.1 度约等于 11 公里整个上海就十几个格子热点区域糊成一片网格太小比如 0.001 度约等于 110 米格子数量上百万聚合计算慢而且每个格子里的点太少统计意义不大。我一般从 0.005 度也就是约 500 米的网格开始试先看整体分布再决定要不要加密。transbigdata 提供了grid_params函数来生成网格参数它需要你传入研究范围。源码里的shanghai.json就是用来确定这个范围的但有时 GeoJSON 边界太碎直接拿来做网格参数计算反而慢我一般会手动算一个外包矩形import transbigdata as tbd # 根据上海出租车数据实际分布确定研究范围 [lngmin, latmin, lngmax, latmax] bounds [120.85, 30.65, 122.00, 31.90] params tbd.grid_params( boundsbounds, cell_size0.005, methodgeohash ) print(params)bounds是研究范围的经纬度最小最大值cell_size0.005表示网格边长约 500 米methodgeohash指定用 geohash 编码。grid_params返回的参数是一个包含网格编号规则的字典后续tbd.grid、tbd.odgrid都要用到它。这里有个容易被忽略的点bounds必须完全覆盖数据范围否则边界外的点会被直接丢掉我见过有人把范围设小了结果陆家嘴的上下车热点整个消失。3.3 简单检查网格覆盖落在城市边界外的点怎么处理设定好网格参数后第一步不是直接聚合画图而是先检查网格覆盖率和边界外的点数。transbigdata 的clean_outofshape在第二章已经用过一次但那是按行政区划边界裁剪网格聚合前还需要确认研究矩形范围是否包住了所有有效点。如果bounds设置不当一部分合法点也会被误伤。我一般会做一个交叉验证把网格参数作用于整个数据集然后统计无法匹配网格编号的记录数import transbigdata as tbd # 为每个GPS点计算网格编号 df[LONCOL], df[LATCOL] tbd.grid(df[lng], df[lat], params) # 检查是否有无法匹配网格的点 invalid_mask df[LONCOL].isnull() | df[LATCOL].isnull() print(f无法匹配网格的点: {invalid_mask.sum()} 条占比 {invalid_mask.mean():.4%})如果占比超过 0.5%说明bounds范围偏小需要扩大边界重新计算grid_params。还有一种情况是原始数据里混入了坐标在海外或外省的点这些点经clean_outofshape后应该已经不在了如果这里还有大量点位落空要回头检查清洗步骤是不是漏跑了。这个检查虽然没有直接产出图表但它决定了后续所有聚合统计的完整性值得每次换城市数据时都跑一遍。4. OD 分析与轨迹重建从上下车点到路径还原4.1 odprocess.py提取出行起点终点构造 OD 矩阵OD 分析是出租车轨迹数据最有价值的产出之一它回答的问题是乘客从哪里上车、到哪里下车城市的职住空间结构和热门出行走廊长什么样。要构造 OD 矩阵首先得从原始 GPS 轨迹中识别出每一次独立的出行。出租车数据的载客状态字段就是干这个用的状态从 0 变 1 的那条记录是上车点从 1 变 0 的那条记录是下车点。源码里的odprocess.py处理的就是这个逻辑。实际写代码时最稳妥的方式是按车辆分组对时间排序然后比较相邻记录的载客状态差异。这里我给出一个实用的提取方法import pandas as pd def extract_od(df, statuscolstatus): df df.sort_values([vehicle_id, time]) # 标记载客状态变化 df[status_change] df.groupby(vehicle_id)[statuscol].diff() # 上车点状态从0变为1 pickup df[df[status_change] 1][[vehicle_id, lng, lat, time]] # 下车点状态从1变为0 dropoff df[df[status_change] -1][[vehicle_id, lng, lat, time]] # 按车辆和时间对齐上下车点 pickup pickup.rename(columns{lng: slng, lat: slat, time: stime}) dropoff dropoff.rename(columns{lng: elng, lat: elat, time: etime}) od pd.merge(pickup, dropoff, onvehicle_id) return od这个提取逻辑的核心是groupby diffdiff()计算每条记录与上一条记录的状态差值差值为 1 说明刚载客差值为 -1 说明刚下客。排序字段time必须是标准化后的时间戳不是字符串否则同一辆车的前后顺序会乱。pd.merge对齐上下车点后每条 OD 记录就包含起点经纬度、终点经纬度、上车时间、下车时间这就是后续 OD 矩阵和出行时长分析的基础。4.2 traj.py 与 ckdnearest.py轨迹点排序与最近邻匹配OD 分析告诉你出行从哪到哪但中间经过哪些路、车速如何需要轨迹重建。transbigdata 的traj.py负责把离散的 GPS 点按时间和车辆排序连成一条条完整的行驶轨迹。这里有技术上的坑GPS 点不是按车辆连续采集的多辆车的数据交叉混在一起必须严格按vehicle_id分组再按时间排序否则轨迹线会乱成一团。traj_process是 transbigdata 里处理这个问题的标准入口它会自动完成分组排序并且要求数据必须包含车辆 ID、时间戳和经纬度列。下面是标准的轨迹处理调用import transbigdata as tbd # 按时间排序并构建轨迹nan值用于分隔不同车辆的轨迹 traj tbd.traj_process( df, timecoltime, vehiclecolvehicle_id, loncollng, latcollat )traj_process返回的轨迹数据里不同车辆的轨迹之间会用空行分隔这样后面画图时每一段轨迹天然是一条独立折线。而ckdnearest.py这个模块解决的是另一个问题把 GPS 点匹配到最近的路网节点或网格中心。比如你要分析某一辆车的轨迹是否经过某个商圈直接判断经纬度距离是不准的因为 GPS 有漂移需要用ckdnearest基于 KD 树做空间最近邻搜索找到离每个轨迹点最近的道路节点再统计经过次数。这个方法在轨迹数据里比粗暴的经纬度范围判断稳得多。4.3 可视化呈现plotmap.py 出图与两个城市的热点对比轨迹和 OD 都算出来后才轮到真正出图的环节。源码里的plotmap.py是地图可视化模块transbigdata 的plotmap函数可以直接把 GeoDataFrame 或网格聚合结果渲染到底图上。下面是用网格热力图对比上海和深圳上下车热点的方法import matplotlib.pyplot as plt import transbigdata as tbd # 为OD数据添加网格编号 od[LONCOL], od[LATCOL] tbd.grid(od[slng], od[slat], params) # 聚合统计每个网格的上车次数 ODg tbd.odgrid(od, params) print(ODg.head()) # 绘制热力图 fig, ax plt.subplots(figsize(8, 8)) tbd.plotmap(ax, bounds, lgdFalse) ODg.plot(columncount, cmapOrRd, axax, alpha0.7) plt.show()tbd.odgrid会把 OD 数据按起点网格和终点网格聚合成一个 OD 矩阵表columncount指定用聚合后的频数列来映射颜色cmapOrRd是从橙色到红色的渐变。这张图出来后你能非常直观地看到城市核心区的上车热点集中在哪几个网格。上海和深圳两份数据放在一起对比时注意两个城市的bounds不同网格参数要各算各的不能用同一套否则上海的格子套到深圳的经纬度上边界完全错位。5. 避坑记录坐标系统一、时间字段和内存的四个实战翻车现场5.1 底图对不上同一份数据画出来跑到海里去现象用 matplotlib 出的图看起来没问题但叠加在线地图底图上时整个点群整体偏移了大约 300 到 500 米有些点直接落在黄浦江里。原因地图底图用的是 GCJ-02 坐标而原始 CSV 里的 GPS 坐标是 WGS-84 标准。国内绝大多数在线底图都做了坐标加密直接用 WGS-84 的经纬度叠上去就会系统性偏移。这是我踩过最多次的坑几乎每次换一个新的数据源都要重新确认一遍坐标系。解决确认数据坐标系后用坐标转换函数统一。transbigdata 里没有强制绑定的转换工具我一般用coord_transform这类封装把 WGS-84 转成 GCJ-02 再画图。判断依据很简单拿一个你知道准确位置的地标点比如上海人民广场看原始坐标和实际坐标的偏差量级偏差在几百米就是坐标系问题。5.2 时间字段解析失败字符串格式混用导致排序错乱现象df.sort_values(time)之后轨迹线还是乱跳同一辆车的轨迹点前后顺序完全不对有时候车辆从浦东瞬间“瞬移”到浦西。原因CSV 里的时间字段有两种格式一部分是2024-03-01 08:30:00另一部分是2024/03/01 08:30:00甚至还有不带日期的08:30:00。pandas 自动推断类型时遇到混合格式会把整列读成字符串字符串排序按字典序走导致时间顺序错乱。解决读取数据时强制指定时间解析格式或者先把所有分隔符统一替换成一种再转datetime。从那以后我每次都会在加载函数里加一行pd.to_datetime(df[time], formatmixed)并且用errorscoerce把解析失败的行打上缺失标记先处理缺失再排序坚决不让字符串格式混进排序流程。5.3 geohash 网格设太小内存直接爆掉现象cell_size从 0.005 改成 0.001 后grid_params计算没问题但执行tbd.grid时内存飙升到几个 GBNotebook 直接卡死。原因geohash 的精度和网格尺寸不是线性关系。cell_size0.001大约对应 100 米级别上海全市范围内生成的网格数量是 500 米网格的 25 倍存储空间和计算量指数级上升本地机器根本扛不住。解决先用 0.01 度的粗网格跑通全流程确认分析逻辑没问题后再局部加密而且只对热点区域做二次细粒度分析不全局加密。如果一定要全城细粒度至少先按行政区裁剪数据缩小处理范围。5.4 画图横坐标太密集时间轴刻度糊成一团现象画一天的上下车时间分布图时x 轴时间刻度从 0 点到 24 点全部挤在一起标签互相重叠图根本没法看。原因matplotlib 默认对时间序列的刻度间隔不够智能当数据量覆盖全天时它会尝试把所有整点都标出来标签一多就重叠。解决手动设置刻度间隔和格式让刻度密度匹配图的尺寸。代码很简单plt.xticks配合pd.date_range只保留每两小时一个刻度并且把标签旋转 45 度图面立刻清爽。这个坑虽然不大但很多人画第一张图就被糊掉的横坐标劝退了其实只是一行刻度设置的事。6. 进阶技巧把静态热力图升级成可交互验证的地图网格热力图和轨迹图能回答大部分空间分布问题但有一个硬伤静态图无法交互查询你看到一个热点网格想点开看这个格子到底有哪些上客点、对应的 OD 流到哪些区域静态图做不到。这时我一般会把分析结果导到 folium 上做交互验证这也是验证分析结论值不值得写进报告的最快方式。import folium # 取聚合后热度最高的前50个网格 top_grids ODg.nlargest(50, count) # 基于上海中心创建交互地图 m folium.Map(location[31.23, 121.47], zoom_start11) for _, row in top_grids.iterrows(): folium.CircleMarker( location[row[lat], row[lon]], radius8, popupf上车次数: {row[count]}, colorred, fillTrue ).add_to(m) m.save(shanghai_hotspots.html)交互地图的验证价值在于你可以在浏览器里放大缩小把热点网格和真实路网、商圈做对照。比如上海的热点网格如果恰好落在人民广场、陆家嘴、虹桥火车站附近那结论就和常识吻合如果热点出现在某个农田区域那大概率是数据里有网约车刷单或虚拟定位的脏数据需要回溯清洗。交互验证通过后还有一个值得做的技巧是轨迹抽稀。原始 GPS 轨迹点密度太高直接画线会很粗很糊我一般用 Douglas-Peucker 算法对每条轨迹做抽稀保留形状特征的同时把坐标点数量压缩到十分之一图面干净渲染速度也快。transbigdata 生态里做这个操作不复杂核心思路是对同一辆车的轨迹按距离阈值过滤中间点阈值设为 30 米左右比较合适。这份源码最值得学习的不是某一个图表有多漂亮而是它的模块划分方式预处理、网格、OD、轨迹、可视化各司其职换一个城市的数据只需要替换 CSV 和边界 JSON主流程完全复用。从那以后我每次拿到一份新的轨迹数据都会强制走一遍这个流程先确认坐标系、再清洗时间字段、然后检查网格覆盖率最后才画图每一步都有对应的校验输出。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询