数据获取与质量评估——打好基础比什么都重要

说实话,空间转录组分析里最容易被低估的环节,就是数据获取和质量评估。我见过不少人一上来就急着跑下游分析,结果花了几周时间,最后发现是数据本身有问题,白白浪费了精力。

2.1 主流数据来源,你得知道去哪找

目前空间转录组数据主要来自两个方向:一个是平台公开的数据,一个是自己跑实验。绝大多数教学和研究用的都是公开数据,常用的有这么几个:

数据来源 平台/格式 特点
10x Visium SPATIAL, H5, 图像 市面上最常见,社区资源丰富
Slide-seq Segmentation, beads 分辨率高,但基因捕获效率略低
ST (Spatial Transcriptomics) 原始测序数据 早期平台,现在用得少了
MERFISH / seqFISH 成像数据 单细胞分辨率,通量有限

我个人习惯首选 10x Visium 数据,因为它的教程和工具链最成熟。你到 10x Genomics 官网的 Datasets 板块,能找到大量示例数据,包括人类乳腺癌、小鼠脑切片这些经典样本。

我的经验是:别去下载最原始的那个 FASTQ 自己跑流程,除非你想练习。直接拿处理好的 SpaceRanger 输出,省时省力。格式一般是 .h5 和 .tif 图像文件夹。

2.2 数据落地——把数据拉下来并看懂结构

假设你找到了一个 Visium 数据集,用 wget 下载后,文件夹结构大概是这样:

# 典型的 SpaceRanger 输出结构
sample345/
├── outs/
│   ├── filtered_feature_bc_matrix.h5
│   ├── filtered_feature_bc_matrix/
│   │   ├── barcodes.tsv.gz
│   │   ├── features.tsv.gz
│   │   └── matrix.mtx.gz
│   ├── spatial/
│   │   ├── tissue_positions_list.csv
│   │   ├── scalefactors_json.json
│   │   └── tissue_hires_image.png
│   └── analysis/
│       ├── clustering/
│       └── ...

这里面最关键的是三个文件:

  • filtered_feature_bc_matrix.h5 —— 基因表达矩阵,相当于单细胞的表达矩阵,但多了 spot 坐标
  • tissue_positions_list.csv —— 每个 spot 的物理坐标 (x, y),组织在不在组织区域 (in_tissue=1/0)
  • scalefactors_json.json —— 图像缩放参数,用来把 spot 坐标映射到组织图像上
避坑: 我曾经下载过一份数据,发现 tissue_positions_list.csv 里全是 in_tissue=0,说白了就是组织区域外的背景点。这种情况直接过滤掉就行,不然下游分析全乱了。

2.3 质量评估——别急着建模

数据一到手,我建议先做三件事:

  1. 看细胞/spot 数量——是不是太少?一般 Visium 一个切片应该有 3000~5000 个 spot
  2. 看基因检出率——中位数基因数太低(比如低于 500),可能组织处理有问题
  3. 看组织图像——有没有气泡、褶皱、坏死区域

我常用 Scanpy 或 Squidpy 做初步质控。举个简单的例子,用 Python 读取 H5 数据:

import scanpy as sc
import pandas as pd

# 读取数据
adata = sc.read_10x_h5('outs/filtered_feature_bc_matrix.h5')
adata.var_names_make_unique()

# 简单质控指标
sc.pp.calculate_qc_metrics(adata, percent_top=None, log1p=False)
print(adata.obs['n_genes_by_counts'].describe())

# 过滤低质量 spot
sc.pp.filter_cells(adata, min_genes=200)
sc.pp.filter_genes(adata, min_cells=3)

print(f"过滤后剩余 {adata.n_obs} 个 spot,{adata.n_vars} 个基因")

结果输出大概长这样:

count    4932.000000
mean      1567.340000
std        413.210000
min        102.000000
25%       1289.000000
50%       1593.000000
75%       1856.000000
max       2873.000000
Name: n_genes_by_counts, dtype: float64
过滤后剩余 4890 个 spot,18543 个基因

这里有个细节:min_genes=200 是单细胞分析的标准阈值,空间转录组可以适当放低到 100。为什么?因为 spot 可能比细胞大,包含多个细胞核碎片,基因检出率天然低一些。

我个人的质控规则:
  • 每个 spot 至少 200 个 UMI(Unique Molecular Identifier)
  • 每个 spot 至少 100 个基因
  • 线粒体基因比例不超过 20%
  • 组织区域外的 spot 直接丢弃

2.4 坐标对齐——让基因和空间位置对上

光有表达矩阵还不够,你得把基因表达数据“贴”到组织图像上。这一步的核心是 tissue_positions_list.csv 这个文件。

我用一个简单的例子说明怎么合并坐标信息:

import pandas as pd
import scanpy as sc

# 读取坐标
coords = pd.read_csv(
    'outs/spatial/tissue_positions_list.csv',
    header=None,
    names=['barcode', 'in_tissue', 'array_row', 'array_col', 'pxl_row_in_fullres', 'pxl_col_in_fullres']
)

# 只保留组织区域的 spot
coords_tissue = coords[coords['in_tissue'] == 1].copy()

# 把坐标加入 adata
adata.obs = adata.obs.join(
    coords_tissue.set_index('barcode')[['pxl_row_in_fullres', 'pxl_col_in_fullres']]
)

# 添加成 spatial 标准格式
adata.obsm['spatial'] = adata.obs[['pxl_col_in_fullres', 'pxl_row_in_fullres']].values

print(f"添加了 {adata.obsm['spatial'].shape[0]} 个 spot 的空间坐标")

2.5 可视化质控——一张图胜过千行代码

我觉得质控中最直观的一步,就是把 UMI 数、基因数用颜色映射到组织图像上。看一眼就知道哪些区域有问题。

import scanpy as sc
import matplotlib.pyplot as plt

# 用 Squidpy 或 Scanpy 绘图
sc.pl.spatial(adata, img_key='hires', 
              color=['n_genes_by_counts', 'total_counts'],
              crop_coord=[1000, 6000, 1000, 6000],
              show=True)

你想象一下,正常组织应该是均匀分布的颜色。如果某个区域突然很暗(基因数极低),那可能是组织折叠、气泡,或者坏死。我以前遇到过一种情况:样本边缘一圈 spot 的基因数只有中心区域的 1/3,后来发现是组织切片时边缘损伤了。

2.6 本章知识结构总览

空间转录组数据获取与质量评估流程 数据来源 了解文件结构 质量评估指标 坐标对齐与合并 可视化质控 输出:Anndata + 空间坐标 核心目标:获得高质量的 spot×gene 矩阵 + 准确组织坐标 排除组织边缘、坏死、气泡等低质量区域

2.7 常见坑和我的解决建议

做多了你就会发现,数据质控这一步往往比下游建模更花时间。我总结几个常见问题:

坑1:组织边缘的 spot 基因数极低
我曾经遇到过边缘 spot 的 UMI 数只有中心的 1/10。解决方案是做一个 total_counts 阈值过滤,比如只保留 UMI 数 > 500 的 spot。
坑2:坐标和图像对不上
有时候下载的数据包 scalefactors_json.json 里的 tissue_hires_scalef 参数不对,导致 spot 画在图像外面。我建议每次都手动检查一下第一个 spot 的像素坐标是否在图像尺寸范围内。
一个小技巧: 如果你的数据是从不同实验室拿到的,先统一列名。有人用 barcode,有人用 cell_id,还有用 spot 的。搞个标准化映射,能省去很多调试时间。

2.8 质控后的标准输出格式

完成所有步骤后,你应该得到一个 AnnData 对象,里面包含:

  • adata.X —— 过滤后的表达矩阵(spot × gene)
  • adata.obs —— spot 级别的元数据(总 UMI、基因数、线粒体比例等)
  • adata.obsm['spatial'] —— 像素坐标 (N×2 数组)
  • adata.uns['spatial'] —— 图像和缩放参数(可选)

保存成标准格式,方便后续直接读取:

# 保存质控后的数据
adata.write('qc_pass.h5ad')

# 下次直接读取
adata = sc.read('qc_pass.h5ad')

print(adata)
# AnnData object with n_obs × n_vars = 4890 × 18543
#     obs: 'n_genes_by_counts', 'total_counts', 'pct_counts_mt'
#     obsm: 'spatial'
#     uns: 'spatial'

看到这个输出,你就可以放心地进入下一步了——构建细胞邻域图,开始特征建模。记住,数据质量决定了分析的天花板,这一步花再长时间都值得。