单细胞 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() 转换。

格式选择策略

项目里到底用哪种格式?我总结了一套判断逻辑:

  1. 如果你是 10X 标准流程 — 直接用 .h5,省事
  2. 如果你要手工检查数据 — 用 MTX,方便 grep/head 看原始文件
  3. 如果你有多个样本需要整合 — 先用 Read10X/Read10X_h5 读成 list,再 merge
  4. 如果你要跨平台协作(Python+R) — 用 .h5ad 作为中间格式

为什么会这么选择?说白了,h5 文件大、读取快、不易出错;MTX 文件多、传输麻烦但可人工审计;h5ad 则兼容性最好。

一个小技巧:如果你不确定文件格式,先跑 file.info() 查看文件大小。.h5 一般几百 MB 到几个 GB,MTX 的三件套加起来通常更大但更“散”。我第一次遇到 raw 和 filtered 两个目录没搞清,直接读 raw 目录进去了,结果后面质控一塌糊涂……后来才注意到 filtered 目录才是降噪后的。

所以,这一章的知识结构

我把整个过程画成了一张图,你看一遍就明白数据从原始文件到 Seurat 对象的完整路线:

单细胞RNA-seq数据格式与读取流程 原始数据 MTX 格式 HDF5 格式 其他格式 Read10X() Read10X_h5() ReadH5AD() / ReadLoom() 稀疏矩阵 (dgCMatrix) SeuratObject 说明

你看,整个流程很清晰:原始数据经过不同读取函数,统一变成稀疏矩阵,最后装入 Seurat 对象。后面所有的质控操作,都是在操作这个对象里的数据。

我之前遇到一个情况,用户用 Read10X 读错了目录,读到了 raw_feature_bc_matrix 而不是 filtered 的,结果后面过滤了两次,细胞数少了一半。嗯,这里要提醒一句:Cell Ranger 输出时,raw 目录包含所有被检测到的 barcode(含背景噪声),filtered 目录才是初步过滤后的细胞。通常我们分析用 filtered 目录。