第二讲:数据导入与格式转换
做单细胞空间转录组分析,第一步就是把手头各种格式的数据给读进来。我刚开始接触这行时,面对.h5ad、.rds、.h5各种文件,确实懵过一阵。今天咱们就把这事捋清楚。
说白了,不同工具生态用不同格式。Scanpy 认 H5AD,Seurat 认 RDS,SPATA2 也有自己的规矩。我们做整合分析时,经常要在这些格式之间来回倒腾。别怕,核心套路就那么几种,掌握了就通。
2.1 H5AD 格式的读取
H5AD 是 Scanpy 生态的标准格式,底层是 Hierarchical Data Format。我自己项目中 70% 的数据都是这个格式。
在 Python 里,读 H5AD 就是一个函数的事:
import scanpy as sc
adata = sc.read_h5ad("sample_data.h5ad")
# 看一眼结构
print(adata)
# 输出类似:AnnData object with n_obs × n_vars = 10000 × 30000
# obs: 'cell_type', 'sample_id'
# var: 'gene_symbol'
# obsm: 'spatial', 'X_pca'
# uns: 'spatial_neighbors'
嗯,注意看 obsm['spatial'],这个就是空间坐标矩阵。没有这个,空间分析无从谈起。
我建议拿到数据后先做个快速检查:
# 检查必须有字段
assert 'spatial' in adata.obsm, "缺空间坐标!"
assert adata.X is not None, "缺表达矩阵!"
print("数据完整性检查通过")
X 是稀疏矩阵格式,但某些函数要求稠密矩阵。记得用 adata.X.toarray() 转一下。
2.2 Seurat 对象的读取
跳转到 R 语言生态,Seurat 是绝对的主角。我早期做项目时,手头有一批 Seurat v3 格式的 rds 文件,折腾了好久才读对。
library(Seurat)
library(SeuratDisk)
# 方法一:直接读 rds
seurat_obj <- readRDS("seurat_object.rds")
# 方法二:从 h5seurat 读
seurat_obj <- LoadH5Seurat("spatial_data.h5seurat")
# 看一眼结构
print(seurat_obj)
# 输出类似:An object of class Seurat
# 17399 features across 8560 samples within 1 assay
# Active assay: RNA (17399 features, 0 variable features)
这里有个坑:readRDS 读的是整个 R 对象,如果文件是旧版 Seurat,可能兼容性有问题。我个人经验是优先用 LoadH5Seurat,更稳定。
2.3 SPATA2 对象的读取
SPATA2 是专门做空间转录组的 R 包,格式跟 Seurat 不太一样。我记得第一次用这个包时,文档看半天才明白它的数据结构。
library(SPATA2)
# 从预处理的 rds 文件读
spata_obj <- readRDS("spata_object.rds")
# 或者从空间转录组原始输出构建
spata_obj <- createSPATAObject(
counts = count_matrix,
spatial = spatial_info_df,
sample_name = "tumor_sample_001"
)
SPATA2 对象的核心是里面藏了个 SpatialData 类的结构,空间坐标、图像、表达数据都封装在一起。你想想看,这其实比 Seurat 更专一,就是为空间数据设计的。
2.4 格式间的转换
好了,三种格式都读进来了。接下来就是核心问题:怎么在它们之间互转?
| 从/到 | H5AD | Seurat | SPATA2 |
|---|---|---|---|
| H5AD → | - | SeuratDisk 包 | SPATA2 转换函数 |
| Seurat → | SeuratDisk 包 | - | SeuratToSpata2() |
| SPATA2 → | spata2_to_h5ad() | spata2_to_seurat() | - |
具体到代码,我个人常用的一条转换链路是这样的:
H5AD → Seurat:
# Python 端先把 anndata 转成 h5seurat
import scanpy as sc
import anndata2ri
adata = sc.read_h5ad("input.h5ad")
# 保存为 h5seurat 格式
adata.write_h5ad("temp.h5ad")
# 再用 R 的 SeuratDisk 转换
# R 代码:
# library(SeuratDisk)
# Convert("temp.h5ad", dest = "h5seurat", overwrite = TRUE)
Seurat → H5AD:
# R 代码
library(SeuratDisk)
# 保存为 h5seurat
SaveH5Seurat(seurat_obj, filename = "temp.h5seurat")
# 再转成 h5ad
Convert("temp.h5seurat", dest = "h5ad")
Seurat → SPATA2:
# R 代码
library(SPATA2)
library(Seurat)
spata_obj <- SeuratToSpata2(
object = seurat_obj,
assay_name = "Spatial",
sample_name = "sample1"
)
obsm['spatial'] 是空的,排查了半天才发现是 Seurat 对象里坐标存在 tissue.positions 子表里,需要手动提取。建议每次转换后打印结构确认完整性。
2.5 知识框架图
为了让你看得更清楚,我把整个数据导入和格式转换的逻辑画成了这张图:
2.6 避坑总结
- 坐标格式不一致:H5AD 里空间坐标是矩阵,Seurat 里可能是数据框,转换时要确认行列不对齐。我建议统一用矩阵格式保存。
- 基因名冲突:不同数据集的基因标识符可能不一样(符号 vs ID),转换时记得用
var_names一一对应。我曾经因为大小写不一致,排查了一整天。 - 压缩格式问题:某些 H5AD 文件用了 LZF 或 GZIP 压缩,R 端可能读不了。用
scanpy.read_h5ad(..., backed='r')可以查看压缩类型。 - Seurat 版本兼容:v3 和 v4/v5 的结构有变化。我习惯装 SeuratDisk 的开发版本来处理新旧格式。
2.7 实战检查清单
每次读入新数据,我都会过一遍这个清单:
- 行/列对应:cells × genes 维度和文档一致
- 空间坐标:
obsm['spatial']存在,且行列数匹配(n_cells × 2) - 无缺失值:表达矩阵里不要有 NaN
- 基因名唯一:
var_names没有重复 - 元信息完整:
obs里有必要的 sample_id、cell_type 等列
做到这几点,后续分析会顺畅很多。记住,数据导入这一步花的时间,后面十倍的返工都省回来了。