资讯动态

分子niche功能验证全解析:Squidpy空间邻居图与富集分数实操

发布时间:2026/10/16 8:03:56 来源:尧图企业网站定制
做了空间转录组分析的人大概率都会遇到这种尴尬拿到一张带空间坐标的切片数据第一个想回答的问题往往是“某个区域的A细胞是不是比别处多”“B细胞和C细胞是不是更容易挨在一起”结果点开工具书发现Squidpy刚好有一个分子niche功能但网上的教程基本只写到“跑一下nhood_enrich画个热图”就结束了。真正让人头疼的其实是后半程——跑完之后这个富集分数到底可不可信、怎么验证它有生物学意义、邻域半径选多少合适、为什么有些细胞群的分数显著得离谱这中间每一步都会劝退不少人。这篇就把Squidpy分子niche功能的验证思路和实操细节一次性讲清楚覆盖从空间邻居图构建到富集分数检验、从结果解读到常见坑位排查的完整链路。适合已经会基础单细胞分析、正在做空间转录组或者刚把Squidpy装上但面对参数不知道从何下手的朋友。1. 分子niche是什么以及为什么不能只看细胞类型比例1.1 圈区域统计比例的旧办法到底缺了什么最早接触空间数据时我习惯的做法和很多人一样画ROI把某个区域内的细胞类型占比算出来再和远处另一个区域比一下。这套流程简单直观但有两个很难绕开的缺陷。第一是区域边界怎么定。同一个组织切片圈得大一点或小一点比例可以相差很远而边界本身带有很强的主观性更关键的是一个细胞只差半个细胞直径的距离可能就被划到两个完全不同的“区域”里这种不连续性在单细胞尺度上很失真。第二是比例没有统计学佐证。假设一个区域的巨噬细胞占20%全局背景占10%光看数字似乎高了但样本量多大、邻域里到底有几个细胞都没被考虑进去。小样本下的高比例可能纯属波动只拿百分比说话很容易被误导。所以后来我转向另一个思路不圈区域而是以每个细胞为中心看它周围的实际邻居构成再把这种构成和整体背景做比较。这个以细胞为中心描述的局部环境本质上就是“分子niche”。一个人住在什么街区不能只看他自己是做什么工作的得看他的邻居都是什么人——细胞也是这样它自身状态很重要但它周围是谁往往决定了它能接收什么信号、和谁发生互作。1.2 分子niche的定义、边界和适用场景在Squidpy里分子niche不是一个独立的数据结构而是一种计算视角。核心是围绕空间邻居图展开先把每个细胞和它的空间近邻连起来然后统计每个细胞的邻域里各类细胞的数量再检验这种数量关系是否显著偏离全局随机。它能回答三类问题哪些细胞类型在空间上倾向共定位某一种细胞的局部环境是否系统性富集或缺失某一类邻居以及不同类型细胞有没有各自偏好的“微环境模板”。但它不适合做的事也别强求。它不能直接证明两个细胞有物理接触或信号互作不能替代配体-受体分析也不能给出一个“niche种类的标准划分”——这些都需要额外的方法来补。把它理解成一个单细胞级别的局部微环境描述工具定位就清楚了。2. nhood enrich的核心机制为什么它比“数邻居”更可靠2.1 空间邻居图是怎么来的要算分子niche第一步是定义“邻居”。Squidpy里对应的是sq.gr.spatial_neighbors它会基于每个细胞的坐标建立一张K近邻图或按固定半径连边。这一步的输出是一张稀疏邻接矩阵存在adata.obsp[spatial_neighbors]里矩阵的行和列都是细胞非零项表示这两个细胞在空间上互为邻居。K近邻和半径邻域的选择很关键。K近邻能保证每个细胞都有固定数量的邻居适合细胞密度不均匀的组织但代价是局部“地理尺度”会随密度伸缩——在细胞稀疏区域K个邻居可能横跨很远的距离在密集团块中K个邻居又可能只覆盖很小一片。半径邻域则固定了物理尺度多远算邻居在不同位置都一致更适合需要严格空间范围的生物学问题缺点是稀疏区域容易出现邻居不足。具体参数的确定要看数据平台。Visium这类spatial transcriptomics平台的坐标通常是像素spot之间距离在百微米量级MERFISH、Slide-seq这类单细胞分辨率的平台坐标常是微米细胞间距往往只有几十微米。所以照抄别人的参数不靠谱先搞清楚坐标单位再选半径后面所有结论才立得住。coord_typegeneric适合不规则分布的细胞坐标绝大多数单细胞空间平台都用它coord_typegrid用于网格规则排列的数据。拿不准就去查数据文档这个参数选错的结果是邻居图完全不符合预期。2.2 富集分数的统计学逻辑邻居图建好之后进入核心步骤sq.gr.nhood_enrich。它的工作方式让我想起一个“罐子摸球”的问题。假设袋子里有100个球其中20个是红的你从里面抓10个如果抓出的10个里红球占了5个这是不是异常直觉上5/10比20%高不少但具体能不能算“不随机”要用超几何分布算一下这个情况出现的概率。nhood_enrich对每个细胞都做类似检验全局背景里某类细胞占多大比例而当前这个细胞的邻域里该类细胞实际有多少在“邻居和全局没有差别”的零假设下当前观察值出现的概率是多少。概率小就说明这个邻域对该类细胞显著富集或缺失。这个过程会被重复做N次N等于细胞数乘以细胞类型数。当前细胞约3000个、细胞类型有10种时就是3万次检验所以结果表里的p值需要做好多重假设校正否则假阳性会很多。这也是它和简单比例法的根本区别——它不仅看比例还看这个比例在多大样本量下、偏离背景多远的统计显著性。2.3 输出里面到底装了什么运行完之后主要结果存在adata.uns[nhood_enrich]里。不同Squidpy版本字段名会有些出入但核心内容一般包括富集强度常用标准化分数用于衡量观测邻域构成与期望背景的偏离幅度p值矩阵代表偏离的统计显著性还有校正后的p值。矩阵的行列都是细胞类型行表示“观察对象”列表示“邻居类型”读起来就是“某行细胞类型的邻域里某列细胞类型是否富集”。画图时sq.pl.nhood_enrich会把这个矩阵渲染成一个对称热图。对角线亮通常说明同类型细胞倾向于扎堆比如肿瘤细胞区、T细胞富集区的自簇集非对角线亮点则提示两类细胞有共定位倾向这往往是下游配体-受体分析的最佳入手点。维度局部细胞类型比例nhood_enrich富集分数关注对象人工圈定的区域单个细胞为中心输出含义区域内的占比邻域构成的统计偏离度样本量考虑基本不考虑超几何检验纳入邻域大小显著性与假阳性控制无提供p值和多重检验校正下游扩展性区域之间比较可为每个细胞生成niche向量3. 从AnnData到niche score的完整实操3.1 安装与数据准备环境搭建不复杂核心依赖就是Scanpy、Squidpy和一份带空间坐标的AnnData对象。安装直接用pip或conda即可pip install squidpy scanpy数据准备阶段我要提醒一点nhood_enrich要求对象里必须有细胞类型注释列而且注释列最好不要有NaN。有些公开数据集里细胞注释不完整有部分细胞是“Unknown”或者空值这些细胞怎么处理如果少量可以保留并当成一个类别但如果占比例很大建议重新检查注释流程。坐标数据通常存在adata.obsm[spatial]里二维数组每行对应一个细胞的x、y坐标。不同平台导出格式不一样有的叫spatial有的叫X_umap但不是真实空间坐标千万不要混淆。可以先打印一下维度和数值范围确认import scanpy as sc import squidpy as sq adata sc.read_h5ad(your_spatial.h5ad) print(adata.obsm[spatial].shape) print(adata.obsm[spatial][:5])如果坐标范围出现几千到几万的像素值那是正常的如果都是0到1的归一化小数需要确认它是不是真实坐标否则接下来邻居图会全部乱掉。3.2 构建空间邻居图时要确认的三件事建邻居图的代码只有几行但这几步检查能省掉后面很多排错时间。sq.gr.spatial_neighbors( adata, coord_typegeneric, n_neighs8, radiusNone )第一件要确认的是邻接矩阵有没有“活下来”。稀疏矩阵的nnz非零元素个数应该远大于细胞数。如果输出只有几百个非零值说明邻居关系几乎没建成大概率是坐标单位或coord_type出了问题。print(adata.obsp[spatial_neighbors].nnz)第二件是看每个细胞的度数分布。用np.diff拿到稀疏矩阵的度序列确认没有大量孤立的0度细胞。0度细胞不会报错但它们在nhood_enrich里没有有效邻居富集分数等于没有意义。import numpy as np degrees np.diff(adata.obsp[spatial_neighbors].indptr) print(min:, degrees.min(), median:, np.median(degrees))第三件是考虑是否要按样本分别建图。如果数据里包含多个切片样本混在一起它们的空间坐标可能重叠甚至错乱先按样本循环处理更稳。这一步很多人会忽略后面专门展开讲。3.3 运行nhood enrich并导出分数矩阵邻居图确认无误后核心计算就一句话sq.gr.nhood_enrich( adata, groupbycell_type, nhoodsadata.obsp[spatial_neighbors] ) res adata.uns[nhood_enrich]结果不会自动生成独立的obsm矩阵所以如果想做下游聚类或差异分析需要自己从结果里把分数取出来。不同版本里字段名略有差异先打印keys再取值print(list(res.keys()))常规情况下可以挑一个标准化分数或使用-log10(p值)作为分数然后存入adata.obsm[niche]。再做一个scale标准化方便后续直接用。import numpy as np score res.get(zscore, None) if score is None: score -np.log10(res[pval].to_numpy() 1e-10) adata.obsm[niche] score.T sc.pp.scale(adata.obsm[niche])最后画一张标准热图快速看一下富集大格局sq.pl.nhood_enrich(adata, groupbycell_type, figsize(6, 5))到这里分子niche的初步分析已经完整跑通。但拿到一张漂亮的热图距离“功能验证”还很远真正的工作在后面。4. 分子niche功能的三种验证思路与配套可视化4.1 空间一致性验证第一种验证最直观有尼采的相邻组织学结构或切片上明显的分区结构把已知的空间domain与niche分数对照看它们是否一致。我处理某段结肠癌切片时病理上能清楚区分肿瘤上皮区、间质区、边缘浸润带。先用leiden聚类给细胞分组再看哪些cluster的空间位置和病理素描图重合。接下来把每个细胞的niche向量按domain分组比较不同domain之间的分数差异。sc.tl.leiden(adata, resolution0.8) sc.pl.spatial_scatter(adata, colorleiden)如果某个domain内的细胞确实拥有系统性相似的niche profile说明niche的富集结果和真实组织结构是对齐的。反之如果domain明明白白分为三个区域但所有细胞的niche都大同小异那就要回去重新检查邻居图或细胞类型注释了。更直接的视觉验证是在空间图上映射单个niche分数。比如把“被巨噬细胞富集的分数”作为颜色映射到各个细胞上肉眼就能看到高分数细胞是否恰好集中在预期区域。可视化时注意用发散型色条并手动设置对称的上下界否则色条会被极端值拉偏。4.2 转录功能一致性验证第二种验证是转录层面的同一个niche环境里的细胞如果这个环境真的在影响它们那么它们的基因表达应该也会表现出功能差异。做法是先根据niche分数把细胞分组。比如只取巨噬细胞按照“是否被肿瘤细胞富集”分成两组然后做差异表达bdata adata[adata.obs[cell_type] Macrophage] bdata.obs[niche_group] ( adata.obsm[niche][:, tumor_col_idx] 0 ).astype(category) bdata.obs[niche_group] bdata.obs[niche_group].map({0: non_tumor_niche, 1: tumor_niche}) sc.tl.rank_genes_groups(bdata, groupbyniche_group, methodwilcoxon) sc.pl.rank_genes_groups(bdata, n_genes15)关键看差异基因是否富集到与niche相关的功能通路。肿瘤富集区域的巨噬细胞上调基因如果包含了缺氧诱导、炎症因子、细胞外基质重塑相关通路那和“肿瘤微环境改变巨噬细胞功能状态”的假说就对上了。反过来如果两组之间基因差异都是零散的核糖体蛋白或线粒体基因基本说明niche分组没有生物学意义先别急着写进文章。这一步是鉴定“统计显著的niche”是否有真实生物学影响的关键一环。统计显著只能说明邻居构成不随机功能影响需要通过转录组的响应来体现。4.3 同类型细胞niche自洽性验证第三种验证比较量化同类细胞如果有稳定的niche偏好那它们的niche向量应该比随机挑选的一群细胞更相似。用轮廓系数或者组内距离就能衡量。from sklearn.metrics import silhouette_score sil silhouette_score( adata.obsm[niche], labelsadata.obs[cell_type], metriceuclidean ) print(sil)细胞类型注释按大类给时轮廓系数往往不高因为一种细胞本身可能分属多种niche状态如果注释更细细分亚型内部的niche一致性通常会上升。某类细胞的轮廓系数接近0甚至为负说明这类细胞根本没有一致的微环境偏好虽然这本身也是一种发现但和“有明确niche”的结论不是一回事。自洽性验证的价值在于它不依赖任何外部注释只依赖内部结构。它能快速筛选出研究中值得聚焦的细胞类型——轮廓系数高的类型值得深入挖掘低的基本可以放弃。5. 实际项目中容易踩的五个坑5.1 邻居图“静默失败”我先列一个最隐蔽的问题邻居图可能没有构建成功但不会有任何报错。这种情况通常在几个样本拼在一起跑的时候出现。有个项目里我把三个切片的AnnData简单concat成一个大对象然后直接对所有细胞调用了spatial_neighbors。结果是细胞确实连上了但很多“邻居”跨越了不同切片之间的空隙甚至同一个细胞周围全是对应切片外的点。富集热图出来之后不同样本的细胞类型显得高度互斥明显是坐标错乱导致的假象。这类问题排查时要先看nnz和度数分布是不是合理如果某个样本的细胞中位数度数明显极低基本就是坐标碎片化。正确做法是逐样本构建邻居图或者至少先确认不同样本的坐标范围有没有重叠再决定是否可以合并统一建图。对于多切片数据我习惯分别对每个样本调用spatial_neighbors把邻接矩阵记到子对象的obsp里后续分析按样本分批进行。虽然写起来不够“一次性”但能避免坐标混用导致的虚假富集。5.2 标签粒度影响富集结论细胞类型注释的粗细对niche结论的影响比刚接触的人想象中更大。把髓系细胞统一标成“Myeloid”时nhood_enrich只会告诉你髓系细胞爱不爱扎堆。但现实中巨噬细胞、树突状细胞、单核细胞各自的空间偏好可能截然不同甚至同一类细胞中不同亚型也是各占山头。标签粒度太粗富集热图会变得“模糊而平庸”大概率是全部对角线偏亮其他位置都是灰色。也不是越细越好。某类细胞全切片只有30个超几何检验的统计功效很低容易从一个极小的样本里得到极高的显著富集但可复现性很差。实践中建议注释到能代表细胞功能的亚群级别同时保证每个亚群至少有足够细胞量。如果某个细分类别太小考虑把它并入上一级功能类别而不是直接删掉。标签的取名也要统一同一个项目里不要混着“T_cell”“T cell”“Tcell”几种写法否则会被当成完全不同的类别。5.3 大数据下的内存与性能到10万细胞以上nhood_enrich的反复检验会开始吃资源。更麻烦的是如果选择半径较大的邻域每个细胞邻域包含的邻居数量急剧上升超几何计算的时间也随之拉长。我的处理办法是在正式跑全量之前先随机抽样5000个细胞做一次预演。预演的目的不是得到最终结论而是检查空间邻居图构建耗时、结果表结构、热图效果并顺手确认参数没选错。预演通过后再跑全量。计算压力太大时还有一个更轻量的方案第一轮只针对感兴趣的细胞类型做。比如一个项目核心问题是“肿瘤细胞邻域是否富集特定成纤维细胞亚群”那可以把其他类型的细胞当作背景邻居的一部分保留但不逐一检验它们的投影。这本质上是用groupby聚合空间邻域背景输出矩阵的行列会大幅精简而感兴趣的配对不会丢。5.4 radius敏感性换个尺度结论就变了很多人在radius参数上一招鲜吃遍天但不同radius算出来的niche谱可能差很多。一个50像素半径在密度高的区域能覆盖一圈细胞在细胞稀的区域可能连最近的邻居都够不着而250像素半径可能把整个结构域里的细胞全算进邻居了。建议项目确定后至少做一次敏感性扫描取三个半径档位比如低、中、高分别跑nhood_enrich比较关键富集是否依然成立。for radius in [50, 100, 200]: sq.gr.spatial_neighbors( adata, coord_typegeneric, n_neighsNone, radiusradius, key_addedfradius_{radius} ) sq.gr.nhood_enrich( adata, groupbycell_type, nhoodsadata.obsp[fradius_{radius}_neighbors], # 具体key按版本调整 key_addedfnhood_enrich_{radius} )如果核心的富集配对在小半径时存在、大半径时消失这不一定有问题更可能说明这种共定位只在特定空间尺度成立比如细胞邻近接触层面而不是组织区域层面的共分布。这个尺度信息本身就是生物学结论的一部分写文章时甚至可以作为独立发现。5.5 批效应不是单细胞数据的专属问题空间数据同样有批效应表现形态却不太一样。不同切片之间的染色批次、切片位置、组织状态都会影响细胞类型注释质量进而影响邻域构成。判断批效应是否干扰niche结论的一个简单办法把热图按样本分别拆分比较核心富集对是否在两个样本里都显著。如果A样本里探索到“M2型巨噬细胞-调节T细胞共富集”但B样本完全不体现先别急着下结论说组织特异性先怀疑注释和染色差异。能做的规避措施不多保证注释流程在两个样本上一致避免一个样本人工校正过头必要时对每个样本单独跑nhood_enrich最后只在样本间稳定的富集对进入下游分析。6. 几个让niche分析更容易落地的个人经验6.1 把功能相关的细胞群组合后再验证刚开始跑nhood_enrich时我喜欢把所有注释好的细胞类型都丢进去结果热图动辄12乘12视觉上很壮观关键信息却被淹没。后来养成了一个习惯针对具体问题先设计“功能模块”而不是一股脑用全部类型。比如研究三阴性乳腺癌微环境我会把成纤维细胞、内皮细胞、周细胞组合成“间质细胞”把CD8 T、CD4 T、Treg组合成“T细胞群”巨噬细胞单独保留。这样富集热图大概只有5乘5但每一格都有清晰生物学指向也更容易得到稳定可解释的结果。组合的依据必须是问题导向不要按表达相似性随便合并。合并后的细胞类别可能会掩盖亚型的异质性所以更适合做第一轮宏观扫描发现目标配对后再回到细粒度注释去验证。6.2 把niche结果和配体-受体互作联合印证分子niche珍貴之处在于它能快速缩小候选细胞对的范围但它本质上只是空间共定位的统计描述不能证明细胞间真的有互作。有效的做法是先跑nhood_enrich得到富集的细胞对再跑Squidpy的配体-受体分析看显著的互作是否恰好发生在这些富集细胞对之间。我印象很深的一个结果是在一张空间切片里成纤维细胞和巨噬细胞在niche层面高度共富集但配体-受体分析显示两者间几乎没有显著互作真正接收成纤维细胞信号的是旁边的内皮细胞。这说明空间靠近并不等于功能互作反过来配体受体显著但niche不富集的配对也值得警惕可能来自远距离内分泌样信号或双方都密集在某个大区域内的巧合。两套方法交叉验证后留下的配对再进入功能实验会扎实很多。6.3 可视化前先想清楚要传达什么很多人把nhood_enrich的热图当成终点跑完就贴上去。实际上热图只是一个诊断工具真正面向读者的是“哪个细胞类型的邻域富集了哪种邻居”这个结论本身可视化要围绕结论调整。我常用的呈现方式有两种。一是对称热图配annotations适合总体展示全组织所有细胞类型的相互富集关系。二是直接挑出两三个重点细胞对把这些配对的niche分数映射到组织空间坐标上形成“双标记叠加图”。叠加图能让读者直观看到共富集发生在组织哪个位置——是弥散性散布还是集中在某个浸润前沿信息量远大于一个热图数字。如果想把niche分数用于伪时序或细胞状态分类建议先做scale再降维。原始富集矩阵往往存在较大的对角优势如果不标准化聚类会被细胞类型本身的自富集带偏而niche差异反而被掩盖。6.4 别把统计显著直接当生物学显著最后这一点想单独拿出来提醒。超几何检验在细胞数以万计、检验次数几十万时p值达到小数点后很多位并不意外。但一个统计学上极度显著的富集生物学上可能只是“这两个细胞类型在同一个解剖结构里分布重叠”的自然结果并不代表它们之间有主动的吸引或互作。下结论之前我会习惯问自己三个问题这个富集是否在所有重复样本稳定成立是否和已知的病理或解剖结构吻合是否能通过下游转录组变化或配体-受体信号得到独立证据三条里至少满足两条我才敢把它写进正文或讨论部分。在我自己的项目里分子niche最被低估的一个价值是“尺度感知”。一张切片上有组织区室层面的结构也有细胞接触层面的微环境不同尺度应该用不同半径去探测得到的结论完全可以互补。与其纠结哪组参数是“最优解”不如把不同尺度下的富集框架当成不同倍率的显微镜换倍率观察同一片组织各说各的道理。逐步踩过各种坑之后我现在跑分子niche的基本流程已经固定下来先按样本独立建邻居图用预演采样确认邻居图有效再跑多尺度富集用空间一致性、转录功能、同类型自洽性三层验证交叉检验最后只保留跨样本稳定且经配体受体佐证的细胞对。整个过程里最花时间的反而不是运行代码而是反复检查“这个富集还能被什么独立证据推翻”。被推翻的想法多了留下来的结论也就站得住脚了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑