4. 创建Seurat对象:Read10X与CreateSeuratObject

拿到单细胞测序原始数据后,第一件事就是把它读进R环境,构造成一个Seurat对象。这一步看似简单,但参数选不对,后面全白费。我刚开始接触时,就吃过文件路径不对的亏,折腾了半天才发现是目录少了一层。

说白了,创建Seurat对象就是两个核心函数的事:Read10X和CreateSeuratObject。先读数据,再装对象。嗯,我们先看看这两个函数怎么配合。

4.1 10X标准输出:三个文件一台戏

10X Genomics的cellranger跑完后,会生成一个目录,里面有三个文件缺一不可:

barcodes.tsv.gz       # 细胞条形码列表
features.tsv.gz       # 基因特征列表(基因名、ID等)
matrix.mtx.gz         # 稀疏矩阵:细胞×基因的表达量

你想想看,这三兄弟各司其职。barcodes决定细胞个数,features决定基因数量,matrix则告诉每个基因在每个细胞里有多少UMI计数。三者严格按行列对齐,少一个、错一行都不行。

我在一个项目中遇到过这样的情况:features.tsv里第一列是Ensemble ID,第二列是基因名,但后续分析需要的是基因名。如果你直接拿默认的读法,后面注释会乱套。所以,参数灵活性很重要。

4.2 Read10X:把三兄弟变成稀疏矩阵

Read10X的作用,就是把上面那三个.gz文件读成一个稀疏矩阵。为什么是稀疏矩阵?因为单细胞数据太稀疏了,一个细胞可能只表达几千个基因,大部分是0。用稀疏矩阵存储,能省几十倍内存。

来看个标准用法:

library(Seurat)

# 读取过滤后的feature-barcode矩阵
pbmc.data <- Read10X(data.dir = "filtered_feature_bc_matrix/")
# 结果是个dgCMatrix(稀疏矩阵)

这里data.dir指向的就是包含那三个文件的目录。Read10X会自动识别并解析。

核心参数:gene.column控制用什么做主基因标识。默认是2,也就是features.tsv的第二列(通常是基因名)。如果你的数据第一列是Ensemble ID,想用这个来做唯一标识,那就设为1。我个人习惯用2,因为基因名更直观。

那么,如果cellranger输出的是原始(raw)特征矩阵而不是过滤后的怎么办?很简单,Read10X也能读,只不过原始矩阵里包含很多空液滴,需要后面过滤。我课上一般建议大家用filtered_feature_bc_matrix,因为细胞已经初步清洗过,数据量更小。

函数 适用场景 输出类型 注意事项
Read10X 标准的cellranger输出(目录模式) dgCMatrix 自动识别三个文件,gene.column可调
Read10X_h5 cellranger输出的HDF5文件 dgCMatrix 单个H5文件,更易管理,但占用更多内存
我的经验:如果你的数据量很大(比如几万细胞),我建议用Read10X_h5读H5文件。因为H5是单个文件,IO读写时更快,而且可以分块读取,对大项目友好。小项目则无所谓。

4.3 CreateSeuratObject:从矩阵到对象

矩阵读进来了,接下来就是把它变成Seurat对象。这就是CreateSeuratObject的活。别看它叫“创建”,其实它干了不少事:把矩阵存进去,顺便帮你做第一波过滤。

pbmc <- CreateSeuratObject(
  counts = pbmc.data,              # 稀疏矩阵
  project = "PBMC",                # 项目名称,不重要
  min.cells = 3,                   # 基因至少在3个细胞中表达
  min.features = 200               # 细胞至少表达200个基因
)

这里面的参数,每个都有讲究。

  • counts:必须是原始UMI计数的稀疏矩阵。别传归一化后的数据,否则后续标准流程会崩。
  • project:就是个标签,方便识别。没啥技术影响。
  • min.cells:只保留在至少N个细胞中表达的基因。默认是3。这个值太小会保留很多噪声基因,太大又会把稀有群体的标志基因误删。我一般在3~5之间调。
  • min.features:只保留至少表达N个基因的细胞。默认是200。这个值用来去掉空液滴和死细胞。如果设得太低,会把死细胞混进来;设得太高,可能会把真实的小细胞丢掉。
避坑指南:我曾经在调min.features时,看着直方图把阈值设到1000,结果把一群浆细胞全丢光了。后来才发现浆细胞表达基因数本来就少。所以,这个参数一定要结合数据分布来定,不要拍脑袋。

来看一个更完整的实战流程:

library(Seurat)
library(dplyr)

# 第一步:读取数据
pbmc.data <- Read10X(data.dir = "filtered_feature_bc_matrix/")

# 第二步:创建对象,顺便过滤
pbmc <- CreateSeuratObject(
  counts  = pbmc.data,
  project = "PBMC_4k",
  min.cells = 5,
  min.features = 200
)

# 第三步:检查对象
pbmc
# 会显示:An object of class Seurat with ... features across ... samples

创建完对象后,我习惯马上跑一个VlnPlot看基因数、UMI数和线粒体比例,来验证刚才的过滤阈值是否合理。但那是下一节的内容了。

4.4 整个流程直观理解

我画了一张图,帮你把从原始文件到Seurat对象的路径串起来。你看一眼就明白了。

Seurat对象创建流程 cellranger输出目录 barcodes.tsv.gz features.tsv.gz matrix.mtx.gz Read10X() dgCMatrix CreateSeuratObject() min.cells / min.features Seurat对象 方向 由上至下 先输入 再读取 最后创建

4.5 常见踩坑与个人避坑技巧

这部分是我最想分享的。别看代码就两行,实际中问题五花八门。

  • 路径错乱:data.dir要指向包含三个.gz文件的目录,不是上一级,也不是子目录。若不放心,用list.files(data.dir)先看一眼。
  • 内存不足:如果用Read10X_h5读到一半崩了,试试用h5read分块读,或者换服务器。我曾在笔记本上直接跑10万细胞的数据,结果R被kill了。后来改用H5文件加DelayedArray才搞定。
  • 基因名重复:features.tsv里如果有多组学数据,基因名可能会重复。Seurat会自动用make.unique处理,但最好提前检查:table(duplicated(rownames(pbmc.data)))。
  • min.features设得太高:这个上面提过。建议先用一个宽阈值创建一个粗糙对象,画VlnPlot看分布,再回来改参数重建。
小技巧:创建完对象后,别急着往下跑。先跑一句pbmc,看看对象里有多少细胞和基因。如果数字和你预期相差很大,赶紧倒回去检查参数。这能省你接下来几个小时的无用功。