ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python出租车GPS轨迹分析:从数据清洗到交互式地图可视化

Python出租车GPS轨迹分析:从数据清洗到交互式地图可视化 简介这份资源面向具备一定Python基础、希望入门时空数据分析与可视化的学习者围绕出租车GPS轨迹这一典型场景解决轨迹数据清洗、栅格统计与地图呈现的实操问题。项目基于transbigdata第三方包展开包含上海与深圳两地的出租车GPS数据可完成数据清洗、栅格内出租车数量计算、OD路径分析以及一天中载客轨迹的提取并最终在地图上可视化呈现。压缩包共26个文件约13.13MB以15个py脚本和2个ipynb笔记本为核心辅以2个csv数据文件、2个json配置、1个png示意图及README、LICENSE等说明文件结构清晰、便于按模块复现。目前已有823人学习下载适合作为城市交通数据分析、GIS可视化方向的练手项目帮助读者掌握从原始GPS到轨迹可视化的完整分析链路与常用工具用法。1. 从一份出租车 GPS 流水说起这套 Python 轨迹分析资源到底能干什么手里拿到一份出租车 GPS 流水通常是几十万到上千万行、每行一个时间戳加经纬度直接丢进 Excel 会当场卡死。这份基于 Python 的出租车轨迹数据分析与可视化资源解决的就是这类「原始点位表 → 可读结论」的落地问题清洗漂移点、按车辆和时间切片、算速度与里程、抽上下客热点、最后落到地图和统计图上。它适合两类人——刚学完 pandas 想找个真实数据集练手的新手以及需要快速搭一套轨迹分析原型、不想从零写清洗逻辑的从业者。整套流程围绕 Python 数据分析与可视化展开不依赖重型大数据集群一台普通笔记本就能跑通全链路。2. 环境与数据准备把原始 GPS 点位变成能算的表2.1 依赖选型与安装轨迹分析的核心依赖其实就几个pandas 做表处理numpy 做数值运算geopandas 或 pyproj 处理坐标系matplotlib 和 folium 出图。常见做法是用 conda 建独立环境避免和系统里的 Python 打架。如果你还在纠结 python 安装教程里那种全局装包的方式建议直接上虚拟环境后面换项目不遭罪。# 创建独立环境指定 3.10 版本兼容性最稳 conda create -n taxi python3.10 -y conda activate taxi # 核心依赖一次装齐 pip install pandas numpy matplotlib folium pyproj shapely geopandas这里有个参数值得说geopandas 在 Windows 上直接 pip 装经常编译失败稳妥做法是先装pip install pipwin pipwin install geopandas或者干脆用 conda 装conda install -c conda-forge geopandas。folium 负责交互式地图纯前端渲染不需要额外起服务。2.2 数据字段与坐标系确认出租车轨迹原始数据一般长这样车辆 ID、时间戳、经度、纬度有的还带载客状态0 空车 / 1 载客和方向角。拿到数据第一件事不是急着画图而是确认坐标系。国内 GPS 原始点位大多是 WGS84但地图底图常用 GCJ02火星坐标两者直接叠加会偏移几百米这是新手最容易翻车的地方。字段含义常见类型处理要点taxi_id车辆唯一标识字符串去重、分组依据timestamp采集时间整数/字符串统一转 datetimelon经度浮点判断坐标系lat纬度浮点判断坐标系status载客状态0/1区分空载与载客import pandas as pd # 读取原始数据指定列名避免首行被当表头 df pd.read_csv( taxi_gps.csv, names[taxi_id, timestamp, lon, lat, status], header0 ) # 时间戳统一转换unit 按实际精度选 s 或 ms df[timestamp] pd.to_datetime(df[timestamp], units) # 快速体检看缺失和经纬度范围 print(df.isnull().sum()) print(df[[lon, lat]].describe())units这个参数要按数据实际情况改有的数据集是毫秒级时间戳写成 s 会得到 1970 年的错误时间。经纬度 describe 出来如果 lon 在 73~135、lat 在 3~53 之间基本是国内 WGS84 范围可以继续。3. 轨迹清洗与特征计算速度、里程、停留点怎么算才靠谱3.1 漂移点与异常速度过滤原始 GPS 一定有漂移车停着不动点位却在几十米内乱跳或者信号丢失后突然跳到一个离谱坐标。不清洗直接算速度会得到时速几百公里的鬼畜结果。常见做法是按车辆分组算相邻点之间的球面距离和时间差超过阈值就判定为异常。import numpy as np def haversine(lon1, lat1, lon2, lat2): 球面距离单位米 R 6371000 phi1, phi2 np.radians(lat1), np.radians(lat2) dphi np.radians(lat2 - lat1) dlambda np.radians(lon2 - lon1) a np.sin(dphi/2)**2 np.cos(phi1)*np.cos(phi2)*np.sin(dlambda/2)**2 return 2 * R * np.arcsin(np.sqrt(a)) # 按车辆分组计算相邻点距离和时间差 df df.sort_values([taxi_id, timestamp]) df[prev_lon] df.groupby(taxi_id)[lon].shift(1) df[prev_lat] df.groupby(taxi_id)[lat].shift(1) df[prev_time] df.groupby(taxi_id)[timestamp].shift(1) df[dist] haversine(df[lon], df[lat], df[prev_lon], df[prev_lat]) df[dt] (df[timestamp] - df[prev_time]).dt.total_seconds() # 速度 m/s过滤掉时间差为 0 和速度超 40m/s(约144km/h) 的点 df[speed] df[dist] / df[dt].replace(0, np.nan) df df[(df[speed] 40) | df[speed].isna()]shift(1)是分组内上移一行配合 groupby 保证不会把 A 车和 B 车的点连起来算距离。速度阈值 40m/s 是经验值市区出租车极少超过设太松过滤不掉漂移设太紧会误删高速路段。这一步做完数据量通常会掉 5%~15%属于正常损耗。3.2 里程累计与停留点识别清洗完就可以算每辆车的总里程和载客里程。里程就是把 dist 按车辆累加载客里程只累加 status1 的段。停留点识别稍微绕一点连续多个点速度接近 0 且位置变化小于阈值就判定为一次停留常用于找候客热点。# 总里程与载客里程单位换算成公里 total df.groupby(taxi_id)[dist].sum() / 1000 loaded df[df[status] 1].groupby(taxi_id)[dist].sum() / 1000 mileage pd.DataFrame({total_km: total, loaded_km: loaded}).fillna(0) mileage[load_rate] mileage[loaded_km] / mileage[total_km] # 停留点速度小于 0.5m/s 视为静止 df[is_stop] df[speed] 0.5 stops df[df[is_stop]].groupby(taxi_id).size() print(mileage.describe()) print(平均停留点数, stops.mean())load_rate是载客率反映车辆运营效率正常出租车白班在 0.5~0.7 之间。停留点这里只做了计数如果要落到地图上找热点还得对停留坐标做聚类后面可视化章节会讲。4. 可视化落地从静态统计图到交互式地图4.1 用 matplotlib 出统计图统计图解决的是「整体分布长什么样」。载客率分布、速度分布、每小时订单量这些用 matplotlib 几行就能出。注意中文字体要单独设否则标题全是方框这是血泪经验。import matplotlib.pyplot as plt # 中文字体Windows 用 SimHeiMac 用 Arial Unicode MS plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False fig, axes plt.subplots(1, 2, figsize(12, 4)) # 载客率分布 axes[0].hist(mileage[load_rate].dropna(), bins30, color#4C72B0) axes[0].set_title(车辆载客率分布) axes[0].set_xlabel(载客率) # 按小时统计订单量 df[hour] df[timestamp].dt.hour hourly df[df[status] 1].groupby(hour).size() axes[1].plot(hourly.index, hourly.values, markero, color#DD8452) axes[1].set_title(全天载客时段分布) axes[1].set_xlabel(小时) plt.tight_layout() plt.savefig(stats.png, dpi150)axes.unicode_minus False这行是解决负号显示成方块的很多人只设了字体没设这个坐标轴负号照样乱码。dpi150 出图清晰度够用再高文件就大了。4.2 用 folium 画交互式轨迹地图静态图看不出空间规律轨迹必须落到地图上。folium 基于 Leaflet生成的 HTML 可以直接浏览器打开缩放拖拽都流畅。把单车轨迹画成线把停留点画成散点热点一目了然。import folium from folium.plugins import HeatMap # 以数据均值作为地图中心 center [df[lat].mean(), df[lon].mean()] m folium.Map(locationcenter, zoom_start12, tilesOpenStreetMap) # 抽一辆车的轨迹画线 one df[df[taxi_id] df[taxi_id].iloc[0]].sort_values(timestamp) points list(zip(one[lat], one[lon])) folium.PolyLine(points, colorblue, weight2, opacity0.7).add_to(m) # 停留点热力图 stop_pts df[df[is_stop]][[lat, lon]].dropna().values.tolist() HeatMap(stop_pts[:5000], radius8).add_to(m) m.save(trajectory.html)HeatMap传入的点建议截断到几千个太多浏览器渲染会卡。radius控制热力半径市区数据 8 左右合适太大会糊成一片。这里用的是 OpenStreetMap 底图如果你的点位是 WGS84 就直接叠是 GCJ02 就得先转换否则整体偏移。5. 避坑与排查轨迹分析里最容易翻车的五件事5.1 坐标系不统一导致整体偏移现象轨迹线画在地图上整体偏离道路几百米形状却是对的。原因GPS 原始点是 WGS84底图用了 GCJ02两套坐标系存在非线性偏移。解决用 pyproj 或 coord-convert 库统一转换转换后再叠加别指望手动平移能对齐。5.2 时间戳单位搞错现象按小时统计出来全是 1970 年或者 8 点集中。原因pd.to_datetime的 unit 参数和数据实际精度不匹配毫秒当秒用。解决先看时间戳位数10 位是秒13 位是毫秒对应 unit 填 s 或 ms转换后打印几行确认年份合理。5.3 分组计算时跨车辆连点现象算出来的速度出现几百公里每小时。原因没按 taxi_id 分组就 shift把上一辆车最后一个点和下一辆车第一个点连起来算了距离。解决所有涉及相邻点的计算先sort_values再groupby(taxi_id)shift 必须在分组内做。5.4 内存爆掉现象读几百万行数据时进程被系统杀掉。原因pandas 默认按对象类型存字符串内存占用翻几倍。解决读入时用dtype指定列类型taxi_id 用 category经纬度用 float32能省一半以上内存实在太大就分块读chunksize。5.5 热力图卡死浏览器现象生成的 HTML 打开就卡拖不动。原因热力图点数太多前端渲染压力大。解决对停留点先做网格聚合或抽样控制传入点数在几千以内或者改用静态密度图替代交互热力图。6. 进阶技巧把轨迹切成 OD 对做流量分析基础流程跑通后真正有价值的是把轨迹抽象成 OD起终点对。做法是对每辆车按载客状态切段status 从 0 变 1 的点记为上车点 O从 1 变 0 的点记为下车点 D一段载客行程就是一个 OD 对。这一步做完就能做区域流量矩阵、高峰时段流向分析甚至喂给可视化大屏。# 标记状态变化点 df[status_change] df.groupby(taxi_id)[status].diff() # 上车点状态由 0 变 1 pickup df[df[status_change] 1][[taxi_id, timestamp, lon, lat]] # 下车点状态由 1 变 0 dropoff df[df[status_change] -1][[taxi_id, timestamp, lon, lat]] # 简单配对同车按时间顺序上车点后最近的下车点 od pd.merge_asof( pickup.sort_values(timestamp), dropoff.sort_values(timestamp), ontimestamp, bytaxi_id, suffixes(_o, _d) ) print(有效 OD 对数量, od.dropna().shape[0])merge_asof是按时间就近匹配比等值 join 更贴合实际——因为上下车时间戳不会完全相等。bytaxi_id保证只在同一辆车内匹配避免张冠李戴。配对完可以按经纬度网格聚合统计每个网格作为起点的流量这就是流量矩阵的雏形。有个细节要注意diff 出来的状态变化点如果数据里有缺失段可能一个上车点配不到下车点dropna 之后数量会少一些属于正常。我一般会先看配对成功率低于 80% 就回头查数据质量而不是硬算。从那以后我每次拿到新的轨迹数据都强制先跑一遍坐标系确认和时间戳体检这两步花五分钟能省掉后面两小时的返工。希望帮到你。本文还有配套的精品资源点击获取
返回列表