4. 数据导入:从10X格式、h5ad或CSV读取数据

数据导入这一步,看着简单,其实坑不少。
我当年刚用Scanpy时,遇到过读进来的矩阵维度对不上,找了好久才发现是文件路径写错了。
今天咱们就把三种主流格式——10X格式、h5ad、CSV——的读取方法捋清楚,顺便聊聊我踩过的坑。

四种常见格式,你该用哪个?

Scanpy支持的数据格式远不止这三种,但实战中90%的场景都在它们之间切换。
我个人习惯按这个优先级选:

  • h5ad:首选,原生Anndata格式,读写快,保留所有信息。
  • 10X格式:最常用,Cell Ranger输出就是这个,需要三个文件。
  • CSV/TSV:老格式,兼容性强,但性能差、易丢元数据。
  • 其他(如HDF5、MTX):极少用,暂不展开。

那具体怎么读?下面逐个拆解。

读取10X格式:三件套别缺了

10X Genomics的Cell Ranger管道会输出三个文件:

  • matrix.mtx(或matrix.mtx.gz):稀疏矩阵,包含非零计数。
  • barcodes.tsv(或barcodes.tsv.gz):细胞条形码列表。
  • features.tsv(或genes.tsv):基因列表,第三列是特征类型(如Gene Expression)。

Scanpy用sc.read_10x_mtx一步搞定:

import scanpy as sc

adata = sc.read_10x_mtx(
    "path/to/filtered_feature_bc_matrix/",   # 包含三个文件的目录
    var_names='gene_symbols',                 # 用基因符号作为变量名
    cache=True                                # 缓存解析结果,下次秒开
)

print(adata)
小提示:路径指向目录,不是单个文件。Scanpy会自动找matrix.mtx等文件。如果文件名不同(比如后缀.gz),它也认得。

我记得有一次同事把filtered_feature_bc_matrix文件夹直接放到了path/to下,结果读进来全是空矩阵。后来发现目录名少写了一层……嗯,路径层级一定要和Cell Ranger输出一致,否则会抛FileNotFoundError。

如果你拿到的是Gene Expression + Antibody Capture的联合产物,features.tsv第三列会标注“Antibody Capture”。Scanpy默认只读“Gene Expression”部分,如果想保留所有特征,需要加参数:

adata = sc.read_10x_mtx(
    "path/to/raw_feature_bc_matrix/",
    gex_only=False   # 保留非基因表达特征
)

读取h5ad:最省心的选择

h5ad是Anndata的原生格式,一句话就够:

adata = sc.read_h5ad("data.h5ad")

为什么我推荐优先用h5ad?因为它读写快、体积小、保留完整元数据。你在预处理阶段加的.obs、.var、.uns都能完整保存下来。而CSV读进去只有表达矩阵,所有别的信息都得重算。

不过有一点要注意:h5ad的版本兼容性。Scanpy 1.8.x和1.9.x生成的h5ad,低版本Scanpy可能读不了。我遇到过跨环境迁移时读不了,最后用sc.read的backed模式先检查文件头才找到原因。

避坑:h5ad文件如果非常大(>50GB),用backed='r'模式只读加载,避免内存爆炸:
adata = sc.read_h5ad("large_file.h5ad", backed='r')

读取CSV/TSV:万不得已的选择

有时候上游工具只输出CSV,比如一些老的scRNA-seq流程或第三方分析结果。Scanpy提供sc.read_csv和sc.read_tsv:

# CSV:行是细胞,列是基因
adata_csv = sc.read_csv("counts.csv", first_column_names=True)

# TSV:用sc.read_text更灵活
adata_tsv = sc.read_text("counts.tsv", delimiter='\t', first_column_names=True)

这里有个关键:行列方向。Scanpy默认n_obs × n_vars,即行是细胞、列是基因。如果你的CSV是基因×细胞,需要转置后再传入:

import pandas as pd
df = pd.read_csv("data.csv", index_col=0)
adata = sc.AnnData(df.T)  # 转置

为什么不推荐CSV?说白了,CSV没有元数据,连.var的gene_id和gene_symbol都分不清。而且大矩阵读写慢成龟速。我建议:只要不是被迫,就别用CSV。

通用读取函数:sc.read

Scanpy还有个万能函数sc.read,能自动识别格式:

# 自动根据后缀选择读取方式
adata = sc.read("data.h5ad")         # → read_h5ad
adata = sc.read("data.csv")          # → read_csv
adata = sc.read("data.mtx")          # → 只读矩阵,需额外指定元数据

但注意:对于10X格式,不能直接用sc.read读matrix.mtx(它会当作普通MTX读,丢掉基因和条形码)。10X格式必须走sc.read_10x_mtx或sc.read_10x_h5。

数据导入决策流程

一句话总结:首选h5ad,其次10X专用函数,最后才是CSV。
数据导入决策流程 原始数据文件 格式判断 sc.read_h5ad sc.read_10x_mtx sc.read_csv AnnData 对象 调优建议 h5ad:最快最全;10X:专门优化,自动解析元数据;CSV:仅用于简单场景,注意行列方向

常用参数对照表

不同读取函数的参数差异很大,我整理了一个速查表,方便你直接粘贴:

函数 关键参数 注意事项
sc.read_10x_mtx var_names('gene_symbols'或'gene_ids')、gex_only、cache 目录路径,三文件必须存在且命名规范
sc.read_h5ad backed('r'或'r+')、filename 大文件用backed='r'节省内存
sc.read_csv first_column_names、delimiter 只能读基因×细胞的转置矩阵时手动处理
sc.read_10x_h5 gex_only、genome 专门读取Cell Ranger的h5输出(单文件格式)

实战避坑指南

这些年我帮同事调试读数据的问题,总结出三个高频翻车点:

  1. 路径写成了文件而非目录:read_10x_mtx会直接报错,别问我怎么知道的。
  2. CSV行列方向反了:读进来一看n_obs只有几百,明显是基因数量。记得检查adata.shape,如果n_obs远小于n_vars,大概率需要转置。
  3. h5ad跨版本不兼容:我在用Scanpy 1.9.8写的h5ad,放到1.7.0的环境里死活读不了。解决方案:拿新版本再写一遍,或者用anndata.read_h5ad的backed模式先浏览内容。
核心建议:在项目中尽早确定数据流格式。如果团队统一用h5ad,后面所有人都不用纠结文件路径和列名。我曾经在一个大项目里,三个组各用各的格式,集成时花了一整天做格式转换……嗯,从那以后我们强制所有上游输出h5ad。

好了,数据导入就这些。
接下来你可以在实际数据上试试:找个10X数据集跑一遍sc.read_10x_mtx,再用sc.read_h5ad读我的测试文件(我会在代码仓库里放一个示例h5ad)。
读进来以后,记得先跑adata.obs_names[:5]和adata.var_names[:5]看一眼,确认数据没跑偏。