Seurat 基础:创建对象 · 质量控制

Seurat 对象是这个工具链里的核心容器——说白了,所有单细胞数据最后都塞进这个结构里。我这些年接手的项目,十有八九第一步就是检查数据能不能正确塞进 Seurat 对象。这一步搞砸了,后面所有分析都白搭。

下面拆开讲:怎么创建对象,怎么看质控指标,怎么避开我踩过的坑。

1. 从表达矩阵到 Seurat 对象

原始数据通常是个计数矩阵:行是基因,列是细胞。我们要把它封装成 Seurat 对象。

最常用的函数是 CreateSeuratObject。下面是个标准流程:

library(Seurat)

# 假设 data 是行基因×列细胞的稀疏矩阵
seurat_obj <- CreateSeuratObject(
  counts = data,
  project = "my_project",
  min.cells = 3,    # 至少在3个细胞中表达的基因才保留
  min.features = 200 # 至少表达200个基因的细胞才保留
)

参数 min.cells 和 min.features 是预过滤。我一般设得很宽松,后面会做更精细的过滤。曾经有个项目我贪快把 min.features 设成 500,结果发现丢弃了大量真实细胞——因为那个样本本身是低转录活性的中性粒细胞。所以预过滤门槛宁可低一些。

2. 质量控制的第一步:理解元数据

创建对象后,Seurat 会自动计算两个字段:nCount_RNA(每个细胞的总 UMI 数)和 nFeature_RNA(每个细胞检测到的基因数)。它们存在对象的元数据(meta.data)里。

查看元数据:

head(seurat_obj@meta.data)
细胞IDnCount_RNAnFeature_RNA
AAACCCAAGCATACT-129741128
AAACCCACACCGTAG-11876862
AAACCCAGTCAGATG-145121653

这两个字段就是质控的原始素材。你想想看,如果一个细胞只测到几十个基因,大概率是空液滴或者死细胞。

3. 计算线粒体比例——这步最常出错

线粒体基因比例高(通常 >20%)意味着细胞膜破损,胞质 RNA 流失,只剩线粒体 RNA 被包在双层膜里。计算方式是用正则匹配:

seurat_obj[["percent.mt"]] <- PercentageFeatureSet(seurat_obj, pattern = "^MT-")

这里我提醒一句:人源数据线粒体基因前缀是 ^MT-,小鼠是 ^mt-。我曾经在一个项目里直接复制人性代码跑小鼠数据,结果线粒体比例全是 0,排查了半天才发现是大小写问题。务必检查物种的前缀规则。

4. 可视化质控指标——快速定位异常

用 VlnPlot 看分布最直观:

VlnPlot(seurat_obj, features = c("nFeature_RNA", "nCount_RNA", "percent.mt"), ncol = 3)

一般效果像这样(示意图,非真实数据):

Seurat 对象创建与质量控制流程图 原始计数矩阵 CreateSeuratObject (min.cells, min.features) Seurat 对象 (含 meta.data) 计算线粒体比例 可视化(VlnPlot) 设置过滤阈值 过滤:nFeature_RNA 低、nCount_RNA 极高、percent.mt 高 → 移除 结束

5. 设定过滤阈值——经验与统计结合

过滤没有绝对标准,但我总结了一套经验值:

指标常见下限常见上限说明
nFeature_RNA200 – 5005000 – 8000太低→空液滴;太高→可能是双细胞
nCount_RNA50020000 – 50000双细胞往往总 UMI 数异常高
percent.mt–15% – 25%超过 25% 大概率是破损细胞

实际操作中我喜欢先用 VlnPlot 看分布,再结合 FeatureScatter 看相关性:

FeatureScatter(seurat_obj, feature1 = "nCount_RNA", feature2 = "percent.mt")
FeatureScatter(seurat_obj, feature1 = "nCount_RNA", feature2 = "nFeature_RNA")

我曾经在一个肿瘤样本里发现 nCount_RNA 和 percent.mt 有明显正相关——那些 UMI 极高的细胞全是线粒体基因,最后确认是红细胞污染。如果不看散点图,单纯用阈值过滤会漏掉这种偏置。

6. 执行过滤——选择 subset 还是直接索引?

两种方式都行,我推荐用 subset 函数,可读性强:

seurat_obj <- subset(seurat_obj, subset = nFeature_RNA > 200 & nFeature_RNA < 5000 & percent.mt < 20)

注意:过滤条件里不能有 | 或复杂的逻辑嵌套?其实可以,但建议拆成多步防止误删。

另一种直接操作元数据:

cells_keep <- which(seurat_obj$nFeature_RNA > 200 & seurat_obj$nFeature_RNA < 5000 & seurat_obj$percent.mt < 20)
seurat_obj <- seurat_obj[, cells_keep]
▸ 警告:subset 会返回一个新的 Seurat 对象,原来的对象不会变。如果你在循环里用 subset,小心内存堆积。我建议每次过滤后删掉临时变量。

7. 过滤后检查——别跳步骤

过滤完再用 VlnPlot 和 FeatureScatter 看一次,确认异常群体被移除。

VlnPlot(seurat_obj, features = c("nFeature_RNA", "nCount_RNA", "percent.mt"), ncol = 3)

这一步看似多余,但我吃过亏——有一次过滤阈值设得太宽,实际只删了 2% 的细胞,可视化才发现分布根本没变。那意味着阈值本身无效,需要重新查原始数据的分布。

8. 保存预处理的 Seurat 对象

到这一步我就用 saveRDS 把干净的对象存起来,作为后续分析的起点。

saveRDS(seurat_obj, "data/seurat_raw_qc.rds")
▸ 建议:文件名带版本号或日期,比如 seurat_qc_20250401.rds。我习惯把过滤参数也用 R 对象注释起来,方便回溯。
attr(seurat_obj, "qc_params") <- list(
  min_features = 200,
  max_features = 5000,
  max_mt = 20
)
saveRDS(seurat_obj, "data/seurat_qc_params.rds")

小结

创建 Seurat 对象和质控是单细胞分析里最琐碎但最关键的一步。我见过太多项目因为线粒体比例算错、阈值设错导致后续聚类全歪。把流程固定下来,用 Snakemake 自动化——这就是这个课程后面要做的。不过先别急,先把这一章的手工操作练熟,后面集成到管道时才不容易翻车。