第4章:创建SingleCellExperiment对象——核心数据结构讲解
在单细胞数据分析中,数据怎么存,直接影响后面一切操作的效率和正确性。我见过不少新手,一开始就把表达矩阵扔进一个普通 data.frame 里,做着做着就发现行名丢了、列名乱了、元数据对不上了。
所以这一章,咱们聚焦在 SingleCellExperiment 这个核心容器上。说白了,它就是为单细胞数据量身定做的 DataFrame Plus。
4.1 为什么非要用 SCE?
你想想看,一个单细胞实验里,我们至少需要存这些东西:
- 表达矩阵(基因×细胞)
- 细胞元数据(样本来源、批次、处理条件等)
- 基因元数据(基因类型、是否高变基因等)
- 降维结果(PCA、t-SNE、UMAP 坐标)
- 质控统计量(库大小、检测基因数、线粒体比例等)
如果用多个独立变量去存,很容易出现索引不一致。我就掉过这个坑——有一次把表达矩阵和细胞注释的行顺序搞反了,结果跑了半天的聚类,发现全是错的。
SCE 解决了这个痛点。它把所有数据整齐地放进一个对象里,按“基因 x 细胞”二维结构统一索引,再也不怕对应不上。
| 对比项 | 普通 data.frame / 独立变量 | SingleCellExperiment |
|---|---|---|
| 数据组织 | 分散在各处 | 统一容器 |
| 索引对齐 | 需手动维护 | 内置行/列名绑定 |
| 扩展性 | 差 | 支持任意附属数据 |
| 内存效率 | 一般 | 支持稀疏矩阵、延迟计算 |
一句话总结:只要接触到 scran 的质控、标准化、降维等后续步骤,你基本绕不开 SCE。
4.2 SCE 对象的核心 Slots
一个 SCE 对象的结构,可以想象成一栋四层小楼,每层负责不同的数据类型。
library(SingleCellExperiment)
# 模拟一个最简单的SCE对象
counts_matrix <- matrix(rpois(100, lambda = 10), nrow = 20, ncol = 5)
rownames(counts_matrix) <- paste0("gene", 1:20)
colnames(counts_matrix) <- paste0("cell", 1:5)
sce <- SingleCellExperiment(
assays = list(counts = counts_matrix)
)
sce
4.3 四个主层:assays, colData, rowData, metadata
4.3.1 assays —— 表达数据的核心层
这是主角。assays 可以放多个表达矩阵,比如 raw counts、归一化后的 logcounts、TPM 等。每个矩阵的维度必须一样(基因×细胞)。
# 添加第二个assay
logcounts(sce) <- log2(counts(sce) + 1)
# 看看现在有几个assays
assayNames(sce)
为什么需要多个?我在项目中发现,早期拿 counts 做质控最靠谱,但后续降维和可视化时 log-normalized 数据更合适。都保留在同一个对象里,切换起来很方便。
我个人习惯:始终保留 raw counts 作为第一个 assay,免得后续标准化方法不满意,还得重新从源头算。
4.3.2 colData —— 细胞级别的元数据
colData 是一个 DataFrame,每一行对应一个细胞,每一列对应一个属性。比如:样本 ID、批次、质控通过/未通过、UMAP 聚类标签等。
# 添加细胞元数据
colData(sce)$sample_id <- rep(c("sample1", "sample2"), length.out = ncol(sce))
colData(sce)$pass_qc <- c(TRUE, TRUE, FALSE, TRUE, FALSE)
# 还可以一步到位创建
sce$total_counts <- colSums(counts(sce))
sce$detected_genes <- colSums(counts(sce) > 0)
这里有个小技巧:直接用 sce$列名 就能存取,和操作 data.frame 没什么两样。
4.3.3 rowData —— 基因级别的元数据
用来记录每个基因的信息:是否线粒体基因、是否核糖体基因、基因符号、是否被鉴定为高变基因等。
# 标记线粒体基因
rowData(sce)$is_mito <- grepl("^MT-", rownames(sce))
# 标记核糖体基因
rowData(sce)$is_ribo <- grepl("^RPL|^RPS", rownames(sce))
嗯,这里要注意:在人类基因中,线粒体基因以 "MT-" 开头;小鼠则是 "mt-"。不同物种别搞混了。
4.3.4 metadata —— 实验级别的元数据
放一些不针对某个细胞或基因的整体信息,比如:实验日期、使用的软件版本、质控阈值、分析流水线参数等。
metadata(sce)$experiment_date <- "2024-03-15"
metadata(sce)$scran_version <- packageVersion("scran")
metadata(sce)$qc_threshold <- list(
min_library_size = 500,
max_mito_ratio = 0.2
)
这东西看起来不起眼,但我在复现别人结果时,经常靠 metadata 来确认参数配置,省了很多翻脚本的时间。
4.4 两个常用附属组件
4.4.1 reducedDims —— 降维结果存放处
做 PCA、t-SNE、UMAP 之后,结果别乱扔,直接存进 reducedDims。
# 假设我们有PCA结果
pca_res <- matrix(runif(15), nrow = 5, ncol = 3)
colnames(pca_res) <- paste0("PC", 1:3)
rownames(pca_res) <- colnames(sce)
reducedDim(sce, "PCA") <- pca_res
# 再添加UMAP
umap_res <- matrix(runif(10), nrow = 5, ncol = 2)
reducedDim(sce, "UMAP") <- umap_res
4.4.2 sizeFactors —— 标准化缩放因子
这是 scran 质控和标准化流程的关键产物,专门用一个 slot 存储。后面做 normalization 时,scran 会自动读取这个值。
# 快速体验(后续章节会详解)
library(scran)
sce <- computeSumFactors(sce)
sizeFactors(sce)
我曾经过的坑:sizeFactors 和 counts 的长度必须与细胞数一致,且不能有 NA 或 0。如果某个细胞 sizeFactor 为 0,后面 logNorm 会出 NaN。检查一下!
4.5 SCE 的对象结构总览
为了让你更直观地理解这些组件之间的关系,我画了一张结构图。
看到这张图应该能理解了吧。assays 是房子主体,colData 和 rowData 是挂在墙上的标签,metadata 是房子的说明书,reducedDims 和 sizeFactors 是后来添加的家具和装饰。
4.6 实战:构建一个完整的 SCE 对象
我们结合一个迷你数据集,把上面讲的串起来。
# 1. 原始数据
set.seed(100)
counts <- matrix(rnbinom(10000, mu = 10, size = 0.5), nrow = 200, ncol = 50)
rownames(counts) <- paste0("gene", 1:200)
colnames(counts) <- paste0("cell", 1:50)
# 2. 构建SCE
sce <- SingleCellExperiment(
assays = list(counts = counts),
colData = DataFrame(
sample_id = rep(c("A", "B"), each = 25),
cell_barcode = colnames(counts)
),
rowData = DataFrame(
gene_symbol = paste0("SYMBOL_", 1:200),
is_mito = grepl("^MT", rownames(counts))
),
metadata = list(
created_at = Sys.time(),
creator = "your_name"
)
)
# 3. 计算一些质控指标并存入colData
library(scuttle)
sce <- addPerCellQCMetrics(sce, subsets = list(Mito = which(rowData(sce)$is_mito)))
sce$total_counts # 每个细胞的文库大小
sce$detected # 检测到的基因数
sce$subsets_Mito_percent # 线粒体比例
# 4. 存一个降维结果(模拟)
pca_result <- matrix(rnorm(50*10), nrow = 50, ncol = 10)
colnames(pca_result) <- paste0("PC", 1:10)
reducedDim(sce, "PCA") <- pca_result
sce
重点回顾:
- SCE 结构清晰,统一索引,扩展性强
- assays 存表达数据,colData 存细胞注释,rowData 存基因注释,metadata 存实验级信息
- reducedDims 和 sizeFactors 是 scran 工作流中的重要伙伴
- 添加新数据时,直接
sce$新列或reducedDim(sce, "名称")即可
记住一句话:好的数据结构,是高质量分析的第一步。我在实际项目中,从来不会让代码里出现两个以上彼此独立的 data.frame 来表达单细胞数据。统一用 SCE,所有下游分析、可视化、协作都顺畅很多。