大数据空间数据清洗指南:处理缺失值、异常值、坐标转换的10个实用技巧

关键词:空间数据清洗、缺失值处理、异常值检测、坐标转换、GIS数据、大数据处理、实用技巧
摘要:空间数据是“带位置的数字记录”——小到外卖骑手的GPS轨迹,大到卫星遥感影像,都属于这类数据。但原始空间数据往往像“没洗过的菜”:有的“叶子缺了”(缺失值),有的“混了烂叶子”(异常值),有的“放错了篮子”(坐标系统不一致)。本文用生活类比+代码实战的方式,拆解空间数据清洗的3大核心问题(缺失、异常、坐标),给出10个能直接落地的实用技巧。读完你会明白:如何把“脏数据”变成“能下锅的菜”,让后续的分析(比如路线优化、城市规划)更靠谱。

背景介绍:为什么空间数据需要“洗澡”?

1. 目的和范围

想象一下:你要给外卖平台做“骑手配送效率分析”,拿到的数据是骑手的GPS轨迹——但其中10%的点没记录(缺失值),有几个点突然跳到了100公里外的郊区(异常值),还有的坐标是“WGS84”(国际标准),有的是“GCJ02”(中国加密标准)。如果直接用这些数据算“平均配送时间”,结果会像“用没洗的菜做饭”:要么淡(缺失值导致结果不准),要么苦(异常值干扰结论),要么根本没法吃(坐标错导致位置偏差)。

本文的核心目标是:用最低门槛的方法,解决空间数据最常见的3个问题——缺失值、异常值、坐标转换,让数据“干净到能直接用”。

2. 预期读者

  • 刚接触空间数据的分析师/产品经理(比如要做共享单车调度、物流路径优化);
  • 数据工程师(需要处理GPS、POI、卫星影像等数据);
  • GIS爱好者(想把自己收集的坐标数据导入地图)。

3. 文档结构概述

本文像“做番茄炒蛋的步骤”:

  1. 先讲“准备工作”(术语表,搞懂空间数据的基本概念);
  2. 再讲“洗菜技巧”(10个核心清洗方法,对应缺失、异常、坐标3大问题);
  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数据后,发现三个问题:

  1. 缺了点:12:05到12:10之间,李师傅的GPS没记录(可能手机没信号);
  2. 错了点:12:30的坐标显示李师傅在“北京天安门”,但他当时应该在“朝阳区国贸”(明显异常);
  3. 乱了点:有的坐标是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——坐标转换就是做这件事。

核心概念的关系:“洗菜的顺序”

空间数据清洗的顺序不能乱,就像“洗菜要先摘烂叶子,再洗干净,最后切”:

  1. 先处理缺失值:补全缺的点,让数据“连续”;
  2. 再处理异常值:删掉或修正错的点,让数据“合理”;
  3. 最后做坐标转换:把所有点转成同一种坐标,让数据“统一”。

类比:做番茄炒蛋时,要先“挑烂番茄”(异常值),再“补买缺的番茄”(缺失值),最后“把番茄切成小块”(坐标转换)——顺序错了,菜就做不好。

核心流程的文本示意图

原始空间数据 → 缺失值检测 → 缺失值处理 → 异常值检测 → 异常值处理 → 坐标转换 → 干净空间数据

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轨迹数据)。
原理:用pandasapply函数或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:时间t2t1 < t2 < t3)。

线性插值的公式是:
x2=x1+t2−t1t3−t1×(x3−x1)x2 = x1 + \frac{t2 - t1}{t3 - t1} \times (x3 - x1)x2=x1+t3t1t2t1×(x3x1)
y2=y1+t2−t1t3−t1×(y3−y1)y2 = y1 + \frac{t2 - t1}{t3 - t1} \times (y3 - y1)y2=y1+t3t1t2t1×(y3y1)

解释:点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(lon2lon1))

其中:

  • 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. 验证的重要性

清洗后一定要可视化验证——比如画轨迹图,看有没有断片、有没有异常点,确保数据是“干净的”。

思考题:动动小脑筋

  1. 如果你处理“共享单车的停车点数据”,有的停车点坐标缺失,你会用本文的哪个技巧补?为什么?
  2. 如果你拿到“百度地图的POI数据”(BD09坐标),要转成WGS84,应该怎么做?(提示:BD09是在GCJ02基础上再加密一次)
  3. 如果你分析“快递员的配送轨迹”,发现有个点的速度是50km/h(市区),但快递员说“当时在坐汽车”,你会删掉这个点吗?为什么?

附录:常见问题与解答

Q1:为什么中国的坐标要加密?

A:为了国家安全——防止境外势力用GPS数据精准定位中国的敏感地点(比如军事基地)。

Q2:怎么判断坐标的参考系?

A:看数据来源——比如手机GPS是WGS84,高德/腾讯是GCJ02,百度是BD09;如果不确定,可以用“坐标转换工具”测试(比如把WGS84的北京天安门坐标导入高德地图,看位置是否偏移)。

Q3:缺失值太多(比如50%),还能补吗?

A:不能——补的话会引入太多误差,建议删掉这段数据,或用其他数据源补充。

扩展阅读 & 参考资料

  1. 《Python地理数据处理》(作者:Joel Lawhead);
  2. Geopandas官方文档:https://geopandas.org/;
  3. Proj4官方文档:https://proj.org/;
  4. 火星坐标转换算法:https://github.com/wandergis/coordTransform_py。

结语:空间数据清洗不是“体力活”,而是“技术活”——用对方法,能让“脏数据”变成“宝藏”。希望本文的10个技巧能帮你“少走弯路”,把空间数据用得更顺手!

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐