4. 读取数据:CreateSeuratObject

好,到了真正动手的环节了。前面我们下载了数据,也简单看了一下文件长什么样。现在要做的,就是把这些零散的文本文件,变成一个R里的对象——Seurat对象。

说白了,CreateSeuratObject 就是 Seurat 流程的“起跑线”。它把表达矩阵包装起来,加上细胞信息、基因信息,然后丢到后面的标准化、降维、聚类流水线上。我记得自己第一次用的时候,直接就丢进去跑,结果后面各种报错,回头一看,原来是这里参数没调好。

核心思想: 这一步不是简单的“读文件”,而是建立数据结构和元数据框架。你后面所有的分析,都依赖于这个对象里存的东西对不对。

4.1 输入文件长什么样?

先看看我们手里有什么。以10x Genomics的Cell Ranger输出为例,通常有三个关键文件:

文件内容典型文件名
特征(基因)表格两列:基因ID(如ENSG...),基因名(如TP53)features.tsv.gz
条码表格一列:细胞条码序列(如 AAACCCAAG...)barcodes.tsv.gz
矩阵市场文件三列:行索引、列索引、UMI计数(稀疏格式)matrix.mtx.gz

这三种文件加起来,就是一个完整的表达矩阵。Seurat 内部会帮你把它们拼起来。你不需要自己手动合并。

4.2 最基础的调用

把这三样东西放在同一个目录下(比如 ./data/filtered_feature_bc_matrix/),然后用一行代码就能读进来:

library(Seurat)
pbmc.data <- Read10X(data.dir = "./data/filtered_feature_bc_matrix/")
pbmc <- CreateSeuratObject(counts = pbmc.data, project = "PBMC_3k")

这里 Read10X 会返回一个稀疏矩阵(dgCMatrix)。然后 CreateSeuratObject 接收这个矩阵,返回一个 Seurat 对象。

我的经验: 如果数据放在不同的目录,或者文件名不规范,可以用 Read10X(data.dir = "...", gene.column = 2) 指定基因名称在哪一列。我之前遇到过一次基因名全成了ENSEMBL ID,后面注释都乱了,加上 gene.column = 2 就正常了。

4.3 关键参数详解

CreateSeuratObject 有很多参数,但大部分情况下你只需要关注这几个:

参数默认值作用
counts无(必需)表达矩阵,行为基因,列为细胞
project"SeuratProject"项目名称,用于后续标记
assay"RNA"Assay名称,多组学时会用到
min.cells0一个基因至少在多少个细胞中表达才保留
min.features0一个细胞至少检测到多少个基因才保留

min.cells 和 min.features 是在构建对象时就做的初级过滤。我用的时候喜欢先设成0,等后面质控步骤再精确过滤。不过如果你数据特别大,也可以设 min.cells = 3 先去掉那些在极少细胞中表达的基因,省点内存。

4.4 对象里藏了什么?

创建完成后,用 pbmc 直接看一眼:

> pbmc
An object of class Seurat
13714 features across 2700 samples within 1 assay
Active assay: RNA (13714 features, 0 variable features)

嗯,2700个细胞,13714个基因。注意这里“features”是基因数,不是降维后的特征。你还可以用 pbmc[["RNA"]]@counts 访问原始计数矩阵,不过一般是操作对象的方法,而不是直接取slot。

避坑指南: 千万别直接用 $ 操作 @counts 改数据。我见过有人直接往稀疏矩阵里写值,结果破坏了稀疏结构,后面计算直接崩。如果要做数据修改,请用 SetAssayData 或新建一个 assay。

4.5 处理其他来源的数据

不是所有数据都来自10x。比如Smart-seq2的数据,一般是每个细胞一个文件,或者一个大的表达矩阵(可能是稠密矩阵)。这时候你不能用 Read10X,得手动读进来,然后传给 CreateSeuratObject。

# 假设你已经读好了表达矩阵(行为基因,列为细胞)
mat <- read.table("expression_matrix.tsv", header = TRUE, row.names = 1, sep = "\t")
mat <- as.matrix(mat)  # 转换成稠密矩阵
obj <- CreateSeuratObject(counts = mat, project = "SmartSeqData")

Seurat 会自动把你的稠密矩阵转成稀疏格式。如果矩阵太大(比如>10万细胞),建议先转稀疏矩阵再传入,省内存。

4.6 元数据太重要了

创建对象时,Seurat 会自动生成几个默认的元数据(metadata):

  • orig.ident:就是你传入的 project 名称
  • nCount_RNA:每个细胞的UMI总数
  • nFeature_RNA:每个细胞检测到的基因数

后面做质控就是靠这两列。我建议创建后立刻看看分布:

summary(pbmc$nCount_RNA)
summary(pbmc$nFeature_RNA)

如果有细胞 UMI 总数只有几十,那基本就是空液滴或者死细胞,后面要滤掉的。

4.7 一张图让你看懂创建流程

下面这张图概括了从原始文件到Seurat对象的完整过程。我画得比较简洁,重点就是“三个文件 -> 稀疏矩阵 -> Seurat对象 + 元数据”。

CreateSeuratObject 数据流 CreateSeuratObject:从文件到对象 matrix.mtx.gz 稀疏计数矩阵 barcodes.tsv.gz 细胞条码列表 features.tsv.gz 基因信息 Read10X() 读取并合并 dgCMatrix 稀疏矩阵 CreateSeuratObject() 参数:counts, project, min.cells, min.features Seurat 对象(含元数据) nCount_RNA nFeature_RNA

你看,从三个文件到最终Seurat对象,中间只经过了两次函数调用。但背后的数据转换和元数据初始化,都是自动完成的。

4.8 常见问题与我的对策

  • 内存不够? 如果数据超过几十万细胞,建议用 Read10X 时设 progress = TRUE 看着进度条。还可以考虑先用 min.cells 过滤掉低表达基因。我曾经处理一个300G的10x数据集,就是靠这个参数把内存占用降到了80G以内。
  • 读进去是空的? 检查一下目录路径,尤其是有没有拼错 filtered_feature_bc_matrix 还是 raw_feature_bc_matrix。raw的是带空液滴的,filtered是Cell Ranger已经过滤过的。
  • 基因名重复了? 如果数据里有多个转录本共享同一个基因名,Seurat会报错。这时候最好去原始数据里检查。如果不想改原文件,可以用 Read10X 的 strip.suffix = TRUE 或者 make.unique 处理。
记住: CreateSeuratObject 不是万能的。如果你的数据是10x Multiome(同时有RNA和ATAC),需要分别读取,然后用 CreateSeuratObject 建RNA和ATAC两个assay,再合并成多模态对象。这个我们后面会专门讲。