单细胞分析全流程:从 10x 矩阵到细胞类型与组间差异
跟完这篇,您能把一份 10x 单细胞数据从原始矩阵一路做到质控、聚类、细胞类型注释与组间差异基因,并知道每一步该调哪些参数、每个中间结果怎么看。
流程总览
一个标准的 Seurat 单细胞分析,就是下面这条固定的流水线。每一步的输入都是上一步的输出,中途出问题往往能顺着这条链回溯定位。
| 步骤 | 关键函数 | 这一步在做什么 | 常见产出 |
|---|---|---|---|
| 1. 建对象 | Read10X / CreateSeuratObject | 把稀疏矩阵读进来,包成一个 Seurat 对象 | 细胞 × 基因 计数矩阵 |
| 2. 质控 | PercentageFeatureSet / subset | 去掉空液滴、双细胞、死细胞 | 干净的细胞集 |
| 3. 标准化 + 高变基因 | NormalizeData / FindVariableFeatures / ScaleData | 消除测序深度差异,挑出有信息量的基因 | 归一化矩阵 + 高变基因 |
| 4. 降维聚类 | RunPCA / FindNeighbors / FindClusters / RunUMAP | 压缩维度、把细胞分群、二维可视化 | UMAP 图 + cluster 编号 |
| 5. 注释 | FindAllMarkers / FeaturePlot / RenameIdents | 给每个 cluster 贴上细胞类型标签 | 带细胞类型的 UMAP |
| 6. 差异与下游 | FindMarkers | 在细胞类型内比较处理组与对照组 | 差异基因表 |
下面按单个人类样本从头走通全流程。多个样本时,降维之前要先做整合(integration,常用 harmony 或 Seurat 自带的整合流程)以消除批次效应,主流程其余步骤一致。示例代码用于说明每步「做什么、调什么参数」,请对着自己的数据填路径、调阈值,不必追求整段照抄即跑。
读入矩阵、建 Seurat 对象
10x Cell Ranger 的标准输出是一个目录,里面有三个文件:barcodes.tsv.gz(细胞条形码)、features.tsv.gz(基因)、matrix.mtx.gz(稀疏计数矩阵)。Read10X 直接读这个目录:
library(Seurat)
# data.dir 指向包含三个 10x 文件的目录
counts <- Read10X(data.dir = "filtered_feature_bc_matrix/")
seu <- CreateSeuratObject(
counts = counts,
project = "sample1",
min.cells = 3, # 至少在 3 个细胞里检出的基因才保留
min.features = 200 # 至少检出 200 个基因的细胞才保留
)
seu
几点说明:
min.cells = 3与min.features = 200是官方教程的常用默认值,作用是先滤掉几乎不表达的基因和明显是空液滴的条形码。这是惯例、非铁律,稀有细胞多的数据可以把min.cells放宽。- 如果您拿到的不是 10x 目录,而是一张普通的计数矩阵(行是基因、列是细胞),可以先读成矩阵再直接传给
CreateSeuratObject(counts = ...)。矩阵的行列约定见 表达矩阵怎么整理。 - 建完对象后
seu@meta.data里已自动带上nCount_RNA(每个细胞的总 UMI)和nFeature_RNA(每个细胞检出的基因数),下一步质控就靠它们。
质控:去掉坏细胞
单细胞数据里混着空液滴、双细胞(两个细胞被包进一个液滴)和死细胞,必须先清掉,否则后面的聚类会被它们带偏。常用三个指标:
| 指标 | 含义 | 太高说明 | 太低说明 |
|---|---|---|---|
nFeature_RNA | 检出基因数 | 可能是双细胞 | 可能是空液滴 / 低质量细胞 |
nCount_RNA | 总 UMI 数 | 可能是双细胞 | 文库偏浅 |
percent.mt | 线粒体基因占比 | 细胞破损 / 濒死 | —— |
先算线粒体比例,画小提琴图看分布,再按分布卡阈值:
# 人类线粒体基因以 MT- 开头;小鼠改用 "^mt-"
seu[["percent.mt"]] <- PercentageFeatureSet(seu, pattern = "^MT-")
# 画小提琴图,肉眼看三个指标的分布
VlnPlot(seu, features = c("nFeature_RNA", "nCount_RNA", "percent.mt"), ncol = 3)
# 阈值按上一步的分布来定,下面的数字是常见起点,不是标准答案
seu <- subset(
seu,
subset = nFeature_RNA > 200 & nFeature_RNA < 2500 & percent.mt < 10
)
关键提醒:
- 上面
nFeature_RNA < 2500、percent.mt < 10都是常用起点、非铁律。线粒体阈值常见落在 5%–20% 之间,代谢旺盛的组织(如心肌、肝)本底就高,应放宽;新鲜血液这类样本则可以更严。务必先看小提琴图,按您自己数据的分布卡,而不是照抄数字。 - 双细胞更稳妥的做法是用专门工具(如 DoubletFinder、scDblFinder)识别,单靠
nFeature_RNA上限只是粗筛。 - 质控卡完之后,对象里的细胞数应该明显减少,且小提琴图上那些极高、极低的离群细胞被削掉,这就是质控做对了的样子。
标准化 + 找高变基因
不同细胞测序深度不同,直接比原始计数不公平,要先标准化;再从两万多个基因里挑出「细胞间变化最大」的那批高变基因,后续降维只用它们,既提速又降噪。
# 1)标准化:LogNormalize,按总量归一后乘 scale.factor(默认 1e4)再取 log
seu <- NormalizeData(seu)
# 2)找高变基因:vst 方法,默认取 2000 个
seu <- FindVariableFeatures(seu, selection.method = "vst", nfeatures = 2000)
# 3)缩放(中心化 + 方差归一),为 PCA 做准备
seu <- ScaleData(seu)
nfeatures = 2000是常用默认,细胞异质性高的数据可以适当调大。- 还有一条更集成的路线是
SCTransform,它把标准化、找高变基因、缩放三步合成一步,对技术噪声的处理更稳;用了它就不必再单独跑上面三步。两条路线选其一即可,属于常见二选一,没有绝对优劣。 - 这一步之后,数据准备工作就结束了,进入分析的核心。
降维与聚类
高维数据先用 PCA 压到几十个主成分,再在 PCA 空间上建近邻图、做图聚类,最后用 UMAP 把结果摊到二维上看。
seu <- RunPCA(seu)
# 肘图看前多少个主成分够用(拐点之后信息增量很小)
ElbowPlot(seu)
# dims 取前 N 个主成分;30 是常用取值,按肘图微调
seu <- FindNeighbors(seu, dims = 1:30)
# resolution 越大,分出的 cluster 越多
seu <- FindClusters(seu, resolution = 0.5)
# UMAP 只做可视化,不参与聚类计算
seu <- RunUMAP(seu, dims = 1:30)
DimPlot(seu, reduction = "umap", label = TRUE)
三个要点:
dims用多少个主成分,看ElbowPlot的拐点,常取 20–30 个,按肘图定、非固定值。resolution直接决定分群粗细:调小合并、调大细分。没有「正确」的分辨率,应结合已知 marker 和生物学预期,多试几个值再定。- UMAP 的坐标只用于看图,簇之间的距离和大小不代表定量关系,别过度解读二维图上的远近。
注释细胞类型
聚类只给出 0、1、2…… 这样的编号,还要把每个 cluster 对应到真实细胞类型。标准做法是找出每群的 marker 基因,再对照已知的细胞类型标志基因来判断。
# 找每个 cluster 相对其余细胞高表达的基因(marker)
markers <- FindAllMarkers(
seu,
only.pos = TRUE, # 只看上调
min.pct = 0.25, # 至少在 25% 的细胞里表达
logfc.threshold = 0.25
)
# 把关心的经典 marker 画到 UMAP 上,肉眼对上细胞类型
FeaturePlot(seu, features = c("CD3D", "MS4A1", "LYZ", "NKG7"))
对上之后,把 cluster 编号重命名成细胞类型,并存一份注释好的对象备用:
# 名字要对应当前的 cluster 编号 0、1、2……
new.ids <- c("0" = "T cell", "1" = "B cell", "2" = "Monocyte")
seu <- RenameIdents(seu, new.ids)
# 把细胞类型写回 meta.data,方便后面按类型取子集
seu$cell_type <- Idents(seu)
DimPlot(seu, reduction = "umap", label = TRUE)
# 存下来,下次 readRDS 直接读回,省去重跑前面所有步骤
saveRDS(seu, "seu_annotated.rds")
注意:
- 上面几个基因(
CD3DT 细胞、MS4A1B 细胞、LYZ单核、NKG7NK / 杀伤细胞)是外周血里被广泛使用的经典 marker;换组织就要换对应的 marker 面板。 - 注释本质上要靠领域知识和 marker 判断,自动注释工具(如 SingleR)可以做参考,但结果需人工核对,不能全盘照收。
new.ids的名字必须逐一对上当前所有 cluster 编号;漏掉某个编号,那一群会保留原来的数字标签。
差异与下游
拿到带细胞类型标签的对象后,最常见的下游问题是:同一类细胞在处理组和对照组之间有什么差异。做法是先锁定某个细胞类型,再在其内部按分组比较。
# 前提:meta.data 里已有 cell_type 和 condition(处理 / 对照)两列
Idents(seu) <- "cell_type"
tcell <- subset(seu, idents = "T cell")
Idents(tcell) <- "condition"
de <- FindMarkers(tcell, ident.1 = "disease", ident.2 = "control")
head(de)
- 结果表里
avg_log2FC是效应量、p_val_adj是校正后 p 值。筛显著基因常用p_val_adj < 0.05,这是通用惯例、非铁律,可按需收紧。 - 单细胞的组间差异有其统计局限:把细胞当独立样本会低估组间变异、放大显著性;样本量足够时更稳妥的是做「伪散装」(pseudobulk,把每个样本每类细胞的计数加总后当作 bulk 样本,用 DESeq2 等做差异)。方法的取舍见 怎么选统计方法,pseudobulk 的具体做法可参照 DESeq2 差异表达。
- 拿到差异基因后,通常接着做富集分析看它们落在哪些通路上,见 富集分析怎么做。
在平台上跑一遍
上面这条 pipeline,在「百沐一下」不写代码也能走完:上传您的 10x 矩阵(或计数矩阵 + 分组表),用一句话说清目的——例如「对这份单细胞数据做质控、聚类、细胞类型注释,再比较 disease 与 control」,组学智能体会自动完成建对象、质控、降维聚类、注释到差异的全流程,并给出图形、结果表和可复现的 R 代码。功能说明见 组学智能体使用指南,或直接到 app.baimuyixia.com 试一遍。