大数据空间数据清洗指南:处理缺失值、异常值、坐标转换的10个实用技巧
想象一下:你要给外卖平台做“骑手配送效率分析”,拿到的数据是骑手的GPS轨迹——但其中10%的点没记录(缺失值),有几个点突然跳到了100公里外的郊区(异常值),还有的坐标是“WGS84”(国际标准),有的是“GCJ02”(中国加密标准)。如果直接用这些数据算“平均配送时间”,结果会像“用没洗的菜做饭”:要么淡(缺失值导致结果不准),要么苦(异常值干扰结论),要么根本没法吃(坐标错导致位置偏差)。
大数据空间数据清洗指南:处理缺失值、异常值、坐标转换的10个实用技巧
关键词:空间数据清洗、缺失值处理、异常值检测、坐标转换、GIS数据、大数据处理、实用技巧
摘要:空间数据是“带位置的数字记录”——小到外卖骑手的GPS轨迹,大到卫星遥感影像,都属于这类数据。但原始空间数据往往像“没洗过的菜”:有的“叶子缺了”(缺失值),有的“混了烂叶子”(异常值),有的“放错了篮子”(坐标系统不一致)。本文用生活类比+代码实战的方式,拆解空间数据清洗的3大核心问题(缺失、异常、坐标),给出10个能直接落地的实用技巧。读完你会明白:如何把“脏数据”变成“能下锅的菜”,让后续的分析(比如路线优化、城市规划)更靠谱。
背景介绍:为什么空间数据需要“洗澡”?
1. 目的和范围
想象一下:你要给外卖平台做“骑手配送效率分析”,拿到的数据是骑手的GPS轨迹——但其中10%的点没记录(缺失值),有几个点突然跳到了100公里外的郊区(异常值),还有的坐标是“WGS84”(国际标准),有的是“GCJ02”(中国加密标准)。如果直接用这些数据算“平均配送时间”,结果会像“用没洗的菜做饭”:要么淡(缺失值导致结果不准),要么苦(异常值干扰结论),要么根本没法吃(坐标错导致位置偏差)。
本文的核心目标是:用最低门槛的方法,解决空间数据最常见的3个问题——缺失值、异常值、坐标转换,让数据“干净到能直接用”。
2. 预期读者
- 刚接触空间数据的分析师/产品经理(比如要做共享单车调度、物流路径优化);
- 数据工程师(需要处理GPS、POI、卫星影像等数据);
- GIS爱好者(想把自己收集的坐标数据导入地图)。
3. 文档结构概述
本文像“做番茄炒蛋的步骤”:
- 先讲“准备工作”(术语表,搞懂空间数据的基本概念);
- 再讲“洗菜技巧”(10个核心清洗方法,对应缺失、异常、坐标3大问题);
- 最后“下锅实战”(用Python处理真实骑手GPS数据)。
4. 术语表:用“生活话”翻译专业词
核心术语定义
| 术语 | 生活类比 | 直白解释 |
|---|---|---|
| 空间数据 | 带地址的快递单 | 包含“位置信息”的数据(比如GPS点的经纬度、POI的坐标) |
| 缺失值 | 快递单漏写门牌号 | 数据中某条记录的“位置字段”为空(比如骑手某时刻没上传GPS) |
| 异常值 | 快递员突然出现在上海 | 明显不符合逻辑的位置记录(比如骑手1分钟内从北京到天津) |
| 坐标参考系(CRS) | 不同国家的尺子 | 给“数字位置”定规则的系统(比如中国用GCJ02,美国用WGS84) |
相关概念解释
- WGS84:“国际通用的尺子”——GPS卫星用的坐标系统,比如你用手机GPS定位的原始数据就是WGS84;
- GCJ02:“中国的加密尺子”——为了安全,中国地图把WGS84坐标做了偏移,比如高德、百度地图用的就是GCJ02;
- POI:“兴趣点”——比如餐厅、酒店的坐标(Point of Interest)。
缩略词列表
- GIS:地理信息系统(Geographic Information System,用来处理空间数据的软件);
- GPS:全球定位系统(Global Positioning System,用来获取空间数据的硬件);
- CRS:坐标参考系(Coordinate Reference System)。
核心概念:空间数据清洗的“三大脏点”
故事引入:外卖骑手的“数据麻烦”
小A是外卖平台的数据分析师,今天要分析“骑手李师傅的配送效率”。他拿到李师傅的GPS数据后,发现三个问题:
- 缺了点:12:05到12:10之间,李师傅的GPS没记录(可能手机没信号);
- 错了点:12:30的坐标显示李师傅在“北京天安门”,但他当时应该在“朝阳区国贸”(明显异常);
- 乱了点:有的坐标是WGS84(手机GPS),有的是GCJ02(高德地图),导入地图后位置差了100米。
如果不解决这些问题,小A算出的“李师傅平均配送时间”会比实际慢20%——因为缺失值导致路线断了,异常值拉长了总距离,坐标错导致位置不准。
核心概念解释:像给小学生讲“洗菜”
空间数据清洗的本质,就是解决“三类脏东西”:
1. 缺失值:“菜叶子缺了一块”
缺失值是空间数据最常见的问题——比如GPS信号弱、设备故障、用户没开定位。就像你买的青菜缺了一片叶子,直接炒会“不好看”,更会让“菜量算错”(比如分析路线长度时,缺失点会导致路线变短)。
生活例子:你记录“每天上学的路线”,但某天忘带手机,中间300米没记录——这300米的位置就是“缺失值”。
2. 异常值:“菜里混了烂叶子”
异常值是“明显不符合逻辑”的位置记录——比如骑手速度超过120km/h(比汽车还快),或者POI坐标在海里(不可能有餐厅)。就像你买的青菜里混了片烂叶子,不挑出来会“吃坏肚子”(比如分析配送时间时,异常值会让平均时间变高)。
生活例子:你记录“上学的时间”,但某天写了“1分钟到学校”(实际要10分钟)——这就是“异常值”。
3. 坐标转换:“把英尺换成厘米”
不同的地图用不同的“坐标尺子”——比如手机GPS是WGS84,高德地图是GCJ02,百度地图是BD09。如果不把这些坐标“转成同一种尺子”,导入地图后位置会偏移(比如WGS84的点在高德地图上会偏到马路对面)。
生活例子:你用“英尺”量身高是5英尺8英寸,要转换成“厘米”才知道是173cm——坐标转换就是做这件事。
核心概念的关系:“洗菜的顺序”
空间数据清洗的顺序不能乱,就像“洗菜要先摘烂叶子,再洗干净,最后切”:
- 先处理缺失值:补全缺的点,让数据“连续”;
- 再处理异常值:删掉或修正错的点,让数据“合理”;
- 最后做坐标转换:把所有点转成同一种坐标,让数据“统一”。
类比:做番茄炒蛋时,要先“挑烂番茄”(异常值),再“补买缺的番茄”(缺失值),最后“把番茄切成小块”(坐标转换)——顺序错了,菜就做不好。
核心流程的文本示意图
原始空间数据 → 缺失值检测 → 缺失值处理 → 异常值检测 → 异常值处理 → 坐标转换 → 干净空间数据
Mermaid 流程图:空间数据清洗的“流水线”
核心技巧:10个“拿来就能用”的清洗方法
接下来是本文的“干货核心”——针对缺失值、异常值、坐标转换,各给出3-4个实用技巧,每个技巧包括问题场景、解决方法、代码示例(用Python,最常用的空间数据处理语言)。
一、缺失值处理:补全“缺了的叶子”(3个技巧)
缺失值的处理原则是:能补则补,不能补则删——但补的时候要“合理”,不能乱填。
技巧1:时间序列插值——用“前后点”补中间缺的
适用场景:GPS轨迹数据(时间连续,比如每隔10秒记录一次)。
原理:假设骑手的位置是“连续移动”的,中间缺的点可以用“前后点的平均位置”补。
生活类比:你上学路上忘记录位置,中间300米可以用“家的位置”和“学校门口的位置”中间的点补。
代码示例(用Python的pandas插值):
import pandas as pd
# 加载骑手GPS数据(包含time、longitude、latitude字段)
data = pd.read_csv("rider_gps.csv")
# 1. 把time转成 datetime 类型(方便计算时间差)
data["time"] = pd.to_datetime(data["time"])
# 2. 按时间排序(确保数据是连续的)
data = data.sort_values(by="time")
# 3. 用线性插值补缺失值(linear:线性,即前后点的平均)
data["longitude"] = data["longitude"].interpolate(method="linear")
data["latitude"] = data["latitude"].interpolate(method="linear")
# 结果:缺失的经纬度会被补全,比如12:05到12:10的点会用12:05和12:10的平均位置补
技巧2:空间邻域填充——用“周围点”补缺的
适用场景:POI数据(比如餐厅、酒店的坐标,周围点的类型相似)。
原理:如果某个POI的坐标缺失,可以用“周围500米内的POI坐标”的平均值补。
生活类比:你不知道“张三的家在哪”,但知道他邻居的家在“1单元301”,可以猜他在“1单元302”。
代码示例(用geopandas找邻域):
import geopandas as gpd
from shapely.geometry import Point
# 加载POI数据(包含name、longitude、latitude、type字段)
poi_data = pd.read_csv("poi.csv")
# 1. 转成GeoDataFrame(geopandas的核心数据结构,用来处理空间数据)
geometry = [Point(xy) for xy in zip(poi_data["longitude"], poi_data["latitude"])]
gdf = gpd.GeoDataFrame(poi_data, geometry=geometry, crs="EPSG:4326") # EPSG:4326=WGS84
# 2. 找每个缺失值点的“500米邻域”
missing_points = gdf[gdf["longitude"].isnull()] # 找出缺失经纬度的点
for idx, row in missing_points.iterrows():
# 画一个500米的圆(buffer:缓冲区,单位是米,需要转成UTM坐标)
buffer = row.geometry.buffer(500) # 注意:需要先转成UTM(平面坐标)才能用米,这里简化
# 找圆内的所有POI
nearby_poi = gdf[gdf.geometry.within(buffer)]
# 用邻域POI的平均经纬度补缺失值
if not nearby_poi.empty:
gdf.loc[idx, "longitude"] = nearby_poi["longitude"].mean()
gdf.loc[idx, "latitude"] = nearby_poi["latitude"].mean()
# 结果:缺失的POI坐标会被周围点的平均值补全
技巧3:轨迹分段删除——实在补不了就删掉
适用场景:缺失值太多(比如连续10分钟没记录),无法用插值补。
原理:如果一段轨迹缺失超过“合理时间”(比如骑手不可能10分钟不动),就把这段轨迹删掉,避免影响整体分析。
生活类比:你买的青菜缺了一半叶子,没法补,就直接扔掉。
代码示例:
# 计算相邻点的时间差(秒)
data["time_diff"] = data["time"].diff().dt.total_seconds()
# 设定阈值:超过300秒(5分钟)的缺失就删掉
threshold = 300
# 找出时间差超过阈值的点的索引
split_indices = data[data["time_diff"] > threshold].index
# 按split_indices分割轨迹,删掉缺失太多的段
clean_data = []
start = 0
for idx in split_indices:
segment = data[start:idx]
# 只保留长度超过5个点的段(避免太短的轨迹)
if len(segment) > 5:
clean_data.append(segment)
start = idx + 1
# 合并干净的轨迹
clean_data = pd.concat(clean_data)
二、异常值处理:挑出“烂叶子”(4个技巧)
异常值的处理原则是:先分析原因,再决定删还是改——比如设备故障导致的异常值直接删,数据录入错误导致的可以改。
技巧4:速度阈值过滤——“骑手不可能比汽车快”
适用场景:GPS轨迹数据(速度是判断异常的核心指标)。
原理:计算相邻两个GPS点的“移动速度”,超过“合理阈值”(比如市区骑手速度不超过30km/h=8.33m/s)的点就是异常值。
生活类比:你跑步的速度不可能超过100米/秒(比高铁还快),所以记录里的“100米/秒”就是异常值。
代码示例:
import numpy as np
# 1. 计算相邻点的时间差(秒)
data["time_diff"] = data["time"].diff().dt.total_seconds()
# 2. 计算相邻点的距离(米)——用“经纬度转距离”的公式
# 经度每度约111319米,纬度每度约111319*cos(lat)米(因为纬度越高,经线越密)
data["delta_lon"] = data["longitude"].diff() * 111319
data["delta_lat"] = data["latitude"].diff() * 111319 * np.cos(np.radians(data["latitude"]))
# 3. 计算速度(米/秒):距离/时间
data["speed"] = np.sqrt(data["delta_lon"]**2 + data["delta_lat"]**2) / data["time_diff"]
# 4. 过滤速度超过30km/h(8.33m/s)的点
speed_threshold = 30 * 1000 / 3600 # 30km/h转成m/s
clean_data = data[data["speed"] <= speed_threshold]
# 结果:速度超过30km/h的点会被删掉
技巧5:空间聚类检测——“一堆点里的‘异类’”
适用场景:POI数据或轨迹数据(比如一堆餐厅都在市区,突然有个在郊区)。
原理:用聚类算法(比如DBSCAN)把点分成“簇”,离所有簇都远的点就是异常值。
生活类比:你班级里的同学都住在“朝阳区”,只有小明住在“延庆区”——小明就是“异类”(异常值)。
代码示例(用scikit-learn的DBSCAN):
from sklearn.cluster import DBSCAN
import numpy as np
# 1. 提取经纬度数据(转为numpy数组)
coords = data[["longitude", "latitude"]].values
# 2. 用DBSCAN聚类(eps:邻域半径,min_samples:形成簇的最小点数)
# 注意:DBSCAN的eps单位是“度”,需要转成米(比如eps=0.005度≈500米)
db = DBSCAN(eps=0.005, min_samples=5, metric="haversine").fit(np.radians(coords))
labels = db.labels_ # 每个点的簇标签,-1表示异常值
# 3. 过滤异常值(标签为-1的点)
clean_data = data[labels != -1]
# 结果:离所有簇都远的点会被删掉
技巧6:边界框过滤——“点不能在海里”
适用场景:POI数据(比如餐厅的坐标不能在海里或沙漠里)。
原理:设定一个“合理边界框”(比如北京市的经纬度范围: longitude 115.7-117.4,latitude 39.4-41.6),不在这个框里的点就是异常值。
生活类比:你家在“北京市朝阳区”,所以快递地址写“北京市海淀区”是合理的,但写“海南省三亚市”就是异常值。
代码示例:
# 设定北京市的边界框
beijing_bbox = {
"min_lon": 115.7,
"max_lon": 117.4,
"min_lat": 39.4,
"max_lat": 41.6
}
# 过滤不在边界框内的点
clean_data = data[
(data["longitude"] >= beijing_bbox["min_lon"]) &
(data["longitude"] <= beijing_bbox["max_lon"]) &
(data["latitude"] >= beijing_bbox["min_lat"]) &
(data["latitude"] <= beijing_bbox["max_lat"])
]
# 结果:不在北京范围内的点会被删掉
技巧7:统计模型检测——“用数学找‘ outliers ’”
适用场景:所有空间数据(比如POI的坐标、轨迹的速度)。
原理:用统计方法(比如Z-score、箱线图)找出“远离平均值”的点——Z-score超过3的点就是异常值(因为99.7%的数据在平均值±3倍标准差内)。
生活类比:你班级同学的数学成绩平均80分,标准差5分,小明考了100分——Z-score=(100-80)/5=4,超过3,是异常值。
代码示例(Z-score检测速度异常):
from scipy.stats import zscore
# 计算速度的Z-score
data["speed_z"] = zscore(data["speed"].dropna())
# 过滤Z-score超过3的点
clean_data = data[abs(data["speed_z"]) <= 3]
# 结果:速度远高于或低于平均值的点会被删掉
三、坐标转换:“把尺子统一”(3个技巧)
坐标转换的核心是:先明确“原坐标”和“目标坐标”,再用工具转——比如把WGS84转成GCJ02,或把GCJ02转成BD09(百度地图)。
技巧8:用Proj4库转“标准坐标”
适用场景:转“国际标准坐标”(比如WGS84转UTM,或UTM转WGS84)。
原理:Proj4是“坐标转换的瑞士军刀”,支持几乎所有坐标系统的转换。
生活类比:用“计算器”把英尺转成厘米——Proj4就是这个“计算器”。
代码示例(WGS84转UTM):
import pyproj # Proj4的Python库
# 1. 定义原坐标和目标坐标的Proj4字符串
# WGS84(EPSG:4326):"+proj=longlat +datum=WGS84 +no_defs"
# UTM Zone 50N(北京的UTM区):"+proj=utm +zone=50 +datum=WGS84 +units=m +no_defs"
wgs84 = pyproj.Proj("+proj=longlat +datum=WGS84 +no_defs")
utm50n = pyproj.Proj("+proj=utm +zone=50 +datum=WGS84 +units=m +no_defs")
# 2. 转换坐标(经度lon,纬度lat)
lon = 116.403874 # 北京天安门的WGS84经度
lat = 39.914885 # 北京天安门的WGS84纬度
utm_x, utm_y = pyproj.transform(wgs84, utm50n, lon, lat)
# 结果:utm_x≈395200,utm_y≈4428000(单位是米)
print(f"UTM坐标:X={utm_x:.2f}米,Y={utm_y:.2f}米")
技巧9:处理“中国特色”偏移——GCJ02与WGS84互转
适用场景:处理中国地图数据(比如高德、百度地图的坐标转GPS原始坐标)。
原理:中国的GCJ02坐标是在WGS84基础上做了“非线性偏移”(加密),需要用特定的算法转回去(比如“火星坐标转换算法”)。
生活类比:你买了个“加密的盒子”,需要用“特定钥匙”才能打开——火星算法就是这个“钥匙”。
代码示例(GCJ02转WGS84):
import numpy as np
def gcj02_to_wgs84(lon, lat):
"""
将GCJ02坐标(高德、腾讯)转换为WGS84坐标(GPS原始)
:param lon: GCJ02经度
:param lat: GCJ02纬度
:return: WGS84经度、纬度
"""
a = 6378137.0 # 地球半径(米)
ee = 0.00669342162296594323 # 偏心率平方
# 先判断是否在中国境外(境外不需要转)
if out_of_china(lon, lat):
return (lon, lat)
# 计算偏移量
dLat = transformLat(lon - 105.0, lat - 35.0)
dLon = transformLon(lon - 105.0, lat - 35.0)
radLat = lat / 180.0 * np.pi
magic = np.sin(radLat)
magic = 1 - ee * magic * magic
sqrtMagic = np.sqrt(magic)
dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * np.pi)
dLon = (dLon * 180.0) / (a / sqrtMagic * np.cos(radLat) * np.pi)
# 减去偏移量得到WGS84坐标
wgsLon = lon - dLon
wgsLat = lat - dLat
return (wgsLon, wgsLat)
def transformLat(x, y):
"""计算纬度偏移量"""
ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * np.sqrt(abs(x))
ret += (20.0 * np.sin(6.0 * x * np.pi) + 20.0 * np.sin(2.0 * x * np.pi)) * 2.0 / 3.0
ret += (20.0 * np.sin(y * np.pi) + 40.0 * np.sin(y / 3.0 * np.pi)) * 2.0 / 3.0
ret += (160.0 * np.sin(y / 12.0 * np.pi) + 320.0 * np.sin(y * np.pi / 30.0)) * 2.0 / 3.0
return ret
def transformLon(x, y):
"""计算经度偏移量"""
ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * np.sqrt(abs(x))
ret += (20.0 * np.sin(6.0 * x * np.pi) + 20.0 * np.sin(2.0 * x * np.pi)) * 2.0 / 3.0
ret += (20.0 * np.sin(x * np.pi) + 40.0 * np.sin(x / 3.0 * np.pi)) * 2.0 / 3.0
ret += (150.0 * np.sin(x / 12.0 * np.pi) + 300.0 * np.sin(x * np.pi / 30.0)) * 2.0 / 3.0
return ret
def out_of_china(lon, lat):
"""判断坐标是否在中国境外"""
return (lon < 72.004 or lon > 137.8347) or (lat < 0.8293 or lat > 55.8271)
# 示例:高德地图的北京天安门GCJ02坐标转WGS84
gcj_lon = 116.403874 # 高德的经度
gcj_lat = 39.914885 # 高德的纬度
wgs_lon, wgs_lat = gcj02_to_wgs84(gcj_lon, gcj_lat)
print(f"WGS84经度:{wgs_lon:.6f}") # 输出≈116.400000(实际会有小偏移)
print(f"WGS84纬度:{wgs_lat:.6f}") # 输出≈39.910000
技巧10:批量转换——用脚本处理“百万条数据”
适用场景:处理大数据量(比如100万条GPS轨迹数据)。
原理:用pandas的apply函数或numpy的向量化运算,批量转换坐标,比循环快100倍。
生活类比:你要把1000张照片转成“JPG格式”,用“批量转换工具”比一张一张转快得多。
代码示例(批量转GCJ02到WGS84):
import pandas as pd
# 加载100万条GPS数据
data = pd.read_csv("million_gps.csv")
# 用apply函数批量转换(注意:apply是逐行处理,大数据量可以用向量化优化)
data[["wgs_lon", "wgs_lat"]] = data.apply(
lambda row: gcj02_to_wgs84(row["gcj_lon"], row["gcj_lat"]),
axis=1,
result_type="expand" # 把返回的两个值拆成两列
)
# 保存结果
data.to_csv("million_gps_wgs84.csv", index=False)
# 结果:100万条数据的坐标会被批量转换
数学模型:清洗背后的“数学逻辑”
空间数据清洗不是“拍脑袋”,而是有严谨的数学支撑——比如插值用线性模型,异常值用统计分布,坐标转换用投影公式。
1. 线性插值的数学公式
假设我们有两个连续的GPS点:
- 点1:时间
t1,坐标(x1, y1); - 点3:时间
t3,坐标(x3, y3); - 中间缺了点2:时间
t2(t1 < t2 < t3)。
线性插值的公式是:
x2=x1+t2−t1t3−t1×(x3−x1)x2 = x1 + \frac{t2 - t1}{t3 - t1} \times (x3 - x1)x2=x1+t3−t1t2−t1×(x3−x1)
y2=y1+t2−t1t3−t1×(y3−y1)y2 = y1 + \frac{t2 - t1}{t3 - t1} \times (y3 - y1)y2=y1+t3−t1t2−t1×(y3−y1)
解释:点2的坐标是点1到点3的“线性过渡”——时间越接近点1,坐标越像点1;时间越接近点3,坐标越像点3。
2. 速度计算的数学公式
相邻两个GPS点的距离(米)用“球面余弦公式”计算:
distance=R×arccos(sin(lat1)×sin(lat2)+cos(lat1)×cos(lat2)×cos(lon2−lon1))distance = R \times \arccos(\sin(lat1) \times \sin(lat2) + \cos(lat1) \times \cos(lat2) \times \cos(lon2 - lon1))distance=R×arccos(sin(lat1)×sin(lat2)+cos(lat1)×cos(lat2)×cos(lon2−lon1))
其中:
R:地球半径(约6371000米);lat1, lon1:点1的纬度、经度(弧度);lat2, lon2:点2的纬度、经度(弧度)。
速度(米/秒)的公式是:
speed=distanceΔtspeed = \frac{distance}{\Delta t}speed=Δtdistance
其中Δt是两个点的时间差(秒)。
3. Z-score的数学公式
Z-score用来衡量“数据点离平均值的距离”:
Z=X−μσZ = \frac{X - \mu}{\sigma}Z=σX−μ
其中:
X:数据点的值(比如速度);μ:数据的平均值;σ:数据的标准差。
规则:|Z| > 3的点是异常值(因为正态分布中,99.7%的数据在μ±3σ内)。
项目实战:清洗骑手GPS数据的完整流程
现在用“真实数据”演示空间数据清洗的完整流程——目标是把“脏的骑手GPS数据”变成“能用来分析配送效率的干净数据”。
1. 开发环境搭建
需要安装以下Python库:
pandas:处理表格数据;geopandas:处理空间数据;numpy:数学计算;matplotlib:可视化;pyproj:坐标转换。
安装命令:
pip install pandas geopandas numpy matplotlib pyproj
2. 数据说明
我们用的是“某外卖骑手的GPS轨迹数据”,包含以下字段:
time:时间(字符串,比如“2023-10-01 12:00:00”);gcj_lon:GCJ02经度(高德地图的坐标);gcj_lat:GCJ02纬度;speed:速度(km/h,原始数据有异常)。
3. 完整代码实现
import pandas as pd
import numpy as np
import geopandas as gpd
from shapely.geometry import Point
import matplotlib.pyplot as plt
from gcj02_to_wgs84 import gcj02_to_wgs84 # 导入之前写的转换函数
# ---------------------- 步骤1:加载数据 ----------------------
data = pd.read_csv("rider_gps_raw.csv")
print("原始数据行数:", len(data)) # 输出:原始数据行数:1000
# ---------------------- 步骤2:处理缺失值 ----------------------
# 1. 转time为datetime类型
data["time"] = pd.to_datetime(data["time"])
# 2. 按时间排序
data = data.sort_values(by="time")
# 3. 线性插值补缺失值
data["gcj_lon"] = data["gcj_lon"].interpolate(method="linear")
data["gcj_lat"] = data["gcj_lat"].interpolate(method="linear")
print("处理缺失值后的数据行数:", len(data)) # 输出:1000(补全了缺失的100行)
# ---------------------- 步骤3:处理异常值 ----------------------
# 1. 计算速度(用之前的公式)
data["time_diff"] = data["time"].diff().dt.total_seconds()
data["delta_lon"] = data["gcj_lon"].diff() * 111319
data["delta_lat"] = data["gcj_lat"].diff() * 111319 * np.cos(np.radians(data["gcj_lat"]))
data["speed_calc"] = np.sqrt(data["delta_lon"]**2 + data["delta_lat"]**2) / data["time_diff"]
# 2. 过滤速度超过30km/h的点
speed_threshold = 30 * 1000 / 3600 # 8.33m/s
data = data[data["speed_calc"] <= speed_threshold]
# 3. 过滤不在北京范围内的点
beijing_bbox = {"min_lon": 115.7, "max_lon": 117.4, "min_lat": 39.4, "max_lat": 41.6}
data = data[
(data["gcj_lon"] >= beijing_bbox["min_lon"]) &
(data["gcj_lon"] <= beijing_bbox["max_lon"]) &
(data["gcj_lat"] >= beijing_bbox["min_lat"]) &
(data["gcj_lat"] <= beijing_bbox["max_lat"])
]
print("处理异常值后的数据行数:", len(data)) # 输出:850(删掉了150行异常值)
# ---------------------- 步骤4:坐标转换(GCJ02转WGS84) ----------------------
data[["wgs_lon", "wgs_lat"]] = data.apply(
lambda row: gcj02_to_wgs84(row["gcj_lon"], row["gcj_lat"]),
axis=1,
result_type="expand"
)
# ---------------------- 步骤5:可视化验证 ----------------------
# 转成GeoDataFrame(WGS84坐标)
geometry = [Point(xy) for xy in zip(data["wgs_lon"], data["wgs_lat"])]
gdf = gpd.GeoDataFrame(data, geometry=geometry, crs="EPSG:4326")
# 画轨迹图
fig, ax = plt.subplots(figsize=(10, 10))
gdf.plot(ax=ax, marker="o", color="red", markersize=5, label="骑手轨迹")
plt.title("骑手GPS轨迹(清洗后)")
plt.xlabel("经度")
plt.ylabel("纬度")
plt.legend()
plt.show()
# ---------------------- 步骤6:保存干净数据 ----------------------
data.to_csv("rider_gps_clean.csv", index=False)
4. 结果说明
- 缺失值处理:补全了100行缺失的经纬度;
- 异常值处理:删掉了150行速度超过30km/h或不在北京的点;
- 坐标转换:把所有GCJ02坐标转成了WGS84;
- 可视化:画出的轨迹是“连续、合理、准确”的(没有断片、没有飞到郊区)。
实际应用场景:清洗后的数据能做什么?
干净的空间数据是“金矿”——能支撑很多有价值的应用:
1. 外卖配送路线优化
用清洗后的骑手轨迹数据,分析“哪些路段容易堵车”,优化配送路线,减少超时率。
2. 共享单车调度
用清洗后的共享单车GPS数据,分析“哪些区域用车需求大”,调度车辆到需求大的区域,减少用户等待时间。
3. 城市规划
用清洗后的POI数据,分析“哪些区域缺少餐厅/医院”,为城市规划提供依据(比如在缺餐厅的区域建商场)。
4. 物流路径规划
用清洗后的货车GPS数据,分析“最优运输路线”,减少燃油消耗和运输时间。
工具和资源推荐
- 数据处理工具:Python(pandas、geopandas)、QGIS(开源GIS软件,可视化空间数据);
- 坐标转换工具:Proj4(Python的pyproj库)、GPS Visualizer(在线坐标转换工具);
- 数据来源:OpenStreetMap(免费POI数据)、高德地图API(获取GCJ02坐标)、GPS设备(获取WGS84数据);
- 学习资源:《Python地理数据处理》(书籍)、Geopandas官方文档(https://geopandas.org/)。
未来发展趋势与挑战
空间数据清洗的未来会越来越“智能”,但也有挑战:
1. 自动化清洗
用AI(比如深度学习)自动检测缺失值和异常值——比如训练一个模型,自动识别“骑手的异常轨迹”。
2. 实时清洗
处理“流式空间数据”(比如实时GPS轨迹)——比如骑手的GPS数据实时上传,实时清洗,实时用于路线优化。
3. 跨源数据融合
融合不同来源的空间数据(比如GPS数据+卫星影像+POI数据)——比如用卫星影像验证POI的位置是否准确。
挑战
- 数据量大:处理10亿条GPS数据时,传统的Python脚本会很慢,需要用Spark等分布式计算框架;
- 隐私保护:空间数据包含用户的位置信息,清洗时需要匿名化(比如去掉用户ID);
- 复杂场景:比如处理“室内GPS数据”(信号弱,缺失值多),需要更复杂的插值方法。
总结:空间数据清洗的“核心心法”
读完本文,你应该记住以下3点:
1. 核心概念回顾
- 缺失值:补全缺的点(用插值或邻域);
- 异常值:删掉或修正错的点(用速度、聚类、边界框);
- 坐标转换:统一尺子(用Proj4或火星算法)。
2. 清洗的顺序
缺失值→异常值→坐标转换——顺序错了,会导致“补了异常值”或“转了错误的坐标”。
3. 验证的重要性
清洗后一定要可视化验证——比如画轨迹图,看有没有断片、有没有异常点,确保数据是“干净的”。
思考题:动动小脑筋
- 如果你处理“共享单车的停车点数据”,有的停车点坐标缺失,你会用本文的哪个技巧补?为什么?
- 如果你拿到“百度地图的POI数据”(BD09坐标),要转成WGS84,应该怎么做?(提示:BD09是在GCJ02基础上再加密一次)
- 如果你分析“快递员的配送轨迹”,发现有个点的速度是50km/h(市区),但快递员说“当时在坐汽车”,你会删掉这个点吗?为什么?
附录:常见问题与解答
Q1:为什么中国的坐标要加密?
A:为了国家安全——防止境外势力用GPS数据精准定位中国的敏感地点(比如军事基地)。
Q2:怎么判断坐标的参考系?
A:看数据来源——比如手机GPS是WGS84,高德/腾讯是GCJ02,百度是BD09;如果不确定,可以用“坐标转换工具”测试(比如把WGS84的北京天安门坐标导入高德地图,看位置是否偏移)。
Q3:缺失值太多(比如50%),还能补吗?
A:不能——补的话会引入太多误差,建议删掉这段数据,或用其他数据源补充。
扩展阅读 & 参考资料
- 《Python地理数据处理》(作者:Joel Lawhead);
- Geopandas官方文档:https://geopandas.org/;
- Proj4官方文档:https://proj.org/;
- 火星坐标转换算法:https://github.com/wandergis/coordTransform_py。
结语:空间数据清洗不是“体力活”,而是“技术活”——用对方法,能让“脏数据”变成“宝藏”。希望本文的10个技巧能帮你“少走弯路”,把空间数据用得更顺手!
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)