Seurat v5 参考数据集映射教程¶
基于 Seurat v5.5.1 官方教程,使用 PBMC 3k 数据模拟参考映射场景。
将数据拆分为参考集(70%)和查询集(30%),完整演示标签转移与 UMAP 投影流程。
目录¶
- 1. 背景与原理
- 2. 数据准备与拆分
- 3. 构建参考集
- 4. 查找转移锚点
- 5. 标签转移
- 6. 评估预测准确性
- 7. Marker 基因验证
- 8. UMAP 投影(MapQuery)
- 9. 参考集与查询集对比
- 10. 预测置信度分析
- 11. 合并可视化
- 12. 总结
- 附录: 完整脚本
1. 背景与原理¶
1.1 什么是参考数据集映射?¶
当获得一批新的单细胞数据(查询集),但没有细胞类型注释时,可以利用已有的、已注释的参考数据集,通过标签转移(Label Transfer)将细胞类型注释从参考集映射到查询集。
1.2 核心函数¶
| 函数 | 作用 |
|---|---|
FindTransferAnchors |
在参考集和查询集之间寻找锚点(anchor)对 |
TransferData |
基于锚点转移标签或连续变量 |
MapQuery |
一步完成标签转移 + 嵌入整合 + UMAP 投影 |
1.3 与数据整合的区别¶
- 数据整合(阶段3):去除批次效应,让多批次数据在同一空间混合
- 参考映射:将查询集投影到参考集的 UMAP 空间,转移已有注释
2. 数据准备与拆分¶
使用阶段1完成的 PBMC 3k 已注释数据,随机拆分为参考集(70%)和查询集(30%):
# 加载已注释的 PBMC 数据
pbmc <- readRDS("output/pbmc3k_final.rds")
pbmc$seurat_annotations <- Idents(pbmc)
# 随机拆分
set.seed(42)
ref_idx <- sample(1:ncol(pbmc), size = floor(ncol(pbmc) * 0.7))
pancreas.ref <- subset(pbmc, cells = cell_ids[ref_idx]) # 1846 细胞
pancreas.query <- subset(pbmc, cells = cell_ids[-ref_idx]) # 792 细胞
3. 构建参考集¶
对参考集进行标准预处理(归一化、找高变基因、PCA、聚类、UMAP):
pancreas.ref <- NormalizeData(pancreas.ref, verbose = FALSE)
pancreas.ref <- FindVariableFeatures(pancreas.ref, verbose = FALSE)
pancreas.ref <- ScaleData(pancreas.ref, verbose = FALSE)
pancreas.ref <- RunPCA(pancreas.ref, verbose = FALSE)
pancreas.ref <- FindNeighbors(pancreas.ref, dims = 1:30, verbose = FALSE)
pancreas.ref <- FindClusters(pancreas.ref, verbose = FALSE)
pancreas.ref <- RunUMAP(pancreas.ref, dims = 1:30, verbose = FALSE)
参考集 UMAP(已知注释)¶
4. 查找转移锚点¶
pancreas.anchors <- FindTransferAnchors(
reference = pancreas.ref,
query = pancreas.query,
dims = 1:30,
reference.reduction = "pca"
)
锚点的作用:锚点是参考集和查询集之间表达模式高度相似的细胞对,通过这些锚点可以建立跨数据集的映射关系,从而将参考集的标签转移到查询集。
5. 标签转移¶
predictions <- TransferData(
anchorset = pancreas.anchors,
refdata = pancreas.ref$seurat_annotations,
dims = 1:30
)
pancreas.query <- AddMetaData(pancreas.query, metadata = predictions)
TransferData 不仅返回预测的细胞类型标签(predicted.id),还返回每个预测的置信度分数(prediction.score.max)。
6. 评估预测准确性¶
6.1 整体准确率¶
由于使用的是同一批 PBMC 数据拆分,真实标签已知,可以直接评估预测准确率:92.4%。
6.2 各细胞类型预测准确率¶
6.3 混淆矩阵¶
混淆矩阵展示了真实标签与预测标签的对应关系。对角线上的数值表示预测正确的细胞数:
准确率达 92.4%,少数错误主要发生在 Naive CD4 T 和 Memory CD4 T 之间的混淆,这是 T 细胞亚型本身表达模式接近导致的。
7. Marker 基因验证¶
用经典 marker 基因在小提琴图上验证预测结果是否合理:
markers <- c("MS4A1", "CD3D", "CD3E", "IL7R", "CD14", "LYZ", "GNLY", "NKG7", "FCGR3A", "PPBP")
VlnPlot(pancreas.query, features = markers, group.by = "predicted.id", ncol = 5, pt.size = 0)
每种预测细胞类型的 marker 基因表达模式符合预期,进一步验证了标签转移的可靠性。
8. UMAP 投影(MapQuery)¶
MapQuery 是 Seurat v5 的新函数,一步完成标签转移 + 嵌入整合 + UMAP 投影:
# 参考集 UMAP 需要保存模型
pancreas.ref <- RunUMAP(pancreas.ref, dims = 1:30, reduction = "pca",
return.model = TRUE, verbose = FALSE)
# MapQuery 一步到位
pancreas.query <- MapQuery(
anchorset = pancreas.anchors,
reference = pancreas.ref,
query = pancreas.query,
refdata = list(celltype = "seurat_annotations"),
reference.reduction = "pca",
reduction.model = "umap"
)
MapQuery 的优势:
- 将查询集直接投影到参考集的 UMAP 空间
- 无需在查询集上重新计算 UMAP
- 保持查询集与参考集在同一坐标系中
9. 参考集与查询集对比¶
9.1 参考集注释 vs 查询集预测¶
9.2 查询集:真实标签 vs 预测标签¶
左侧为查询集的真实细胞类型标签,右侧为通过锚点转移得到的预测标签,两者高度一致。
10. 预测置信度分析¶
ggplot(pancreas.query@meta.data, aes(x = predicted.id, y = prediction.score.max)) +
geom_boxplot(fill = "#4C9BE8", alpha = 0.7) +
geom_jitter(width = 0.2, alpha = 0.3, size = 0.5)
不同细胞类型的预测置信度都很高,B 细胞和 Platelet 的预测最为确信,T 细胞亚型(Naive vs Memory)的置信度稍低,符合生物学预期。
11. 合并可视化¶
11.1 查询集投影到参考 UMAP¶
11.2 查询集在参考空间中的分布¶
12. 总结¶
核心流程¶
参考集(已注释)-> 预处理 + PCA + UMAP(保存模型)
|
查询集(未注释)-> 预处理 -> FindTransferAnchors
|
TransferData -> 预测标签 + 置信度
|
MapQuery -> 投影到参考 UMAP 空间
关键知识点¶
- FindTransferAnchors 是标签转移的核心,寻找跨数据集锚点对
- TransferData 可以转移分类标签或连续变量
- MapQuery 一步完成标签转移 + UMAP 投影,是 Seurat v5 推荐方法
- 预测准确率取决于参考集注释质量和数据集之间的生物学相似性
- 本教程使用同源数据拆分,准确率高达 92.4%;真实场景中准确率通常在 80-95% 之间
附录: 完整脚本¶
mapping_vignette.R¶
# ============================================================
# Seurat v5 阶段4-A:参考数据集映射 (Mapping & Annotation)
# ============================================================
# 基于官方教程:https://satijalab.org/seurat/articles/integration_mapping
# 由于 panc8 数据集下载超时,使用已有的 PBMC 3k 数据模拟参考映射场景
# 将 PBMC 数据拆分为参考集(70%)和查询集(30%),
# 演示完整的标签转移 + UMAP 投影流程
# ============================================================
library(Seurat)
library(ggplot2)
library(patchwork)
# 设置工作目录
setwd("E:/BaiduNetdiskDownload/生物信息学/pbmc3k_tutorial")
output_dir <- "output/mapping"
dir.create(output_dir, showWarnings = FALSE, recursive = TRUE)
cat("=== Seurat v5 阶段4-A:参考数据集映射 ===\n\n")
# ============================================================
# 1. 加载已注释的 PBMC 数据
# ============================================================
cat("## 1. 加载已注释的 PBMC 数据\n\n")
# 加载阶段1完成的带有细胞类型注释的 Seurat 对象
pbmc <- readRDS("output/pbmc3k_final.rds")
# 将 active.ident(细胞类型注释)保存为 metadata 列
pbmc$seurat_annotations <- Idents(pbmc)
cat(sprintf("加载完成:共 %d 个细胞,%d 个基因\n", ncol(pbmc), nrow(pbmc)))
cat(sprintf("已知细胞类型:%s\n", paste(levels(pbmc$seurat_annotations), collapse = ", ")))
# 保存图片函数
save_plot <- function(filename, plot_obj, width = 10, height = 8) {
filepath <- file.path(output_dir, filename)
ggsave(filepath, plot_obj, width = width, height = height, dpi = 150, bg = "white")
cat(sprintf(" 已保存: %s\n", filename))
}
# ============================================================
# 2. 拆分为参考集和查询集
# ============================================================
cat("\n## 2. 拆分为参考集和查询集\n\n")
set.seed(42)
cell_ids <- colnames(pbmc)
n_cells <- length(cell_ids)
# 70% 参考集,30% 查询集
ref_idx <- sample(1:n_cells, size = floor(n_cells * 0.7))
ref_cells <- cell_ids[ref_idx]
query_cells <- cell_ids[-ref_idx]
cat(sprintf("参考集:%d 个细胞\n", length(ref_cells)))
cat(sprintf("查询集:%d 个细胞\n", length(query_cells)))
# 提取参考集和查询集
pancreas.ref <- subset(pbmc, cells = ref_cells)
pancreas.query <- subset(pbmc, cells = query_cells)
# 确保使用 seurat_annotations 作为 Idents
Idents(pancreas.ref) <- "seurat_annotations"
Idents(pancreas.query) <- "seurat_annotations"
# 记录参考集的真实细胞类型(用于后续验证)
cat(sprintf("\n参考集细胞类型分布:\n"))
print(table(pancreas.ref$seurat_annotations))
cat(sprintf("\n查询集真实细胞类型分布(用于验证预测准确性):\n"))
print(table(pancreas.query$seurat_annotations))
# 保存查询集的真实标签用于后续比较
query_true_labels <- pancreas.query$seurat_annotations
# ============================================================
# 3. 构建参考集(预处理 + 聚类 + UMAP)
# ============================================================
cat("\n## 3. 构建参考集\n\n")
# 参考集预处理
pancreas.ref <- NormalizeData(pancreas.ref, verbose = FALSE)
pancreas.ref <- FindVariableFeatures(pancreas.ref, verbose = FALSE)
pancreas.ref <- ScaleData(pancreas.ref, verbose = FALSE)
pancreas.ref <- RunPCA(pancreas.ref, verbose = FALSE)
pancreas.ref <- FindNeighbors(pancreas.ref, dims = 1:30, verbose = FALSE)
pancreas.ref <- FindClusters(pancreas.ref, verbose = FALSE)
pancreas.ref <- RunUMAP(pancreas.ref, dims = 1:30, verbose = FALSE)
cat("参考集预处理完成\n")
# 参考集 UMAP(按已知细胞类型着色)
p1 <- DimPlot(pancreas.ref, reduction = "umap", group.by = "seurat_annotations",
label = TRUE, label.size = 4, repel = TRUE) +
ggtitle("参考集 UMAP(已知注释)")
save_plot("01_reference_umap.png", p1)
# ============================================================
# 4. 查找转移锚点 (FindTransferAnchors)
# ============================================================
cat("\n## 4. 查找转移锚点\n\n")
# 查询集预处理
pancreas.query <- NormalizeData(pancreas.query, verbose = FALSE)
# 查找锚点
cat("正在查找转移锚点...\n")
pancreas.anchors <- FindTransferAnchors(
reference = pancreas.ref,
query = pancreas.query,
dims = 1:30,
reference.reduction = "pca"
)
cat("锚点查找完成\n")
# ============================================================
# 5. 标签转移 (TransferData)
# ============================================================
cat("\n## 5. 标签转移 (TransferData)\n\n")
# 转移细胞类型标签
predictions <- TransferData(
anchorset = pancreas.anchors,
refdata = pancreas.ref$seurat_annotations,
dims = 1:30
)
pancreas.query <- AddMetaData(pancreas.query, metadata = predictions)
cat("标签转移完成\n")
cat(sprintf("\n预测细胞类型分布:\n"))
print(table(pancreas.query$predicted.id))
# ============================================================
# 6. 评估预测准确性
# ============================================================
cat("\n## 6. 评估预测准确性\n\n")
# 比较预测标签和真实标签
pancreas.query$prediction.match <- pancreas.query$predicted.id == pancreas.query$seurat_annotations
match_result <- table(pancreas.query$prediction.match)
cat(sprintf("预测结果:\n"))
print(match_result)
cat(sprintf("\n预测准确率: %.1f%%\n",
round(match_result["TRUE"] / sum(match_result) * 100, 1)))
# 按细胞类型查看预测准确性
cat("\n各细胞类型预测准确性:\n")
acc_by_type <- sapply(levels(pancreas.query$seurat_annotations), function(ct) {
idx <- pancreas.query$seurat_annotations == ct
if (sum(idx) > 0) {
correct <- sum(pancreas.query$predicted.id[idx] == ct)
sprintf("%s: %d/%d (%.0f%%)", ct, correct, sum(idx), correct/sum(idx)*100)
} else {
sprintf("%s: 0/0", ct)
}
})
print(acc_by_type)
# 预测准确性柱状图
acc_df <- data.frame(
celltype = levels(pancreas.query$seurat_annotations),
accuracy = sapply(levels(pancreas.query$seurat_annotations), function(ct) {
idx <- pancreas.query$seurat_annotations == ct
if (sum(idx) > 0) sum(pancreas.query$predicted.id[idx] == ct) / sum(idx) * 100
else 0
})
)
p2 <- ggplot(acc_df, aes(x = reorder(celltype, -accuracy), y = accuracy)) +
geom_bar(stat = "identity", fill = "#4C9BE8") +
geom_text(aes(label = sprintf("%.0f%%", accuracy)), vjust = -0.5, size = 3.5) +
ylim(0, 105) +
labs(title = "各细胞类型预测准确率", x = "细胞类型", y = "准确率 (%)") +
theme_bw() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
save_plot("02_prediction_accuracy.png", p2)
# 混淆矩阵热图
conf_matrix <- as.data.frame(table(pancreas.query$seurat_annotations, pancreas.query$predicted.id))
colnames(conf_matrix) <- c("True", "Predicted", "Count")
p3 <- ggplot(conf_matrix, aes(x = Predicted, y = True, fill = Count)) +
geom_tile() +
geom_text(aes(label = Count), size = 3) +
scale_fill_gradient(low = "white", high = "#2166AC") +
labs(title = "混淆矩阵:真实标签 vs 预测标签", x = "预测标签", y = "真实标签") +
theme_bw() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
save_plot("03_confusion_matrix.png", p3, width = 10, height = 9)
# ============================================================
# 7. Marker 基因验证
# ============================================================
cat("\n## 7. Marker 基因验证\n\n")
# 用经典 Marker 基因验证预测
markers <- c("MS4A1", "CD3D", "CD3E", "IL7R", "CD14", "LYZ", "GNLY", "NKG7", "FCGR3A", "PPBP")
p4 <- VlnPlot(pancreas.query, features = markers, group.by = "predicted.id",
ncol = 5, pt.size = 0) +
plot_layout(ncol = 1) &
theme(axis.text.x = element_text(angle = 45, hjust = 1, size = 8))
save_plot("04_marker_violin.png", p4, width = 15, height = 12)
cat("Marker 基因小提琴图已保存\n")
# ============================================================
# 8. UMAP 投影 (MapQuery)
# ============================================================
cat("\n## 8. UMAP 投影 (MapQuery)\n\n")
# 重新运行 UMAP 并保存模型
pancreas.ref <- RunUMAP(pancreas.ref, dims = 1:30, reduction = "pca",
return.model = TRUE, verbose = FALSE)
# 使用 MapQuery 一步完成标签转移 + 嵌入整合 + UMAP 投影
cat("正在执行 MapQuery...\n")
pancreas.query <- MapQuery(
anchorset = pancreas.anchors,
reference = pancreas.ref,
query = pancreas.query,
refdata = list(celltype = "seurat_annotations"),
reference.reduction = "pca",
reduction.model = "umap"
)
cat("MapQuery 完成\n")
# ============================================================
# 9. 参考集与查询集 UMAP 对比
# ============================================================
cat("\n## 9. 参考集与查询集 UMAP 对比\n\n")
# 参考集 UMAP
p5 <- DimPlot(pancreas.ref, reduction = "umap", group.by = "seurat_annotations",
label = TRUE, label.size = 3, repel = TRUE) +
NoLegend() +
ggtitle("参考集注释")
# 查询集投影 UMAP
p6 <- DimPlot(pancreas.query, reduction = "ref.umap", group.by = "predicted.celltype",
label = TRUE, label.size = 3, repel = TRUE) +
NoLegend() +
ggtitle("查询集预测标签")
p_combo <- p5 + p6
save_plot("05_ref_vs_query_umap.png", p_combo, width = 16, height = 7)
# 查询集:真实标签 vs 预测标签对比
p7 <- DimPlot(pancreas.query, reduction = "ref.umap", group.by = "seurat_annotations",
label = TRUE, label.size = 3, repel = TRUE) +
ggtitle("查询集真实标签")
p8 <- DimPlot(pancreas.query, reduction = "ref.umap", group.by = "predicted.celltype",
label = TRUE, label.size = 3, repel = TRUE) +
ggtitle("查询集预测标签")
p_truth_vs_pred <- p7 + p8
save_plot("06_truth_vs_predicted.png", p_truth_vs_pred, width = 16, height = 7)
# ============================================================
# 10. 预测置信度分析
# ============================================================
cat("\n## 10. 预测置信度分析\n\n")
# 查看预测分数分布
p9 <- ggplot(pancreas.query@meta.data, aes(x = predicted.id, y = prediction.score.max)) +
geom_boxplot(fill = "#4C9BE8", alpha = 0.7) +
geom_jitter(width = 0.2, alpha = 0.3, size = 0.5) +
labs(title = "各细胞类型预测置信度", x = "预测细胞类型", y = "预测分数") +
theme_bw() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
save_plot("07_prediction_scores.png", p9, width = 10, height = 7)
cat(sprintf("平均预测分数: %.3f\n", mean(pancreas.query$prediction.score.max)))
# ============================================================
# 11. 合并参考集和查询集可视化
# ============================================================
cat("\n## 11. 合并参考集和查询集可视化\n\n")
# 给参考集和查询集添加来源标签
pancreas.ref$dataset <- "Reference"
pancreas.query$dataset <- "Query"
# 合并 UMAP 可视化
p10 <- DimPlot(pancreas.query, reduction = "ref.umap", group.by = "predicted.celltype",
label = TRUE, label.size = 3, repel = TRUE) +
labs(title = "查询集投影到参考 UMAP", color = "预测细胞类型") +
theme(plot.title = element_text(size = 14))
save_plot("08_query_projected_umap.png", p10, width = 10, height = 8)
# 按数据来源分组着色
p11 <- DimPlot(pancreas.query, reduction = "ref.umap", group.by = "dataset",
cols = c("Query" = "#E41A1C")) +
labs(title = "查询集在参考 UMAP 空间中的分布", color = "数据集")
save_plot("09_query_on_ref_space.png", p11, width = 10, height = 8)
# ============================================================
# 12. 保存结果
# ============================================================
cat("\n## 12. 保存结果\n\n")
saveRDS(pancreas.ref, file.path(output_dir, "reference_dataset.rds"))
saveRDS(pancreas.query, file.path(output_dir, "query_mapped.rds"))
cat("已保存: reference_dataset.rds, query_mapped.rds\n")
# 保存预测结果
write.csv(pancreas.query@meta.data[, c("seurat_annotations", "predicted.id",
"prediction.score.max", "prediction.match")],
file.path(output_dir, "mapping_predictions.csv"), row.names = TRUE)
cat("已保存: mapping_predictions.csv\n")
cat("\n=== 阶段4-A 参考数据集映射 完成!===\n")
cat(sprintf("共生成 9 张图 + 1 个CSV + 2 个RDS文件\n"))
cat(sprintf("预测准确率: %.1f%%\n",
round(match_result["TRUE"] / sum(match_result) * 100, 1)))
本教程基于 Seurat v5.5.1 官方文档整理
R 4.6.1 + Seurat 5.5.1 + Windows 环境
阶段4-A: 参考数据集映射(标签转移 + MapQuery + 预测准确率 92.4%)