1. 多切片空间数据整合,为什么单靠 merge 会翻车
做空间转录组的人迟早会撞上同一个问题:手里有三四张切片,可能是同一组织连续切片,也可能是不同样本、不同 FOV,想放在一起看细胞类型分布和空间结构。第一反应通常是merge()一把梭,把几个 Seurat 对象拼起来,然后 NormalizeData、FindVariableFeatures、RunPCA、RunUMAP 走一遍。跑完一看 UMAP,切片之间泾渭分明,同一类细胞被拆成好几团,空间散点图上坐标也各说各话。
这不是你参数调错了,而是merge()只做行拼接,它不处理批次效应,也不管不同切片之间的坐标系统。空间数据比普通单细胞多了一层麻烦:每个细胞除了表达谱,还有 x、y 坐标和所属切片(FOV/sample)。多切片整合要同时解决两件事——表达层面的批次校正,以及空间层面的坐标对齐。前者靠整合算法,后者靠统一坐标系或按切片分面展示。
我试过直接 merge 三张 CODEX 淋巴结切片,UMAP 上 B 细胞分成三簇,CD34 血管内皮也散开,完全没法比较。后来换成 Seurat 的整合流程,先按切片做 anchor 找对应关系,再统一降维,同一类细胞才聚到一起。这篇就把这套流程拆成可复制的步骤,包括对象构建、NormalizeData 参数、FindVariableFeatures 设置,以及整合前后 UMAP 和空间散点的对比验证。
适合谁看:已经跑过单切片 Seurat 空间分析,现在要处理多样本联合的人;或者正在被批次效应和坐标错位卡住的人。核心检索词就是 Seurat 空间数据多切片整合,下面所有代码都在本地 R 环境可复现。
先说清楚一个前提:空间数据的整合和普通 scRNA-seq 整合思路一致,但多了坐标维度。Seurat v5 里空间对象用SpatialExperiment或Seurat对象承载,LoadAkoya()、LoadVizgen()、LoadNanostring()这些函数会把坐标和表达一起读进来。多切片时,每个切片是一个独立对象,整合的目标是让表达谱可比、坐标可对照。
2. TaoToken 前置:统一 Key 管理多切片分析里的模型调用
空间整合流程本身是本地 R 计算,为什么还要提 TaoToken?因为实际项目里,你不可能只跑 Seurat。多切片分析经常伴随几个需要模型能力的环节:用大模型辅助解读 cluster marker、生成分析报告、把 R 脚本里的报错贴给模型排查、或者用 coding agent 批量改参数。这些调用如果每个工具各配一套 Key,管理起来很乱,额度也分散。
TaoToken 在这里的角色是统一入口:一个 Key 覆盖模型对话、coding plan、API 调用。官网在 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 端点是 https://taotoken.net/api 。你可以在控制台生成 Key,然后在不同工具里复用同一个 Key,不用为每个客户端单独申请。
具体到空间整合场景,我通常这样用:Seurat 跑完得到 cluster 和 marker 列表,把 marker 贴到模型对话里让它帮我判断细胞类型;R 脚本报错时,把报错和上下文发给模型定位;如果要写批量处理多个切片的循环脚本,用 coding plan 让 agent 直接改代码。这些都不影响本地 Seurat 计算,只是把「解读和排障」这一步接上模型。
需要拿 Key 的话,进控制台创建,路径是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite 。创建后在 API Keys 页面复制,地址 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 。文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,里面有各语言的调用示例。
如果你用的是 Claude Code 这类终端 agent 做脚本维护,接入配置在 https://taotoken.net/claude-code-anthropic?utm_source=taotoken_aicg_blog_end&utm_content=claudecode&utm_campaign=rewrite 。模型对话入口在 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=model-chat&utm_campaign=rewrite ,适合快速贴 marker 问类型。长期跑编码和 agent 任务可以用 coding plan,地址 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 。
这里要强调:TaoToken 不替代 Seurat,也不碰你的生产数据。它只是把模型调用这一层统一起来,让你在整合流程里遇到需要「问模型」的环节时,不用切换多个 Key。下面进入正题,先构建多切片对象。
3. 可复制配置:多切片 Seurat 对象构建与整合参数
这一节给完整可跑的配置。假设你有三个切片,分别用LoadAkoya()读入,得到slice1、slice2、slice3。每个对象里包含蛋白强度矩阵和坐标。先看单切片的标准处理,再做多切片整合。
单切片标准化,蛋白数据用 CLR,margin=2 表示按细胞标准化(对每个细胞的标记做中心对数比)。这一步和官方 vignette 一致:
library(Seurat) library(ggplot2) # 单切片:CLR 标准化 + 缩放 slice1 <- NormalizeData(slice1, normalization.method = "CLR", margin = 2) slice1 <- ScaleData(slice1) VariableFeatures(slice1) <- rownames(slice1) # panel 小,全部当可变特征 slice1 <- RunPCA(slice1, npcs = 20, verbose = FALSE)多切片时,不要先 merge 再标准化。正确顺序是:每个切片单独 NormalizeData,然后选整合锚点。Seurat v5 用IntegrateLayers(),老版本用FindIntegrationAnchors()+IntegrateData()。下面给 v5 写法:
# 每个切片单独标准化 slices <- list(slice1 = slice1, slice2 = slice2, slice3 = slice3) slices <- lapply(slices, function(x) { x <- NormalizeData(x, normalization.method = "CLR", margin = 2) x <- ScaleData(x) VariableFeatures(x) <- rownames(x) x <- RunPCA(x, npcs = 20, verbose = FALSE) x }) # 合并为一个对象,保留切片信息 merged <- merge(slices[[1]], y = slices[-1], add.cell.ids = names(slices)) merged[["RNA"]] <- split(merged[["RNA"]], f = merged$orig.ident) # 找锚点并整合 merged <- FindVariableFeatures(merged, selection.method = "vst", nfeatures = 2000) anchors <- FindIntegrationAnchors(object.list = slices, dims = 1:20) integrated <- IntegrateData(anchorset = anchors, dims = 1:20)如果你用 Seurat v5 的IntegrateLayers(),配置片段如下,注意method = HarmonyIntegration或CCAIntegration按需选:
merged <- NormalizeData(merged, normalization.method = "CLR", margin = 2) merged <- FindVariableFeatures(merged, selection.method = "vst", nfeatures = 2000) merged <- ScaleData(merged) merged <- RunPCA(merged, npcs = 20, verbose = FALSE) merged <- IntegrateLayers( object = merged, method = CCAIntegration, orig.reduction = "pca", new.reduction = "integrated.cca", verbose = FALSE )关键参数说明,用表格对照:
| 参数 | 作用 | 空间数据建议 |
|---|---|---|
| normalization.method | 标准化方法 | 蛋白用 CLR,转录本用 LogNormalize |
| margin | CLR 标准化方向 | margin=2 按细胞,margin=1 按特征 |
| nfeatures | 可变特征数 | panel 小时可全选,转录本取 2000 |
| dims | 参与整合的 PC 数 | 20 起步,看 elbow plot 调整 |
| new.reduction | 整合后降维名 | 后续 UMAP 用这个 |
整合完成后,用整合后的降维跑 UMAP 和聚类:
integrated <- RunUMAP(integrated, reduction = "integrated.cca", dims = 1:20, verbose = FALSE) integrated <- FindNeighbors(integrated, reduction = "integrated.cca", dims = 1:20, verbose = FALSE) integrated <- FindClusters(integrated, resolution = 0.4, n.start = 1, verbose = FALSE)坐标对齐方面,空间散点用ImageDimPlot()按切片分面,或者手动统一坐标范围。如果切片来自同一组织连续切片,可以按 FOV 分别画,再拼图对比。不要强行把不同 FOV 的坐标叠在一起,除非你做过配准。
4. 验证请求:整合前后 UMAP 与空间散点对比
配置跑完,必须验证整合是否真的消除了批次效应。验证动作分两步:UMAP 对比和空间散点对比。
先看整合前。用 merge 后未整合的对象跑 UMAP,按切片着色:
# 整合前:merge 后直接降维 merged <- RunUMAP(merged, dims = 1:20, verbose = FALSE) p_before <- DimPlot(merged, group.by = "orig.ident", shuffle = TRUE) + ggtitle("Before integration")再看整合后:
p_after <- DimPlot(integrated, group.by = "orig.ident", shuffle = TRUE) + ggtitle("After integration") p_before + p_after判断标准:整合前如果三个切片的细胞在 UMAP 上分成三块,说明批次效应明显;整合后同一类细胞应该混在一起,切片标签不再主导聚类。如果整合后还是分块,检查 dims 是否太小、nfeatures 是否够、或者切片间生物学差异本身很大。
空间散点对比用ImageDimPlot(),按 cluster 着色,分面看每个切片:
# 空间散点:整合后按 cluster 着色 ImageDimPlot(integrated, fov = "slice1", cols = "parade") + ggtitle("slice1 clusters") ImageDimPlot(integrated, fov = "slice2", cols = "parade") + ggtitle("slice2 clusters")如果你要对比整合前后的空间结构,可以分别画 marker 的空间表达。比如 CD34 血管内皮、CD21 B 细胞、Lyve1 淋巴内皮:
p1 <- ImageFeaturePlot(integrated, fov = "slice1", features = c("CD34", "CD21", "Lyve1"), min.cutoff = "q10", max.cutoff = "q90") p2 <- ImageDimPlot(integrated, fov = "slice1", cols = "parade") p1 + p2验证成功的标志:整合后 UMAP 上切片混合,同一 cluster 在不同切片的空间位置上呈现相似的组织结构,比如内皮细胞沿血管走行、B 细胞聚成生发中心。如果某个 cluster 只在单切片出现,先别急着当批次,可能是真实生物学差异,结合 marker 判断。
这一步如果 marker 列表看不懂,可以把 cluster 的 top marker 贴到模型对话里问细胞类型,入口在 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=model-chat&utm_campaign=rewrite 。把 marker 和表达量一起发过去,让它帮你对照已知标记。
5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth
跑整合流程时,报错分两类:Seurat/R 本身的错,和模型调用层的错。分开说。
Seurat 侧常见问题:
Error in FindIntegrationAnchors: object.list must be a list of Seurat objects。检查slices是不是 list,每个元素是不是 Seurat 对象。如果你用split()拆过 RNA assay,注意object.list要传原始对象列表,不是拆分后的。
Error: VariableFeatures not set。整合前必须对每个切片设 VariableFeatures。蛋白 panel 小的时候直接VariableFeatures(x) <- rownames(x),转录本用FindVariableFeatures()。
Error in IntegrateData: dims exceeds number of PCs。dims 不能超过 RunPCA 的 npcs。npcs=20 就写 dims=1:20。
模型调用侧常见报错,这些和 TaoToken 接入相关:
401 Unauthorized。Key 没带对或过期。检查请求头里 Authorization 是否为Bearer <你的Key>,Key 从 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 复制,注意别带空格。
local proxy failed。本地代理配置和实际网络环境不匹配。检查你的 HTTP_PROXY/HTTPS_PROXY 环境变量,如果不需要代理就清掉。这个报错和 TaoToken 本身无关,是本地环境问题。
Error reading choices或reading choices相关。通常是响应体解析失败,可能是模型返回格式和客户端预期不一致。检查你用的客户端是否支持该模型,或者换模型对话入口测试 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=model-chat&utm_campaign=rewrite 。
OAuth相关报错。如果你用 Claude Code 接入,OAuth 流程没走完或 token 过期。重新走一遍接入配置,参考 https://taotoken.net/claude-code-anthropic?utm_source=taotoken_aicg_blog_end&utm_content=claudecode&utm_campaign=rewrite 。配置时三件套要写全:Base URL、Key、Model ID。Base URL 用 https://taotoken.net/api ,Key 用控制台生成的,Model ID 按文档填。
如果你用 Cline MCP 或 Codex 的 auth.json,同样三件套不能少。auth.json 里 Base URL 指向 https://taotoken.net/api ,Key 填进去,Model ID 按你选的模型写。少任何一个都会报认证或模型找不到。
排查顺序建议:先确认 Seurat 对象结构没问题,再确认模型调用层 Key 和 Base URL 正确。两边分开定位,别混在一起查。
6. 把整合流程接上模型能力,后续怎么走
多切片整合跑通后,下一步通常是批量处理和报告生成。你可以把上面的配置封装成函数,对任意数量的切片循环执行。整合后的对象保存成 RDS,方便下次直接读。
如果要把这套流程做成可复用的脚本,用 coding plan 让 agent 帮你重构和加注释,地址 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 。把现有脚本贴进去,让它改成函数式、加参数校验、补日志。
模型调用统一走 https://taotoken.net/api ,Key 在控制台管理。文档里有 R 和 Python 的调用示例,https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 。需要新 Key 或查看额度,进 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite 。
最后提醒一个实操细节:空间数据整合后,坐标信息不会自动对齐,ImageDimPlot()还是按原 FOV 画。如果你要跨切片比较空间结构,建议按 cluster 分别画每个切片的散点,再横向拼图,而不是强行叠加坐标。整合解决的是表达层面的可比性,空间层面的对齐需要额外的配准步骤,那是另一个话题了。