1. 空间数据与聚类基础
说实话,大多数人刚开始接触空间聚类时,都以为它只是普通聚类加了个“坐标”。其实差得远。空间数据的结构、自相关特性,直接影响聚类结果的可靠性。这一节,我们先打好地基。
1.1 空间数据结构
空间数据不像你熟悉的表格数据那么简单。它有两种主流模型:矢量和栅格。我当年入门时,总把矢量当成表格里的点坐标,后来才明白它背后还有几何拓扑关系。
| 特性 | 矢量数据 | 栅格数据 |
|---|---|---|
| 基本单元 | 点、线、面 | 像素/像元(网格) |
| 精度 | 高(边界精确) | 受分辨率限制 |
| 存储结构 | 坐标序列 + 属性表 | 二维数组 + 波段元数据 |
| 典型格式 | Shapefile、GeoJSON、GeoPackage | GeoTIFF、NetCDF、IMG |
| 空间分析操作 | 缓冲区、叠加、网络分析 | 滤波、重分类、邻域统计 |
| Leiden聚类适用性 | 直接用于节点/边图 | 需先构建邻接图 |
在Leiden聚类里,最常用的就是矢量数据里的“多边形”或“点”。每个要素就是一个节点,节点之间的邻接关系构成边。你想想看,如果拿栅格数据来做,首先要把网格转成邻接图——这一步我在项目里踩过坑,处理不当会导致聚类结果不连续。
1.2 空间自相关概念
“自相关”这个词听起来吓人,说白了就是:距离近的样本,属性值可能更像。你去问地理学家,他们管这叫“地理学第一定律”。
为什么要理解这个?因为Leiden聚类本质上是在找“邻居效应”强的区域。如果数据根本没有空间自相关,那聚类结果就和随机分组差不多。
衡量自相关有两个常用指标:
- 全局莫兰指数(Global Moran’s I):判断整体上是否存在空间聚集。取值范围[-1,1]。正值表示正相关(同类聚集),负值表示离散。
- 局部莫兰指数(Local Moran’s I):告诉你哪个局部区域是高高聚集、低低聚集、还是离群点。
实用技巧:我习惯先用PySAL库快速算一下全局莫兰值。如果I值接近0,说明数据几乎是随机分布,可以用传统聚类;如果I值显著>0,那Leiden这类空间聚类就很有价值。
这里有个避坑点——我遇到过一朋友直接拿原始坐标算欧氏距离做K-means,没考虑空间权重矩阵。结果聚类边界全是锯齿状,因为算法忽略了更真实的“邻居”定义。在Leiden聚类里,邻居通常用Queen邻接(共享边或顶点)或Rook邻接(只共享边)。
1.3 聚类任务定义
聚类是“无监督学习”里的老演员了。它的任务是把样本分成若干组,组内相似、组间相异。但空间聚类的特殊之处在于:
- 不仅要考虑属性相似,还要考虑空间接近;
- 结果要满足地理完整性(比如一个聚类不能碎成两个远距离岛屿);
- 邻居效应本身是一个隐式约束,Leiden正是通过局部模块度优化来兼顾这两点。
用一个具体例子说明:假设你要把城市街区按人口密度聚类。普通K-means可能把一个高密度街区和三公里外的另一个高密度街区归为一类,中间隔着公园和低密度区。而Leiden聚类会检查它们的空间邻居关系——如果中间有显著的低密度阻隔,就不会强行合并。这就是“邻居效应”的真正含义。
核心要点:Leiden聚类不是直接对你的数值标签做聚类,而是在你构建的加权邻接图上进行社区发现。这张图的节点是你空间单元,边权可以是你自己定义的空间权重(比如反距离、共边长度),也可以结合属性相似度。
下面这张SVG图整理了本章的核心逻辑:从空间数据结构出发,经过自相关分析确认数据特性,最后进入聚类任务。
在实际项目中,我通常先用geopandas读入数据,构建空间邻接表,再交给Leiden。下面是一个极简的`Python`数据准备片段(仅示意结构):
import geopandas as gpd
import libpysal
# 读取矢量数据(例如社区多边形)
gdf = gpd.read_file("neighborhoods.shp")
# 构建空间权重矩阵(Queen邻接)
w = libpysal.weights.Queen.from_dataframe(gdf)
# 转成邻接表格式(节点对 + 边权)
edges = []
for i in w.neighbors:
for j in w.neighbors[i]:
edges.append((i, j, w.weights[i][j] if w.weights else 1.0))
# 这里后续会将 edges 转化为 Leiden 需要的图结构
警告:我曾经直接使用默认的欧氏距离作为边权,忽略了空间权重矩阵的归一化。结果低密度区域被错误合并。记得做行标准化(row-standardization),否则度差异大的节点会扭曲聚类。
好,这一章就聊到这儿。记住:空间数据看结构,自相关定方向,聚类任务最终落到邻居效应的量化上。下节我们会深入Leiden的具体算法细节。不过那不是现在的事——先把基础消化掉。