单细胞 RNA-seq 数据格式与读取 — 别被文件后缀吓到
好,咱们进入正题。单细胞数据到底长什么样?说白了,它跟 bulk RNA-seq 最大的区别就是——每个细胞都有自己的“小账本”。
我刚接触单细胞那会儿,打开下载的文件夹就懵了。一堆 .mtx、.h5、.tsv、.barcodes.tsv……当时心想,这是要搞数据考古吗?
其实你不用担心。这些格式虽多,但核心逻辑就一个:基因 × 细胞 的表达矩阵。Seurat 帮我们封装了大部分读取工作,你只需要知道它在背后做了什么。
三种核心数据格式
单细胞数据格式主要分为三大类。我个人习惯把它们比作“散装零件”、“整装零件”和“拼好模型”。
| 格式 | 典型文件 | 特点 | 读取方式 |
|---|---|---|---|
| MTX(Market Exchange Format) | matrix.mtx, genes.tsv, barcodes.tsv | 稀疏矩阵,只存储非零值,省空间 | Read10X() |
| HDF5(.h5) | filtered_feature_bc_matrix.h5 | 单一文件包含所有信息,读取快 | Read10X_h5() |
| H5AD / Loom / CSV | .h5ad, .loom, .csv | 其他工具(Scanpy、Monocle)常用 | ReadH5AD() 等专用函数 |
我的建议是:如果你从 10X Genomics 拿数据,优先用 .h5 格式。我曾经在项目里用 MTX 格式,结果一文件夹有十几个文件,传输时漏了一个,跑了两小时才发现。
MTX 格式读取实战
先看最常见的 10X 输出。一个标准的 Cell Ranger 结果目录长这样:
./sample/
├── filtered_feature_bc_matrix/
│ ├── matrix.mtx.gz
│ ├── barcodes.tsv.gz
│ └── features.tsv.gz
用 Seurat 读取时,你只需要一句话:
library(Seurat)
data <- Read10X(data.dir = "./sample/filtered_feature_bc_matrix/")
嗯,这里要注意:Read10X() 会自动识别 .mtx.gz 还是 .mtx,也会处理好 barcodes 和 features 的对应关系。
返回的是一个 稀疏矩阵(dgCMatrix)。为什么用稀疏矩阵?你想想看,单细胞数据中绝大多数基因在单个细胞里是0表达。10万细胞 × 2万基因,如果全存成普通矩阵……内存直接爆炸。
避坑指南:我曾经遇到一个情况,用 Read10X 读取后发现所有 counts 都是整数——这其实不对。Cell Ranger 的输出是 UMI 计数,应该是整数。如果你看到小数,检查一下是不是用了 Read10X() 而文件是 Feature Barcode Matrix(抗体标签数据),那个是浮点数。
HDF5 格式读取
HDF5 文件把所有内容打包成一个 .h5 文件。读取也更简单:
data <- Read10X_h5(filename = "./sample/filtered_feature_bc_matrix.h5")
# 查看一下内部结构
slotNames(data)
# [1] "Gene Expression" "Antibody Capture"
如果同时有基因表达和抗体捕获数据,它会返回一个 list。用 names(data) 可以查看有哪些 assay。
我个人的习惯:项目初期切探索数据时,我喜欢用 MTX 格式,因为能直接查看 .tsv 文件;确认无误后,我会转存成 .rds 或 .h5seurat,读取更快。
其他格式与转换
单细胞生态越来越开放,你可能会拿到其他工具生成的文件:
| 读取函数 | 输入格式 | 来源/说明 |
|---|---|---|
ReadH5AD() |
.h5ad | Scanpy (Python) 的输出 |
ReadLoom() |
.loom | Loom 格式,常用于 Velocyto |
ReadSTARsolo() |
STARsolo 输出 | STAR 比对后的计数矩阵 |
ReadMEX() |
MTX 目录 | 非 10X 的 MTX 格式 |
关键点:无论你用什么格式读入,最终都建议转成 SeuratObject。后续所有质控、整合、可视化都基于这个对象。如果你用 Scanpy 分析到一半想转 Seurat,可以用 ReadH5AD() 读入,再用 as.Seurat() 转换。
格式选择策略
项目里到底用哪种格式?我总结了一套判断逻辑:
- 如果你是 10X 标准流程 — 直接用 .h5,省事
- 如果你要手工检查数据 — 用 MTX,方便 grep/head 看原始文件
- 如果你有多个样本需要整合 — 先用 Read10X/Read10X_h5 读成 list,再 merge
- 如果你要跨平台协作(Python+R) — 用 .h5ad 作为中间格式
为什么会这么选择?说白了,h5 文件大、读取快、不易出错;MTX 文件多、传输麻烦但可人工审计;h5ad 则兼容性最好。
一个小技巧:如果你不确定文件格式,先跑 file.info() 查看文件大小。.h5 一般几百 MB 到几个 GB,MTX 的三件套加起来通常更大但更“散”。我第一次遇到 raw 和 filtered 两个目录没搞清,直接读 raw 目录进去了,结果后面质控一塌糊涂……后来才注意到 filtered 目录才是降噪后的。
所以,这一章的知识结构
我把整个过程画成了一张图,你看一遍就明白数据从原始文件到 Seurat 对象的完整路线:
你看,整个流程很清晰:原始数据经过不同读取函数,统一变成稀疏矩阵,最后装入 Seurat 对象。后面所有的质控操作,都是在操作这个对象里的数据。
我之前遇到一个情况,用户用 Read10X 读错了目录,读到了 raw_feature_bc_matrix 而不是 filtered 的,结果后面过滤了两次,细胞数少了一半。嗯,这里要提醒一句:Cell Ranger 输出时,raw 目录包含所有被检测到的 barcode(含背景噪声),filtered 目录才是初步过滤后的细胞。通常我们分析用 filtered 目录。