第1章 Seurat基础:数据对象SeuratObject、Assays、meta.data结构详解

1.1 从一个问题开始:SeuratObject到底是什么?

刚接触Seurat的同学,十有八九会被这个名词搞晕。其实没那么玄乎——说白了,SeuratObject就是Seurat用来接住你所有单细胞数据的那个盒子。你跑完10X Genomics的Cell Ranger,拿到了一堆矩阵文件;或者你从别处下载了表达矩阵,要往Seurat里灌,都得先装进这个盒子里。

我记得第一次接触时,总觉得它很神秘,还去读源码。后来才明白:一个SeuratObject就是一个多层字典,里面既有表达矩阵,也有细胞注释,还有下游分析结果。你要学会的不是记它的每个槽位,而是理解它的三层架构——Assays、meta.data、reductions。

这章我们就盯死前两层:Assays和meta.data。这两层搞定,后面网格搜索搞聚类参数,你手不抖。

个人习惯:我每次拿到新数据,第一件事不是跑PCA或聚类,而是先打印一下SeuratObject的结构。你敲两行str()或dim(),心里就有底了。

1.2 三层架构:SeuratObject的骨架

一个SeuratObject长什么样?给你看一眼就知道了:

# 假设你已经创建了一个SeuratObject,叫pbmc
> pbmc
An object of class Seurat 
13714 features across 2638 samples within 1 assay 
Active assay: RNA (13714 features, 0 variable features)
 2 layers present: counts, data

看到没?它告诉你三件事:

  • 13714 features —— 基因数量
  • 2638 samples —— 细胞数量
  • 1 assay —— 里面有一个叫"RNA"的Assay

再往里解剖,Assays是核心数据层,存表达矩阵;meta.data是细胞属性层,存注释信息;reductions是降维结果层,存PCA、UMAP坐标(后面章节再讲)。

你想想看,这三层是不是把单细胞分析需要的所有东西都装进去了?这就是Seurat的设计哲学——你只管往一个对象里塞数据,别操心变量怎么管理。

避坑指南:我曾经犯过一个低级错误——用$符号直接给SeuratObject加了一列,以为它会自动更新到meta.data里。结果呢?它不会!你得用AddMetaData()。或者直接用pbmc$new_column <- ...,这个倒可以,但仅限于meta.data列。

1.3 Assays:表达数据的存放地

一个SeuratObject可以装多个Assay。比如你同时测了RNA、ATAC,那就有两个Assay,名字分别叫"RNA"和"ATAC"。但绝大多数情况下,你只有一个Assay,默认就叫"RNA"。

每个Assay内部又有三个最主要的矩阵:

矩阵名 数据类型 存储内容 常用场景
counts 原始整数 UMI计数 差异表达、QC
data 归一化浮点数 NormalizeData后的结果 可视化、聚类输入
scale.data 中心化缩放浮点数 ScaleData后的结果 PCA、热图

我在项目中遇到过:有同学把counts矩阵和data矩阵搞混了,结果差异基因一个都没找到——因为counts是整数,差异分析要求使用原始计数。说白了,counts是原材料,data是半成品,scale.data是精加工。你什么阶段用什么矩阵,心里得有数。

记住:聚类参数网格搜索时,用的矩阵是data(默认),不是counts。如果你想用原始counts做聚类,得显式指定slot = "counts"。但我不推荐,具体原因后面章节会说。

1.4 meta.data:细胞的“身份证”

我常常把meta.data比作细胞的"身份证"。有了它,你才知道每个细胞从哪来、是什么类型、质量好不好。

你创建一个SeuratObject时,Seurat会自动帮你生成几列:

> head(pbmc[[]])
                   orig.ident nCount_RNA nFeature_RNA
AAACATACAACCAC-1     SeuratProject       2419          90
AAACATACACGCTC-1     SeuratProject       2467         120
AAACATACAGACCGC-1     SeuratProject       3241          95
  • orig.ident —— 样本来源标识。如果你不指定,默认就是"SeuratProject"。
  • nCount_RNA —— 每个细胞的总UMI数。QC的时候看这个。
  • nFeature_RNA —— 每个细胞检测到的基因数。过滤低质量细胞看这个。

然后你可以自己加列。比如聚类后加的seurat_clusters、RNA_snn_res.0.5等,都存放在meta.data里。这些列在后面的网格搜索中会频繁用到——你要比较不同参数下的聚类结果,本质上就是对比meta.data里不同分辨率的cluster列。

一个小技巧:我常常用table(pbmc$RNA_snn_res.0.8)来看聚类结果,一目了然。你也可以用dim(pbmc[[]])查看meta.data的维度——行数永远等于细胞数。
避坑:很多人用pbmc@meta.data直接操作,但R的@符号是访问S4对象槽位的。如果你不小心改了内部结构,Seurat可能直接崩掉。我建议:读用@,写用AddMetaData()。

1.5 SVG结构图:一目了然的层次关系

上面讲了一堆,不如给你画张图。这是我自己整理的SeuratObject核心结构:

SeuratObject 三层核心结构 SeuratObject Assays (一个或多个) meta.data (数据框) counts data scale.data nCount_RNA nFeature_RNA orig.ident seurat_clusters 说明: SeuratObject 是容纳所有数据的顶层容器; Assays 存储多层表达矩阵,meta.data 存储细胞级注释信息。

图里很清楚:SeuratObject在最顶层,下面挂Assays和meta.data。Assays内部又分为counts、data、scale.data三个核心矩阵;meta.data里则是一列一列的细胞属性。你在网格搜索中要反复操作的,主要就是meta.data里的聚类标签列,以及控制Assay里哪个矩阵被送进聚类算法。

1.6 实战:亲手拆解一个SeuratObject

光说不练假把式。咱们来写两行代码,看看你能从SeuratObject里挖出什么信息。

# 创建一个简单的SeuratObject(假设你已安装Seurat)
library(Seurat)

# 模拟一个expression matrix(3个基因,5个细胞)
mat <- matrix(data = rpois(15, lambda = 3), nrow = 3, ncol = 5)
rownames(mat) <- c("GeneA", "GeneB", "GeneC")
colnames(mat) <- paste0("Cell", 1:5)

# 创建SeuratObject
seu_obj <- CreateSeuratObject(counts = mat, project = "test")

# 看看对象结构
str(seu_obj)

敲完str(seu_obj),你会看到类似这种输出:

Formal class 'Seurat' [package "Seurat"] with 13 slots
  ..@ assays      :List of 1
  .. ..$ RNA:Formal class 'Assay' [package "Seurat"] with 8 slots
  .. .. .. ..@ counts      :Formal class 'dgCMatrix' ...
  .. .. .. ..@ data        :Formal class 'dgCMatrix' ...
  .. .. .. ..@ scale.data  : matrix [1:3, 1:5] ...
  ..@ meta.data   :'data.frame':	5 obs. of  3 variables:
  .. ..$ orig.ident   : Factor w/ 1 level "test" ...
  .. ..$ nCount_RNA   : num [1:5] ...
  .. ..$ nFeature_RNA : num [1:5] ...

看懂了吗?@assays里面的RNA对象下,有counts、data、scale.data三个槽;@meta.data有orig.ident、nCount_RNA、nFeature_RNA三列。和你之前了解的完全一致。

现在你可以试试:

# 获取meta.data的前几行
head(seu_obj[[]])

# 获取counts矩阵
GetAssayData(seu_obj, slot = "counts")

# 获取默认的data矩阵
GetAssayData(seu_obj)

我个人习惯在每次新建SeuratObject后,都跑一下dim(seu_obj)和rownames(seu_obj[[]]),确认细胞和基因数量对得上。这一步做扎实了,后面网格搜索才不会在数据源头栽跟头。

总结一下本章核心:
- SeuratObject 是一个S4对象,内部包含Assays、meta.data、reductions等主要成分。
- Assays 存储表达数据,主要操作counts、data、scale.data三个矩阵。
- meta.data 是一个数据框,每一行对应一个细胞,每一列对应一个属性(如样本来源、聚类标签)。
- 聚类参数网格搜索中,你绝大多数时间在操作meta.data中的聚类结果列,以及控制Assay里的输入矩阵。