> 作者 (author): LKP <kunpeng.liao@abiosciences.com>
> Skill 及脚本参数来源: Chen et al., 2025, Cancer Cell 43, 1656–1676, <https://doi.org/10.1016/j.ccell.2025.06.020>
空间 Niche 重构与邻域(vicinity)分析
从 RCTD 组成到 niche 标签、再到种子结构的距离分层。
1. 组成矩阵与分组
# 每 spot 亚型比例(RCTD counts 转置按行归一化,行和=1)
comp <- t(LayerData(spatial, assay = "RCTD", layer = "counts")); comp <- comp / rowSums(comp)
# 按 Louvain 簇求 colMeans → 簇级组成 → scale 后层次聚类
hc <- hclust(dist(scale(cluster_composition)), method = "ward.D2")
groups <- cutree(hc, k = 12) # 12 组(可按先验 niche 数调整)
2. Archetype 打分与一一指派
# 配置表:niche, feature_regex, weight
# 例:N12_Nerve 匹配 ^S[0-9]+_Schwann|^Schwann$ 权重 5;
# 恶性 ductal 4 型各 weight 3;N01_TLS 权重 2
# 组级分数 = Σ 组内匹配 regex 的亚型比例 × weight
# 一一指派(防两组争同一标签):
shifted <- score_mat - min(score_mat) # 非负化
assign <- clue::solve_LSAP(shifted, maximum = TRUE) # 匈牙利算法最大权匹配
spatial$niche3 <- assigned_labels # factor 按名称排序
3. Visium spot 图构建
library(FNN); library(igraph)
# 候选邻居:k=12 近邻(覆盖六邻接+对角)
knn <- FNN::get.knn(coords[, 1:2], k = 12)
# 边保留阈值 = median(第1近邻距离,>0有限值) × 1.25
base_d <- median(knn$nn.dist[, 1][knn$nn.dist[, 1] > 0 & is.finite(knn$nn.dist[, 1])])
thr <- base_d * 1.25
# 建无向图 + simplify
g <- make_empty_graph(directed = FALSE) |> add_edges(edges) |> simplify()
4. 种子结构距离分层(vicinity)
# 种子:目标 niche 的 spots(如 N12_Nerve);每片种子 <4 个则跳过该片
# BFS 层距离:虚拟超节点连全部种子 → igraph::distances() - 1
# 侵袭二分类:恶性分数(恶性亚型比例和)kmeans(centers=2, nstart=50),
# 中心均值高者 = invaded_proxy
# 每层上限 max_layer = 5;并列/不可达/超限 → NA
# 区域划分:layer 0 = "nerve";1:2 = "juxta-neural_1-2";3:5 = "peri-neural_3-5"
5. 分层统计检验
# 组成按 (片 × proxy类 × region × celltype) 求均值
# 配对差值:juxta 区域逐片 (invaded − non_invaded),≥3 对时
wilcox.test(diff, mu = 0, exact = FALSE) # + BH 校正
# 折线图:按 nerve_layer 0-5 各细胞类型均值 ± SE,facet free_y
常见坑
- niche 用层次聚类(ward.D2 + cutree),不是 KMeans;KMeans 仅用于侵袭二分类
- 一一指派必须匈牙利:贪心逐组取 top 会导致标签冲突
- 距离是图跳数(BFS 层),不是欧氏 µm——不规整区域(褶皱/空隙)更稳健
- 1.25× 阈值:过小图断裂(不可达 NA 暴增),过大跨 tissue 缝隙;用中位近邻距离自适应
- 种子稀疏切片直接跳过:种子 <4 的片分层无统计意义,全片记 NA