空间聚类基础:KMeans、Louvain、Leiden聚类在空间spots上的应用与局限性

拿到空间转录组数据,第一件事往往是问:这些spots到底能分成哪些区域?说白了,就是空间聚类。我刚开始接触这行时,以为把普通单细胞聚类算法直接搬过来就行,结果跑出来的图惨不忍睹——相邻的spots被分到不同cluster,空间连续性全没了。今天咱们就聊聊三种最常用的聚类方法:KMeans、Louvain、Leiden,看看它们在空间spots上到底怎么用,坑在哪里。

KMeans:简单粗暴,但太“天真”

KMeans的原理大家都知道,基于欧氏距离,迭代找中心点。把它用在空间spots上,就是把每个spot的基因表达向量当特征,硬生生分成K堆。

from sklearn.cluster import KMeans
kmeans = KMeans(n_clusters=5, random_state=42)
spatial_clusters = kmeans.fit_predict(expression_matrix)
# expression_matrix shape: (n_spots, n_genes)

嗯,代码看着挺干净。但我第一次跑完拿到结果时,发现某些cluster的spots在整个组织上像撒芝麻一样散落。为什么?因为KMeans根本不看spots之间的空间位置关系,它只认表达谱相似度。

KMeans的致命短板:

  • 球形假设:只能识别球状分布,空间数据往往是流形结构
  • K值预设:你根本不知道组织有多少功能区域
  • 忽视空间邻域:相邻两个spots基因表达可能接近,但KMeans并不保证它们分到一类

我建议只在做快速初步探索时用KMeans,比如看看区域是不是大致能分成几块。正式分析?别指望它。

Louvain:考虑图结构,但分辨率调到你崩溃

Louvain是单细胞转录组聚类的老牌方法,后来被搬到空间数据上。它的核心思想是:先根据spot间的基因表达相似度和空间邻域构建一个图,然后用模块度优化来划分社区。

步骤大致这样:

  1. 构建空间邻接图(比如基于距离半径或KNN)
  2. 给边加上权重(表达相似性)
  3. 跑Louvain算法,迭代合并社区使模块度最大
import scanpy as sc
# 假设 adata 已包含 spots 的空间坐标和表达
sc.pp.neighbors(adata, n_neighbors=10, use_rep='X_pca')
sc.tl.louvain(adata, resolution=0.5)
# adata.obs['louvain'] 就是聚类标签

我在项目中遇到过这样的场景:用一个稀疏的邻接图(比如只连最近的两个邻居),Louvain很容易把所有spots聚成一团。调高resolution参数,社区又碎得厉害。我去过一个坑,当时以为resolution越高越好,结果跑出来30多个cluster,组织切片上看全是噪点。

Louvain的常见问题:

  • 分辨率敏感:同一个数据,改0.1能差出两个等级,可重复性堪忧
  • 不稳定:不同随机种子可能产生差异明显的划分
  • 图构建依赖:邻域大小、权重计算方式对结果影响巨大

Leiden:更精细,更稳当

Louvain有一个内在缺陷:它可能会把不连通的社区当作一个最优解。Leiden算法在Louvain基础上加了一步“精炼”,保证划分出的每个社区内部是连通的。说白了,就是更细的粒度控制。

使用起来和Louvain几乎一样:

sc.tl.leiden(adata, resolution=0.5, n_iterations=-1)
# 相比louvain多了一个 n_iterations 参数控制迭代次数

我个人习惯把Leiden作为空间聚类的首选。原因很简单:
– 算法更稳定,多次跑结果一致
– 能自动检测不同大小的社区
– 计算速度不输Louvain(甚至更快一点)

记得有一次比对两种方法,同样的数据、同样的分辨率,Louvain分出的cluster边缘非常模糊,Leiden却能清晰勾勒出过渡区。这在实际的肿瘤切片分析中,直接影响到肿瘤边界判断。

经验之谈:

使用Leiden时,建议先尝试0.2~1.0之间的多个resolution,然后用“聚类稳定性”或“空间连续性”指标来选择最优参数。不要只看轮廓系数。

三种方法对比一表看清

方法 输入依赖 空间约束 自动聚类数 稳定性 适用场景
KMeans 表达矩阵 无 否 高(但结果差) 快速预分类
Louvain 图+表达 通过邻接图间接 否(resolution调) 中等 常规空间聚类
Leiden 图+表达 连通性保证 否(resolution调) 高 推荐首选

避坑指南(我踩过的那些坑)

我曾经在一批小鼠脑组织数据上用Louvain跑聚类,相同的参数在两份切片上得到完全不同的区域划分。后来发现,问题出在邻接图的构建上——两份切片的空间分辨率不一致,导致KNN邻域包含的实际组织区域面积不同。
解决办法?用“物理距离半径”代替“最近邻居个数”,比如设定半径50微米内的spots都连接起来。

还有一个坑:不要把所有基因一股脑塞进邻接图计算。我建议先用高度可变基因(比如top2000)降维新维度(PCA),然后基于低维embedding计算邻接图和相似度。否则计算量上天,而且噪声会把好的聚类糊掉。

你想想看,空间聚类本质上是在“表达相似性”和“空间邻近性”之间找平衡。Leiden给了我们一个相对好的起点,但参数调优的过程,永远没法完全自动化——这就是最考验经验的地方。

最后一句建议:

拿Leiden做核心,配合空间可视化(把cluster映射到组织坐标上),肉眼检查一致性和生物学合理性。我从来不信纯指标选出来的聚类数。

空间聚类三种方法流程对比 表达矩阵 + 空间坐标 KMeans (球形假设) 忽略空间邻域 输出:球形聚类 Louvain (模块度) 构建空间邻接图 输出:不稳定划分 Leiden (精炼模块度) 保证社区连通性 输出:稳定精细聚类 经验:图聚类(Louvain/Leiden)空间连续性远优于KMeans,但参数调优不可省