首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【单细胞整合避坑指南】数据集不平衡如何毁掉你的分析?Nature Biotech 方法实战教程

【单细胞整合避坑指南】数据集不平衡如何毁掉你的分析?Nature Biotech 方法实战教程

原创
作者头像
用户11125847
发布2026-09-02 15:58:43
发布2026-09-02 15:58:43
20
举报

适用场景:当你需要将多批次/多样本的 scRNA-seq 数据做整合(Integration),但发现各样本间细胞类型组成差异很大(比如肿瘤 vs 正常、不同发育阶段、不同患者间细胞比例悬殊)。 本文目标:教你把 Maan et al., Nature Biotechnology 2024 的核心方法落地到你的分析流程中,避免"看上去整合得很好,实际上稀有细胞类型已经被吞并了"的悲剧。


一、前言:为什么你的整合结果不可信?

单细胞数据整合(Integration)已经成为标配操作。但大多数教程都在教你:

代码语言:python
复制
scvi.model.SCVI.setup_anndata(adata, batch_key="batch")
model = scvi.model.SCVI(adata)
model.train()

这里有一个巨大的盲区:这些方法默认假设不同批次间"共享且均衡"地含有各类细胞。真实数据往往并非如此:

  • 患者 A 的肿瘤样本里上皮细胞占 70%,患者 B 的活检样本里只有 5%
  • 正常组织里有大量免疫细胞,但肿瘤微环境中某些免疫细胞亚群极度稀少
  • 发育数据中,Day 0 的前体细胞很多,Day 7 的终末分化细胞在早批次里根本没有

后果:整合时为了"消除批次效应",算法会强行把转录组相似但批次来源不同的细胞拉到一起。当某类细胞在一个批次中极度稀少时,它会被"吸收"进另一个批次中数量占优的相似细胞群里。你的 UMAP 图看起来批次混合得很漂亮,但生物学信号已经丢了。

这篇 Nature Biotech 文章通过 2,600 次整合实验证明:数据集不平衡的影响是细胞类型特异性的,而且在所有主流方法(Seurat/Harmony/scVI/BBKNN/Scanorama)中都存在。


二、核心概念:两个决定整合成败的指标

文章提出了两个关键属性,用来预判你的数据在整合后会不会出问题:

指标

含义

通俗解释

Aggregate cell type support

某细胞类型在所有批次中的总细胞数(取 log₂)

这类细胞够不够多?越少越容易被"淹没"

Minimum cell type center distance

在 PCA 空间中,该类细胞与其最相似邻居的距离

这类细胞够不够"独特"?越像邻居,越容易被"吞并"

致命组合:某细胞类型数量少(低 support)与邻居转录组相似(低 center distance) → 整合后几乎100%会被错误合并或丢失。


三、实战准备:环境准备

代码语言:bash
复制
# 基础环境
pip install scanpy scvi-tools harmony-pytorch bbknn

# 安装文章作者开发的平衡聚类指标库
pip install balanced-clustering
代码语言:Python
复制
import scanpy as sc
import pandas as pd
import numpy as np
from scipy.spatial.distance import cosine
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import f1_score
from balanced_clustering import balanced_adjusted_rand_index, balanced_adjusted_mutual_info

四、Step 1: 整合前诊断——你的数据有多"不平衡"?

原则:整合前必须先做诊断,不要拿到数据就直接跑IntegrateData

4.1 检查批次间细胞类型比例

代码语言:Python
复制
# 假设 adata.obs 中有 'batch' 和 'cell_type' 两列
for batch in sorted(adata.obs['batch'].unique()):
    print(f"\n=== {batch} ===")
    props = adata[adata.obs['batch']==batch].obs['cell_type'].value_counts(normalize=True)
    print(props.map(lambda x: f"{x:.1%}"))

危险信号

  • 某细胞类型在批次 A 中占 30%,在批次 B 中占 < 2%
  • 某细胞类型只出现在部分批次中(非共享细胞类型)

4.2 计算"最小细胞类型中心距离"

这是文章的核心诊断工具,用来判断"谁和谁容易混淆"。

代码语言:Python
复制
def compute_cell_type_distances(adata, cell_type_key='cell_type', n_pcs=20):
    """计算各细胞类型在PCA空间中的中心距离"""
    sc.tl.pca(adata, n_comps=n_pcs, use_highly_variable=True)
    
    centers = {}
    for ct in adata.obs[cell_type_key].unique():
        mask = adata.obs[cell_type_key] == ct
        centers[ct] = adata.obsm['X_pca'][mask].mean(axis=0)
    
    # 计算每个细胞类型的最近邻
    results = []
    for ct1 in centers:
        min_dist = 1.0
        nearest = ""
        for ct2 in centers:
            if ct1 != ct2:
                # 使用余弦距离,与文章一致
                d = cosine(centers[ct1], centers[ct2])
                if d < min_dist:
                    min_dist = d
                    nearest = ct2
        results.append({
            'cell_type': ct1,
            'nearest_neighbor': nearest,
            'min_center_distance': min_dist,
            'total_cells': np.sum(adata.obs[cell_type_key] == ct1)
        })
    
    return pd.DataFrame(results).sort_values('min_center_distance')

# 运行诊断
diagnosis_df = compute_cell_type_distances(adata)
print(diagnosis_df)

解读输出:

代码语言:txt
复制
      cell_type nearest_neighbor  min_center_distance  total_cells
0        pDC           cDC2               0.12            45
1    CD8_TCM        CD8_TEM               0.18           320
2       cDC2           pDC               0.12          2800
  • pDC 与 cDC2 距离仅 0.12,且 pDC 只有 45 个细胞 → 高危!整合后 pDC 很可能被合并进 cDC2
  • CD8_TCM 与 CD8_TEM 距离 0.18,虽然不算极度相似,但如果某批次中 TCM 极少,也会被合并 行动:把 min_center_distance < 0.3 且 total_cells 偏少的细胞类型标记为重点类型

五、Step 2: 整合策略选择——对症下药的三种方案

根据不平衡的严重程度,选择不同策略:

方案 A:轻度不平衡(比例差异 < 10 倍,无非共享类型)

直接用标准流程,但降低校正强度,避免过度抹平生物学信号:

代码语言:Python
复制
# Seurat (R语言示例,Python 可用 seurat-via-scanpy 或 rpy2)
# 减少锚点数量,防止强行拉平
FindIntegrationAnchors(..., k.anchor = 5)

# Harmony
adata = sc.external.pp.harmony_integrate(adata, key='batch', theta=1)  # theta 默认2,降低则更保守

# scVI
SCVI.setup_anndata(adata, batch_key="batch")
model = SCVI(adata, n_latent=20)  # 增加 latent dim,保留更多信号

方案 B:中度不平衡(比例差异 10-100 倍,或少量非共享类型)

推荐"分群整合"——按大类分开处理,避免大类淹没小类:

代码语言:Python
复制
# 示例:将免疫细胞和上皮细胞分开整合
# 先对每个批次做粗分群(或利用已知标记),分成 major compartments
for compartment in ['immune', 'epithelial', 'stromal']:
    sub = adata[adata.obs['compartment'] == compartment].copy()
    # 对每个 compartment 单独整合
    scvi.model.SCVI.setup_anndata(sub, batch_key='batch')
    model = scvi.model.SCVI(sub)
    model.train()
    sub.obsm['X_integrated'] = model.get_latent_representation()
    # 保存结果,最后合并

方法选择:文章发现 Harmony 和 BBKNN 在不平衡场景下的鲁棒性相对较好。如果数据不平衡但你需要保留稀有细胞信号,优先尝试 Harmony。

方案 C:重度不平衡(比例差异 > 100 倍,或大量非共享类型)

禁止一次性整合所有批次!改用以下两种策略之一:

策略 1:参考映射(Query-to-Reference)

把细胞数最多、质量最好的批次作为 Reference,其余作为 Query 逐个映射。

代码语言:txt
复制
# Seurat v4/v5 的 MapQuery 思路
# reference: 大样本/对照样本
# query1, query2, query3: 其他样本
# 分别将 query 映射到 reference,而不是全部混在一起做整合

策略 2:顺序整合(Sequential Integration)

如果有时间序列(如 Day0 → Day3 → Day7),按时间顺序逐步合并,而非一次性整合:

代码语言:txt
复制
# Day0+Day3 先整合,结果再与 Day7 整合
integrated_d0_d3 = integrate(day0, day3)
final = integrate(integrated_d0_d3, day7)

六、Step 3: 整合后评估——扔掉 ARI,用上 bARI!

传统指标为什么骗人?

ARI(Adjusted Rand Index)会被大细胞类型主导。假设你的数据里有 5000 个 CD4 T 细胞和 50 个 pDC,即使整合后所有 pDC 都被错误合并进 cDC2,ARI 仍然可能 > 0.85,因为 CD4 T 细胞整合得"太好了"。

6.1 使用平衡指标

代码语言:Python
复制
# 假设你已经有了整合后的聚类标签 'leiden' 和真实标签 'cell_type'
bARI = balanced_adjusted_rand_index(adata.obs['cell_type'], adata.obs['leiden'])
bAMI = balanced_adjusted_mutual_info(adata.obs['cell_type'], adata.obs['leiden'])

print(f"Balanced ARI: {bARI:.3f}")
print(f"Balanced AMI: {bAMI:.3f}")

关键对比:同时计算普通 ARI 和 bARI:

代码语言:Python
复制
from sklearn.metrics import adjusted_rand_score
ARI = adjusted_rand_score(adata.obs['cell_type'], adata.obs['leiden'])
print(f"ARI: {ARI:.3f} vs bARI: {bARI:.3f}")
  • 如果 ARI > 0.85 但 bARI < 0.6 → ⚠警告! 稀有细胞类型已经被吞并,你的整合结果不可信。

6.2 细胞类型特异性 KNN 分类(文章核心评估方法)

不要只看总体准确率,要逐个细胞类型看 F1 分数:

代码语言:Python
复制
def evaluate_integration_per_cell_type(adata, embedding_key='X_pca', label_key='cell_type'):
    """在整合后的嵌入空间上评估每个细胞类型的分类性能"""
    X = adata.obsm[embedding_key]
    y = adata.obs[label_key]
    
    # 分层抽样,确保训练/测试集中各细胞类型比例一致
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, test_size=0.3, stratify=y, random_state=42
    )
    
    knn = KNeighborsClassifier(n_neighbors=15)
    knn.fit(X_train, y_train)
    y_pred = knn.predict(X_test)
    
    # 逐个细胞类型输出 F1
    results = []
    for ct in sorted(y.unique()):
        f1 = f1_score(y_test, y_pred, labels=[ct], average=None, zero_division=0)[0]
        results.append({'cell_type': ct, 'F1_score': f1})
    
    return pd.DataFrame(results).sort_values('F1_score')

# 对整合后的数据运行(如 X_scVI, X_harmony 等)
results_df = evaluate_integration_per_cell_type(adata, embedding_key='X_scVI')
print(results_df)

危险信号:

  • 如果某细胞类型 F1 < 0.7,而大多数细胞类型 F1 > 0.9 → 该类细胞在整合空间中已经"丢失身份"
  • 特别关注你在 Step 1 中标记的重点类型

七、Step 4: 下游分析避坑指南

7.1 无监督聚类:不要迷信默认 resolution

代码语言:Python
复制
# 错误做法:直接 resolution=1
# sc.tl.leiden(adata, resolution=1)

# 正确做法:先用最平衡的批次/对照样本确定最优 resolution
# 使得聚类数 ≈ 真实细胞类型数
for res in [0.2, 0.5, 1.0, 1.5]:
    sc.tl.leiden(adata, resolution=res)
    n_clusters = adata.obs['leiden'].nunique()
    print(f"resolution={res}: {n_clusters} clusters")

检查点:如果整合后聚类数突然比对照实验少了好几个,说明有细胞类型被"吞并"了。

7.2 差异表达与 Marker 基因

不平衡数据做 DGE 的大坑:稀有细胞类型的信号会被批次特异性噪音淹没。

可以这样做:

代码语言:Python
复制
# 不要直接在整个整合后的数据上跑 rank_genes_groups 找全局 marker
# 而是按批次分别找 marker,然后取交集,过滤掉批次特异性基因

markers_batch1 = find_markers(adata[adata.obs['batch']=='batch1'])
markers_batch2 = find_markers(adata[adata.obs['batch']=='batch2'])
consensus_markers = intersect(markers_batch1, markers_batch2)

7.3 轨迹推断

如果你的轨迹包含稀有中间态(如发育数据):

代码语言:Python
复制
# 检查关键中间态在各批次中的数量
for batch in adata.obs['batch'].unique():
    print(f"\n{batch}:")
    print(adata[adata.obs['batch']==batch].obs['cell_type'].value_counts())
  • 如果某关键中间态在部分批次中缺失,不要强行整合后做轨迹。
  • 建议:分批次各自推断轨迹,再比较拓扑结构是否一致。

八、快速自查表(Checklist)

复制到你的实验记录里,每次整合前打勾:

检查项目

状态

如果为"是"的行动

是否存在只在某批次出现的细胞类型?

考虑分群整合或参考映射

是否有细胞类型比例差异 > 10 倍?

降低整合强度,使用 Harmony/BBKNN

是否使用了默认参数直接整合?

根据不平衡程度调参

bARI 是否显著低于 ARI (>0.2 差距)?

稀有细胞类型整合失败,需重新调整

是否有细胞类型的 KNN-F1 < 0.8?

检查是否被合并到相似大群

标记基因排名在不同批次间是否一致?

分别做 DGE 后取交集


九、参考资源

  1. Iniquitate 流程源码https://github.com/hsmaan/Iniquitate
  2. 平衡聚类指标库https://github.com/hsmaan/balanced-clustering
  3. 原文:Maan et al., Characterizing the impacts of dataset imbalance on single-cell data integration, Nature Biotechnology, 2024.

十、总结

整合后的 UMAP 批次混合得再漂亮,如果 bARI 很低、稀有细胞的 F1 很差,你的生物学结论也可能也是有问题的。先诊断不平衡,再选择策略,最后用平衡指标验收,是单细胞整合分析的"保命三连"。


本次的分享就到这里啦,欢迎在楼下交流你在整合中遇到的不平衡问题,或者贴出你的诊断结果一起讨论!; )

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 一、前言:为什么你的整合结果不可信?
  • 二、核心概念:两个决定整合成败的指标
  • 三、实战准备:环境准备
  • 四、Step 1: 整合前诊断——你的数据有多"不平衡"?
    • 4.1 检查批次间细胞类型比例
    • 4.2 计算"最小细胞类型中心距离"
  • 五、Step 2: 整合策略选择——对症下药的三种方案
    • 方案 A:轻度不平衡(比例差异 < 10 倍,无非共享类型)
    • 方案 B:中度不平衡(比例差异 10-100 倍,或少量非共享类型)
    • 方案 C:重度不平衡(比例差异 > 100 倍,或大量非共享类型)
  • 六、Step 3: 整合后评估——扔掉 ARI,用上 bARI!
    • 6.1 使用平衡指标
    • 6.2 细胞类型特异性 KNN 分类(文章核心评估方法)
  • 七、Step 4: 下游分析避坑指南
    • 7.1 无监督聚类:不要迷信默认 resolution
    • 7.2 差异表达与 Marker 基因
    • 7.3 轨迹推断
  • 八、快速自查表(Checklist)
  • 九、参考资源
  • 十、总结
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档