读取数据:三个核心函数

好,到了真正动手的环节了。

数据读取,是所有分析的起点。这一步如果错了,后面全是白费功夫。我个人习惯在项目一开始就花几分钟确认数据格式,而不是跑了几十个步骤才发现读进来的数据是空的——这种坑我踩过不止一次。

今天咱们就聊透 Scanpy 的三个主力读取函数:read_10x_mtx、read_h5ad 和 read_csv。每个函数长什么样,参数怎么配,什么时候用哪个,我给你掰扯清楚。

4.1 家族图谱:数据从哪里来

在读代码之前,先让你对这三个函数的关系有个整体感觉。下面这张图总结了它们各自服务的场景:

Scanpy 数据读取函数选择流程 原始数据 read_10x_mtx 10X 标准输出 read_h5ad 已处理 Anndata read_csv 外部表格数据 AnnData 对象 预处理 → 聚类 → 注释 提示: 路径、文件存在性、压缩格式——这些细节决定了读取能否一次成功。 我见过太多报错都是因为路径写错或者忘了解压 .gz。

4.2 read_10x_mtx:标准 10X 数据读取

做单细胞分析,10X Genomics 的数据格式基本是标配。你从 Cell Ranger 拿到的输出目录里,通常会有一个 filtered_feature_bc_matrix 文件夹。里面有三个文件:matrix.mtx、barcodes.tsv 和 features.tsv。

read_10x_mtx 就是专门吃这种结构的。你用不着手动拼文件路径,它自己会去目录里找。

核心参数一览
参数类型默认值说明
pathstr必填目录路径,指向包含三个文件的文件夹
var_namesstr"gene_symbols"选 gene_ids 还是 gene_symbols 作为变量名
make_uniqueboolTrue是否自动去重重复的基因名
cacheboolFalse是否缓存原始数据,节省下次读取时间
gzippedboolNone自动检测文件是否 gzip 压缩
我的惯用写法:
import scanpy as sc

adata = sc.read_10x_mtx(
    path="data/filtered_feature_bc_matrix/",
    var_names="gene_symbols",
    make_unique=True
)
print(adata)
# Output: AnnData object with n_obs × n_vars = 5000 × 33694
避坑点——我曾经在这个参数上卡了半小时:
# 错误示范:自己拼接文件路径
adata = sc.read_10x_mtx("data/matrix.mtx")  # 不要这样做!

# 正确做法:给目录路径
adata = sc.read_10x_mtx("data/filtered_feature_bc_matrix/")

函数会自动去找 matrix.mtx。你想想看,如果每次都手动拼文件名,换一个批次就得改代码,多容易出错。

还有一个细节:var_names 选 "gene_symbols" 还是 "gene_ids"?我推荐前者,因为后续画图、查找 marker 时,人类可读的基因名比 ENSEMBL ID 直观太多。

4.3 read_h5ad:加载已处理数据

如果你手头有一份 .h5ad 文件,那真是最省心的格式了。h5ad 是 AnnData 的原生持久化格式,里面把矩阵、观测信息、变量信息、非结构化元数据全都打包了。说白了,读进来就直接是一个完整的 AnnData 对象,什么都不用再配。

示例:
adata = sc.read_h5ad("path/to/processed_data.h5ad")
print(adata)
# Output: AnnData object with n_obs × n_vars = 15000 × 2000
#     obs: 'cell_type', 'sample_id', 'n_genes'
#     var: 'highly_variable'
#     uns: 'pca', 'umap', 'neighbors'

你可能会问:那什么时候用 read_10x_mtx,什么时候用 read_h5ad?我一般这样区分:

场景推荐函数理由
从 Cell Ranger 原始输出开始read_10x_mtx直接读目录,保留完整变量信息
加载中间处理结果read_h5ad已经过质控、标准化,层次化组织
与其他工具、合作者交换数据read_h5ad单一文件,可自包含所有信息
只想快速预览数据read_h5ad一行代码,零配置

4.4 read_csv:灵活导入外部表格

现实工作中,数据格式并不总是那么标准。有时候你会拿到第三方平台导出的 CSV、TSV,或者某个实验室自己整理的表达矩阵。read_csv 就是为你提供后门的。

注意:
import pandas as pd
import scanpy as sc

# 先读成 DataFrame
df = pd.read_csv("my_data.csv", index_col=0)
# 再转成 AnnData
adata = sc.AnnData(X=df.values,
                   obs=pd.DataFrame(index=df.index),
                   var=pd.DataFrame(index=df.columns))
print(adata.shape)
# Output: (8000, 30000)

嗯,这里要注意,read_csv 本身只能读外部 CSV 后构造 AnnData,不能一步到位。不过我个人更喜欢先用 pandas 看看数据质量,再决定要不要转。

read_csv 的参数其实不多,因为它本质上是给 pd.read_csv 搭了个桥:

参数说明
filename文件路径
delimiter分隔符,默认制表符(TSV),CSV 时用逗号
first_column_names第一列是否当作列名
backed是否以 backed 模式读取(不全部载入内存)

4.5 实战对比:我该怎么选?

为了让你更直观地记住区别,我把三个函数的核心特征拉了个表:

维度read_10x_mtxread_h5adread_csv + 构造
输入格式目录(含三个标准文件)单个 .h5ad 文件任意 CSV/TSV 表格
自动解析自动读取矩阵、barcode、feature完整恢复 AnnData需要手动指定索引/列名
数据完整性只含原始计数可包含 obs/var/uns 等取决于导出方
使用频次项目初期项目中期及二次加载特殊对接场景
加载速度较快极快较慢(依赖 pandas)
适合人群所有单细胞操作者任何有 .h5ad 的场景有第三方数据的人
几个实战小技巧:
  • 如果你的数据量很大(比如几十万细胞),考虑用 backed='r' 模式,这样读取时不会一下子吃掉所有内存。
  • 每次读完数据,我都习惯先打印 adata,瞄一眼 n_obs 和 n_vars。如果数字是 0,嗯,八成是路径错了。
  • 从 10X 读取后,可以顺手 adata.var_names_make_unique(),防止后续报重名错误。