单细胞数据读取与创建 Seurat 对象:从原始文件到可分析的数据结构
拿到单细胞数据的第一件事是什么?不是跑分析,是先把数据读进来。我见过不少同学,把10X的matrix.mtx当作普通文本直接读,结果搞了一整天……嗯,这活确实能手动做,但Seurat给你封装好了,咱们就好好用。
这章的核心就两件事:不同平台的数据怎么读,以及CreateSeuratObject的参数怎么调。说白了,就是把外部的原始数据变成Seurat能理解的"对象",后面所有分析都基于这个对象展开。
2.1 数据格式千差万别,读取方法也得跟着变
单细胞测序平台五花八门,输出格式自然不统一。我最早做项目时,实验室既有10X又有Smart-seq2,每次换数据集都得翻文档……后来总结出一个规律:先看文件扩展名,再选读取函数。
| 平台 | 典型文件结构 | 推荐的读取方法 |
|---|---|---|
| 10X Genomics (v2/v3) | matrix.mtx.gz, barcodes.tsv.gz, features.tsv.gz 或 raw_feature_bc_matrix.h5 |
Read10X() 或 Read10X_h5() |
| 10X Multiome (RNA + ATAC) | 多组学H5文件,包含多个assay | Read10X_Multiome() |
| Drop-seq | 数字表达矩阵 (DGE) 或 *.dge.txt.gz |
ReadDGE() 或 read.table() |
| Smart-seq2 | 每个细胞一个文件,或整合后的表达矩阵 | ReadSmartSeq() 或直接读矩阵 |
| H5AD / Loom | 单文件HDF5格式 (常见于Scanpy生态) | ReadH5AD() 或 ReadLoom() 然后 as.Seurat() |
2.1.1 10X数据——最流行,也最不容易出错
如果你做的是10X数据,恭喜你,Seurat为它提供了最完善的"一站式"读取方案。我个人习惯优先用H5版本,因为一个文件搞定所有,不容易漏文件。但有些实验室给的是MEX格式(三个文件),也没问题。
# 读取H5文件(推荐)
library(Seurat)
counts_h5 <- Read10X_h5("raw_feature_bc_matrix.h5")
# 读取MEX格式(三个文件在同一个目录下)
counts_mex <- Read10X(data.dir = "filtered_feature_bc_matrix/")
# 多组学数据(如10X Multiome)
counts_multi <- Read10X_Multiome(data.dir = "filtered_feature_bc_matrix/")
# 返回一个list,包含"Gene Expression"和"Peaks"两个矩阵
注意区分:Read10X() 默认找 matrix.mtx,如果文件夹下有多个子目录(如 raw_feature_bc_matrix),记得把路径指向正确的子目录。我曾经因为路径写错了一层,卡了半小时……
2.1.2 Drop-seq与Smart-seq2——矩阵在手,天下我有
这两个平台的数据,本质都是一张基因 × 细胞的计数矩阵。区别在于Drop-seq通常提供稀疏矩阵格式(DGE),而Smart-seq2更常见的是稠密矩阵(行是基因,列是细胞,值可能是FPKM或TPM,但建议用原始计数)。
# Drop-seq DGE格式
dge <- ReadDGE("sample.dge.txt.gz")
# 返回的是稀疏矩阵,可以直接传给CreateSeuratObject
# Smart-seq2——直接用read.table读
expr_matrix <- read.table("expression_matrix.tsv", header = TRUE, row.names = 1, sep = "\t")
# 如果值是TPM或FPKM,最好做转换?别急,后面我们会讲标准化,但CreateSeuratObject只认整数计数
# 对于非UMI数据,保留原始值即可,但最好注明是"log-normalized"还是"raw counts"
一个经验:Smart-seq2数据经常有大量dropout(零值),读进来后记得用FeatureScatter检查一下基因表达分布。如果发现很多基因只在1~2个细胞中表达,后面min.cells参数得调大一些。
2.2 CreateSeuratObject——把原始数据"包装"成标准对象
数据读进来了,现在我们要调用CreateSeuratObject()函数。这个函数就像一台打包机——你把原始计数矩阵塞进去,它吐出一个含有assay、meta.data、reductions等空壳的Seurat对象。注意,这时候对象里还没有降维、聚类,只有原始表达矩阵和最基本的质量控制信息。
函数签名长这样(我挑最常用的参数讲):
CreateSeuratObject(
counts, # 原始计数矩阵(稀疏/稠密均可)
project = "SeuratProject", # 项目名称,用于标识
assay = "RNA", # 测序类型,默认为RNA
names.field = 1, # 从细胞barcode中提取分组信息
names.delim = "_", # 分隔符
meta.data = NULL, # 额外的细胞元数据
min.cells = 3, # 至少在多少细胞中表达的基因才保留
min.features = 200 # 一个细胞至少要有多少基因表达
)
我见过太多人直接带着默认参数跑了。这样行不行?也行,但结果往往不够干净。咱们逐个拆解一下关键参数。
2.2.1 counts——唯一必须手动传的参数
原始计数矩阵,必须是整数。Seurat内部会把它存储为稀疏矩阵(dgCMatrix),所以即使你传的是稠密矩阵,它也会帮你转换,节省内存。
2.2.2 min.cells 和 min.features——第一道质控门
这两个参数是创建对象时就执行的过滤,而且过滤速度非常快(因为只做计数筛选)。
min.cells = 3:基因至少在3个细胞中出现,才保留。这能去掉那些"幽灵基因"——比如只在1个细胞中意外捕获的噪声。min.features = 200:每个细胞至少检测到200个基因。如果低于这个数,大概率是空液滴或死细胞。
你可能会问:那后续不是还有更细致的质控吗?对,这里只是粗略过滤,但能极大减小对象大小。我做过的一个PBMC数据集,原始矩阵含3万个基因,设了min.cells = 3后只剩1.8万,节省了40%内存。
我曾经踩过的坑:有一批Smart-seq2数据,部分细胞的测序深度很低,min.features设成200后,丢了一半细胞。后来我把阈值降到100,先建对象,再在质控阶段用subset()结合其他指标(如线粒体比例)做精细过滤。所以阈值不要一刀切,结合你的实验设计来定。
2.2.3 project——给对象打标签
这个参数默认是"SeuratProject"。如果你有多个样本,建议改成有意义的名字,比如"PBMC_healthy_1"。后面合并对象时,project字段会作为默认的样本标识,省得你自己再去加。
pbmc <- CreateSeuratObject(counts = counts_h5,
project = "PBMC_Control_DonorA",
assay = "RNA",
min.cells = 3,
min.features = 200)
一个小技巧:如果数据来自不同批次,可以在meta.data中预置批次信息,比如:
meta <- data.frame(row.names = colnames(counts), batch = rep("batch1", ncol(counts)))
pbmc <- CreateSeuratObject(counts = counts, meta.data = meta)
这样后面做整合时,就不用手动添加了。
2.2.4 names.field 和 names.delim——解析细胞barcode里的"密码"
某些10X数据,细胞barcode可能长这样:AAACCCAAGAAACTCA-1。如果names.delim = "-"且names.field = 1,Seurat会把"-"后面的数字(1)提取出来,存到meta.data的orig.ident列中。如果你每个样本都单独跑10X管道,这个数字就是样本索引。不过我个人习惯在project里直接指定,所以一般保持默认不额外设置。
2.3 创建完对象后,先检查什么?
用上面代码创建好对象后,别急着做下一步。先简单看看:
# 打印对象信息
pbmc
# 输出类似:
# An object of class Seurat
# 2000 features across 1000 samples within 1 assay
# Active assay: RNA (2000 features, 0 variable features)
# 查看前6个细胞的barcode和细胞数
head(colnames(pbmc))
# 查看meta.data中的列
head(pbmc@meta.data)
注意看nCount_RNA和nFeature_RNA这两列——它们是创建对象时自动计算的,分别表示每个细胞的UMI总数和检测到的基因数。如果发现有细胞nFeature_RNA为0,那可能是空液滴或者数据读取有问题,需要排查原始数据。
2.4 特殊场景:多模态数据(CITE-seq / ASAP-seq)
如果你的数据同时包含RNA和蛋白质(CITE-seq),或者RNA和ATAC,你可以在创建对象时指定多个assay。但Seurat官方推荐的做法是:先用RNA数据创建一个标准对象,再用CreateAssayObject()添加其他模态。这部分我会在第5章详细讲,这里先提一句。
还记得我一开始说的吗?单细胞分析的起点就是把东西读进来。现在你有了一个干净、标准、可复现的Seurat对象。接下来所有分析——质控、标准化、降维、聚类、注释——都跑在这个对象上。踩稳这一步,后面的路会顺很多。