2. 数据格式与读取:常见空间数据格式与读取方法
空间单细胞数据,说白了就是给每个细胞或spot贴了个空间坐标。但各个平台输出的文件格式五花八门,不统一。
我刚开始接触空间转录组数据时,光是把h5ad转成Seurat对象就折腾了半天。所以这一章,咱们先把常见格式和读取方法捋清楚。
2.1 三大主流数据格式概览
目前主流的有三种:h5ad(Python系)、SpatialExperiment(R系)、Seurat(R系)。
我个人习惯是:如果上游用Scanpy处理,我就用h5ad;下游要用R做统计或可视化,就转成Seurat或SpatialExperiment。你想想看,选对格式能省不少事。
| 格式 | 核心框架 | 存储内容 | 典型扩展名 |
|---|---|---|---|
| h5ad | AnnData (Python) | 表达矩阵、元数据、空间坐标、邻接图 | .h5ad |
| SpatialExperiment | SpatialExperiment (R) | 表达矩阵、空间坐标、图像元数据 | .rds 或 .rda |
| Seurat | Seurat (R) | 表达矩阵、降维、空间坐标、图像 | .rds |
2.2 h5ad 格式与读取
h5ad 是 Python 生态最常见的格式。内部结构分为:X(表达矩阵)、obs(细胞/spot元数据)、var(基因元数据)、obsm(包括空间坐标)、uns(非结构化元数据)。
我用 Python 读取 h5ad 的写法很简单:
import scanpy as sc
adata = sc.read_h5ad('sample.h5ad')
print(adata)
# AnnData object with n_obs × n_vars = 5000 × 20000
# obs: 'batch', 'spot_id'
# obsm: 'spatial'
# uns: 'spatial_neighbors'
读取后,空间坐标一般在 adata.obsm['spatial'] 里。我遇到过一个坑:有些平台把坐标写在 adata.obs 里,命名成 array_row/array_col,手动检查一下才稳。
# 查看空间坐标
spatial_coords = adata.obsm['spatial']
print(spatial_coords.shape) # 5000 x 2
sc.read_h5ad 读取超大文件(比如 10万+ spots),内存会炸。我曾经在16GB的笔记本上直接崩了。建议用 backed=True 模式,按需读取。
# backed 模式,避免 OOM
adata = sc.read_h5ad('large_sample.h5ad', backed='r')
2.3 SpatialExperiment 格式与读取
SpatialExperiment 是 R 生态里的专用格式。它继承自 RangedSummarizedExperiment,多了个 spatialCoords 槽位专门存坐标。
我通常这样读:
library(SpatialExperiment)
spe <- readRDS('sample_spe.rds')
# 或者直接读取 h5ad 转过来
spe <- readH5AD('sample.h5ad')
print(spe)
# class: SpatialExperiment
# dim: 20000 5000
# spatialCoords names: 'x' 'y'
提取坐标也很直接:
coords <- spatialCoords(spe)
head(coords)
# x y
# spot_1 100.5 200.3
# spot_2 101.2 201.1
as.character() 或 factor() 手动修正一下就好。
2.4 Seurat 格式与读取
Seurat 格式是单细胞分析的经典格式。空间数据在 Seurat 里有个 Spatial 的 assay,里面包含表达矩阵、坐标(image 对象)、H&E 图像。
我一般这样读:
library(Seurat)
seurat_obj <- readRDS('sample_seurat.rds')
# 或者从 h5ad 转
seurat_obj <- Read10X_H5('filtered_feature_bc_matrix.h5')
# 再手动创建 Seurat 对象
查看空间坐标:
# 获取坐标
coords <- GetTissueCoordinates(seurat_obj)
head(coords)
# tissue row col
# 1 slice1 100 200
# 2 slice1 101 200
我个人习惯在 Seurat 对象里同时保留多个模态(gene expression + spatial + protein),用 @assays 隔开。
2.5 格式互转实战
大部分时候你不会只用一个生态。比如你从10X Visium拿到的是h5和JSON,你用Seurat处理完,又想在Python里做批次校正。这时候格式转换就不可避免。
有一回我接了个项目,上游给了10个h5ad文件,但下游R包只认SpatialExperiment。我写了段代码批量转:
library(SpatialExperiment)
library(zellkonverter)
# h5ad -> SpatialExperiment
for (f in list.files(pattern = '*.h5ad')) {
spe <- readH5AD(f)
saveRDS(spe, gsub('.h5ad', '_spe.rds', f))
}
反过来也差不多:
import anndata
import scanpy as sc
# Seurat rds -> h5ad
# 先通过R导出为h5ad,然后再用Python读
# 在R中:
# library(Seurat)
# library(SeuratDisk)
# SaveH5Seurat(seurat_obj, filename = 'temp.h5Seurat')
# Convert('temp.h5Seurat', dest = 'h5ad')
- 如果你团队主要用Python,就用h5ad + Scanpy。
- 如果下游分析要用R(比如聚类可视化),优先SpatialExperiment。
- Seurat适合深度单细胞经验的人,但空间数据多模态管理有些笨重。
2.6 本章知识结构
下图展示了三种格式的读取流程与互相转换的关系:
2.7 读取并验证数据
无论用哪种格式读取,我建议第一步先做完整性验证。别等到下游分析才发现空间坐标丢了。
# Python
def validate_adata(adata):
assert 'spatial' in adata.obsm, '空间坐标缺失!'
assert adata.obs.shape[0] == adata.obsm['spatial'].shape[0], 'spot数和坐标数不一致'
print('数据完整性检查通过')
# R
validate_spe <- function(spe) {
stopifnot(ncol(spe) == nrow(spatialCoords(spe)))
cat('SpatialExperiment 验证通过\n')
}
spatial 坐标列名不是 'x'/'y',而是 'col'/'row' 或 'X'/'Y',记得手动重命名。我在处理 10X 数据时就遇到过一次,记得统一命名做后续分析。