单细胞测序做到今天最让分析人员头疼的其实不是拿不到数据而是同一批细胞里转录组告诉你“这群细胞像T细胞”表面蛋白却告诉你“它其实是个还没活化的naive亚群”两张皮对不上。过去几年Seurat生态里针对这类多模态数据的处理思路逐渐成熟其中跨模态整合Weighted Nearest Neighbor简称WNN几乎成了绕不开的一环。它做的事情很具体把同一批细胞测到的转录组、表面蛋白、染色质可及性等不同来源的信息按每个细胞自身的可信度加权后拼成一张统一的近邻图再拿这张图去做聚类和降维。适合谁看如果你手头有CITE-seq、多组学或者同批次的多模态数据已经能跑通单模态的Seurat标准流程但一遇到“两种模态聚类结果打架”“ADT维度怎么选”“权重图该不该信”这类问题就卡壳那这篇内容基本就是为你准备的。下面我按数据准备、实操流程、结果解读、排查踩坑四个部分把我在几个模拟项目里反复验证过的做法摊开讲。1. 跨模态整合到底在解决什么问题1.1 单模态分析在免疫和发育场景里的三个典型困境先说清楚痛点不然后面讲参数会觉得是在背文档。转录组聚类最大的问题之一是分辨率与生物学真实性之间的矛盾把resolution往上调CD4和CD8分开了但naive和memory也一起被切碎往下调亚群又糊成一片。第二是低表达基因的噪声像某些转录因子在单细胞里检出率极低靠它做标记基因基本是碰运气。第三是模态之间相互矛盾最常见的就是RNA聚类把一群细胞划进某个亚群但ADT上的谱系标记根本不支持这个判断。这些问题的根源在于单模态数据只捕捉了一个维度的信息而细胞状态本身是多维的。你做聚类时实际上是在一个信息不完整的空间里做距离判断噪声和缺失值都会把近邻关系带偏。1.2 多模态数据带来的信息增量具体在哪里拿最常见的CITE-seq场景来说同一批细胞同时测了mRNA和细胞表面蛋白。蛋白这一层的特点是表达量高、动态范围宽、检出稳定特别适合用来界定谱系和活化状态转录组这一层优势在于覆盖全基因组能发现蛋白面板覆盖不到的亚状态。两边的互补性非常强。问题在于不能简单地把两套矩阵拼起来做PCA。为什么因为RNA矩阵动辄两万维、稀疏、服从负二项分布ADT矩阵只有一两百维、稠密、走的是计数比例两者的量纲、噪声结构、维度数量完全不在一个量级。硬拼的结果就是高维的RNA把低维的ADT彻底淹没ADT的信息增量等于白测。WNN的思路正好绕开了这个问题它不拼接原始数据而是先让每个模态独立降维再在低维空间里为每个细胞分别评估“这个模态对它的区分度有多高”最后加权融合。1.3 WNN的加权思路为什么值得用每个细胞都会得到一组模态权重权重反映的是该细胞在对应模态的近邻结构里有多“可信”。举个直观的例子一个典型的B细胞ADT上CD19、CD20信号很强且干净蛋白模态对它的权重就会偏高而一个处于过渡态的细胞表面标记可能模糊转录组提供的近邻关系反而更稳RNA权重就占主导。这种细胞级别的自适应加权是WNN和早期“两模态各算一半”的粗暴融合最大的区别也是它能在真实数据里稳定出结果的关键。代价是计算量。WNN要为每个细胞做一次权重优化细胞数上万时内存和耗时都不小后面会专门讲资源控制。2. 数据准备与预处理的关键环节2.1 两种模态必须独立预处理不能共用一套流程这是最容易翻车的地方。很多新手图省事把ADT当成一个额外的assay塞进对象然后直接对整体做NormalizeData和ScaleData结果ADT的信号被RNA的标准化参数带偏聚类出来的权重图完全没法看。正确做法是按模态分别走各自适合的标准化路径。RNA用常规的LogNormalize选高变基因、缩放、PCAADT必须用CLRcentered log-ratio归一化而且margin参数要设成2。这里解释一下为什么CLR默认margin1是按特征也就是每个蛋白做中心化对数比这在蛋白数量少的时候会让每个蛋白的分布被强行拉平margin2是按细胞做意思是“同一个细胞内部各个蛋白的相对比例”这更符合ADT数据的生成机制——每个细胞的抗体捕获总量本身受实验条件影响细胞内的相对丰度才是生物学信号。library(Seurat) # 假设对象 obj 已经包含 RNA 和 ADT 两个 assay DefaultAssay(obj) - RNA obj - NormalizeData(obj, normalization.method LogNormalize, scale.factor 1e4) obj - FindVariableFeatures(obj, selection.method vst, nfeatures 2000) obj - ScaleData(obj, features rownames(obj)) obj - RunPCA(obj, features VariableFeatures(obj), npcs 50) DefaultAssay(obj) - ADT obj - NormalizeData(obj, normalization.method CLR, margin 2) # ADT特征少缩放时用全部特征即可 obj - ScaleData(obj, features rownames(obj)) obj - RunPCA(obj, features rownames(obj), npcs 30, reduction.name pca_adt, reduction.key pcaadt_)注意PCA的reduction名字要显式改掉否则会和RNA的pca冲突后面FindMultiModalNeighbors取reduction时就会报错或者取错。2.2 barcode对齐是隐形炸弹多模态数据里RNA和ADT虽然是同一批液滴但建库和测序是分开的最后合并时靠barcode匹配。如果上游已经给了合并好的对象这一步通常没问题但如果你自己从两个矩阵文件读进来再合并一定要先检查两边的细胞数是否一致、barcode命名规则是否统一有的流程会给barcode加后缀有的不会。# 合并前先对齐 common_cells - intersect(colnames(rna_mat), colnames(adt_mat)) cat(RNA细胞数:, ncol(rna_mat), ADT细胞数:, ncol(adt_mat), 交集:, length(common_cells), \n) obj - CreateSeuratObject(counts rna_mat[, common_cells], assay RNA) adt_assay - CreateAssayObject(counts adt_mat[, common_cells]) obj[[ADT]] - adt_assay注意如果交集细胞数比任一单模态少了一大截先别急着往下跑回头查barcode格式。我在一个模拟项目里遇到过RNA的barcode带“-1”后缀、ADT不带的情况交集只剩个位数排查了半天才发现是命名规则不一致。2.3 模态特异性的质控门槛要分开设RNA的质控看nFeature_RNA、percent.mtADT的质控看总计数和背景蛋白。ADT的背景信号主要来自非特异性结合判断方法是看那些理论上不该表达的对照蛋白比如同型对照的分布。如果某个细胞的ADT总计数异常高但对照蛋白也高大概率是背景污染应该剔除。我的经验是ADT的质控阈值不要照搬RNA。RNA可以卡nFeature_RNA 200ADT这边更该关注的是每个细胞检出的蛋白数和对照蛋白占比。一般要求检出蛋白数不少于面板总数的三到四成对照蛋白占比控制在个位数百分比以内。这些数字不是硬标准得看你用的抗体面板和建库质量最好先画几张分布图再定。3. WNN整合的完整实操流程3.1 构建多模态对象并确认两个reduction都在在跑整合之前先确认obj里同时存在pca和pca_adt两个reduction且维度合理。RNA的PCA维度按常规经验取肘部拐点附近比如30ADT因为特征数本来就不多常见面板一百多个蛋白PCA维度取到15到20就够了取太多会把噪声也当成主成分反而稀释信号。# 确认reduction存在 Reductions(obj) # [1] pca pca_adt # 看ADT的碎石图判断维度 ElbowPlot(obj, reduction pca_adt, ndims 30)3.2 核心函数FindMultiModalNeighbors的参数怎么定这是整个流程的核心。关键参数就三个reduction.list、dims.list、modality.weight.name。reduction.list按顺序给两个模态的降维结果dims.list一一对应给维度范围modality.weight.name是让函数把算出来的权重存进meta.data方便后面可视化和解读。obj - FindMultiModalNeighbors( obj, reduction.list list(pca, pca_adt), dims.list list(1:30, 1:18), modality.weight.name RNA.weight, verbose TRUE )跑完之后obj里会多出一个weighted.nn的邻居对象还有一个wsnn的共享邻居图。这两个是后续降维和聚类的输入不要搞混。weighted.nn用于RunUMAPwsnn用于FindClusters。3.3 基于加权图的降维和聚类# 用加权近邻图做UMAP obj - RunUMAP(obj, nn.name weighted.nn, reduction.name wnn.umap, reduction.key wnnUMAP_) # 用共享加权图做聚类 obj - FindClusters(obj, graph.name wsnn, algorithm 3, resolution 0.6, verbose FALSE) # 出图 DimPlot(obj, reduction wnn.umap, label TRUE, repel TRUE)这里有几个细节值得说。RunUMAP必须指定nn.nameweighted.nn否则它默认用RNA的pca等于白做整合。FindClusters必须指定graph.namewsnn不指定的话它会去找默认的RNA_snn聚类结果和加权图无关。这两处是最常见的“跑了但没生效”的原因我在帮别人看代码时见过不止一次。algorithm3是SLM算法比默认的Louvain在大图上更稳、更快多模态数据细胞数通常不小建议直接用它。resolution从0.6起步根据聚类数和标记基因的合理性再调。3.4 两模态之外的第三种整合场景桥接整合WNN解决的是“同一批细胞、多个模态”的问题。但实际项目里还有一种常见情况你有一批CITE-seq数据作为参考另有一批只有RNA的普通scRNA-seq数据想让后者借助前者的蛋白信息做标签迁移。这时候用的不是WNN而是FindBridgeIntegration。它的思路是先拿一个同时有两种模态的桥接数据集分别建立两套锚点再把query数据映射进去。这个函数对桥接数据的质量要求比较高桥接数据最好覆盖你要研究的细胞类型否则映射出来的标签可信度会打折扣。# 桥接整合示意桥接数据集需同时含RNA和ADT bridge - FindBridgeIntegration( anchorset anchors, reference bridge_obj, query query_obj, reduction wnn.umap )这条路径和WNN是互补关系不是替代关系选哪条取决于你手头数据的模态结构。4. 结果解读与下游分析4.1 权重图该怎么看才算看懂了权重算出来之后最直接的检查方式是按聚类分组画小提琴图。RNA.weight的值在0到1之间越接近1说明这个类群的近邻结构越依赖转录组。VlnPlot(obj, features RNA.weight, group.by seurat_clusters, pt.size 0.1) NoLegend()正常的模式应该是不同类群的权重有明显分化。比如某些亚群权重接近0.8说明转录组在这些细胞上区分度好另一些亚群权重只有0.2到0.3说明蛋白信息在这群细胞里起了主导作用。如果你看到所有类群的权重都挤在0.5附近那就要警惕了——要么两个模态的信息高度冗余比如蛋白面板选得和RNA标记基因高度重合要么某个模态的预处理出了问题比如ADT的PCA维度取太少导致蛋白结构没被充分捕捉。提示权重图是一张诊断图不是一个“好看就行”的展示图。权重分布异常往往比聚类结果异常更早暴露数据问题。4.2 整合后的差异分析和标记基因聚类定下来之后差异分析要在RNA assay上做这是常规操作。但如果你的目的是找“蛋白层面特异、转录组层面不明显”的亚群就得把两个模态的标记基因都拿出来对照看。Idents(obj) - seurat_clusters # RNA标记基因 rna_markers - FindAllMarkers(obj, assay RNA, only.pos TRUE, min.pct 0.25, logfc.threshold 0.25) # ADT标记蛋白 DefaultAssay(obj) - ADT adt_markers - FindAllMarkers(obj, assay ADT, only.pos TRUE, min.pct 0.1, logfc.threshold 0.1)ADT的FindAllMarkers参数要和RNA区分开min.pct可以放低因为蛋白检出本身就稀疏logfc阈值也要放低因为CLR变换后的数值范围比RNA小得多。直接套用RNA的参数很可能一个标记蛋白都找不出来。两套结果对照着看理想状态是每个类群既有RNA标记又有蛋白标记且两者在生物学上能对上。如果某类群只有蛋白标记、RNA上找不到明显差异基因这类群往往值得单独拎出来深挖因为它可能是转录组分辨率抓不到的稀有状态。4.3 权重信息怎么反哺下游分析权重不只是诊断工具还能直接用于下游筛选。一个实用的做法是把权重极端偏斜的细胞单独提出来看。比如RNA.weight 0.2的细胞说明这类细胞的近邻关系几乎全靠蛋白支撑转录组信号很弱。这类细胞有可能是低质量细胞RNA捕获差也有可能是真实的特殊状态比如某些终末分化细胞转录组整体下调。判断方法是对这批细胞单独看线粒体比例和nFeature_RNA如果质控指标正常那更可能是真实生物学信号值得保留。# 提取RNA权重偏低的细胞 low_rna - WhichCells(obj, expression RNA.weight 0.2) summary(obj$nFeature_RNA[low_rna]) summary(obj$percent.mt[low_rna])这一步我建议每个项目都做一遍花不了几分钟但经常能发现一些被默认流程忽略掉的细胞群。5. 常见问题与排查实录5.1 权重分布一边倒怎么办最常见的现象是所有权重都接近0或都接近1。前者说明ADT模态在近邻结构里几乎没贡献后者说明RNA独大。排查顺序我一般是这样走先确认两个PCA的维度是否匹配。dims.list里第一个给30、第二个给18如果实际算出来的pca_adt只有15维函数会报错或者截断权重计算就不可靠了。再看ADT的CLR是否用了margin2用错margin会让蛋白信号被拉平。最后看抗体面板和细胞类型的匹配度——如果你研究的细胞类型正好不在面板覆盖范围内ADT自然提供不了区分度这时候权重偏低是合理的不该硬调参数。现象可能原因排查动作权重全部0.3ADT预处理异常或面板不匹配检查CLR的margin参数、PCA维度权重全部0.8ADT信号被噪声淹没检查ADT质控、对照蛋白占比权重集中在0.5两模态信息高度冗余对照标记基因判断是否需要WNN部分类群权重缺失该类群细胞在某一模态被过滤回溯质控步骤5.2 聚类结果和单模态对不上该信哪个这个问题没有标准答案得看你的生物学问题。如果两个单模态聚类只是在亚群细分上有差异WNN的结果通常更稳因为它综合了两边的证据。但如果WNN把两个在RNA上分得很开的类群合并了那就要怀疑是不是ADT的噪声把距离拉近了。我的处理办法是做一次三方对照RNA单独聚类、ADT单独聚类、WNN聚类把三套标签放在同一张UMAP上画并排图。差异最大的那批细胞单独提出来看它们的权重值是多少。权重偏低的细胞WNN的归属更可信权重居中的细胞说明两个模态给出的信号本身就冲突这类细胞往往处于过渡态单独标出来做轨迹分析反而更有价值。5.3 内存和耗时报错怎么压细胞数上万之后FindMultiModalNeighbors很容易吃满内存。几个实际有效的压缩手段第一把RNA的PCA维度从50降到30ADT从30降到15维度降下来内存占用是平方级的下降第二在跑之前把不必要的assay清掉比如原始counts如果已经用不上可以只保留data第三如果只是探索性分析可以先随机下采样一部分细胞跑通流程确认参数合理后再上全量。# 下采样探索 set.seed(42) subset_cells - sample(colnames(obj), size min(5000, ncol(obj))) obj_sub - subset(obj, cells subset_cells)注意下采样只用于调参探索最终结果一定要用全量数据重跑。权重是细胞级别的下采样会改变近邻结构两者结果不能混用。还有一种情况是跑完之后对象体积暴涨原因是weighted.nn和wsnn这两个图对象本身占空间。如果后续不再需要重新聚类可以在保存前把它们删掉只保留聚类标签和UMAP坐标。5.4 一个容易被忽略的坑批次效应和模态整合的先后顺序如果数据跨多个批次很多人会先做批次校正再做WNN这个顺序本身没问题但要注意校正的对象。批次校正应该在每个模态内部单独做而不是在整合之后对加权图做。因为WNN的权重是针对细胞算的如果先整合再校正校正算法会破坏权重结构。正确顺序是RNA内部做批次校正比如用CCA或Harmony→ ADT内部单独处理 → 两个模态都干净了再跑FindMultiModalNeighbors。这个顺序我在一个跨批次模拟项目里验证过反过来做的话权重图会出现明显的批次相关偏斜同一类群在不同批次里权重差异很大聚类也跟着乱。6. 几个我实际踩过的经验点最后分享几个文档里不会写、但实际项目里很影响结果的小细节。第一ADT面板的对照蛋白一定要参与预处理但不要参与聚类。对照蛋白在CLR归一化时需要保留因为它影响细胞内的相对丰度计算但在选PCA特征时要排除掉否则对照蛋白的噪声会混进主成分。第二WNN的resolution不要和单模态共用一套经验值。因为加权图的近邻结构比单模态更紧致同样的resolution在WNN上往往聚出更多类群。我的习惯是先固定0.6跑一版看聚类数是否落在预期范围再上下微调。第三保存对象时把权重列单独导出成csv。权重是解读结果的关键证据但Seurat对象动辄几个G合作者不一定愿意为了看一列权重去加载整个对象。导出成表格配合聚类标签一起给出去沟通效率会高很多。weight_df - data.frame( cell colnames(obj), cluster obj$seurat_clusters, rna_weight obj$RNA.weight ) write.csv(weight_df, wnn_weights.csv, row.names FALSE)第四跑完WNN后回头再看一眼单模态的标记基因。整合的价值在于互补如果整合后某个类群的标记基因在两个模态上都能自洽解释那这个类群的置信度就很高如果只有一个模态支持建议在报告里标注出来别当成铁结论。这套流程我在几个模拟项目里来回跑过参数不是死的但排查逻辑是通用的先看预处理干不干净再看权重分不分散最后看聚类和标记基因对不对得上。三步走完绝大多数“整合了但结果怪”的问题都能定位到具体环节。