跳转至

381_单细胞数据伪批量分析


一句话说明

伪批量(Pseudo-bulk)就是把同一类型细胞的reads合并成"一个样本",再用传统RNA-seq分析方法做差异分析——这样统计更可靠。


核心知识点

要点1:为什么要做伪批量

  • 直接在单细胞层面做差异分析(如 FindMarkers)会把"细胞内重复"当成独立样本,严重高估统计显著性(p值偏小)
  • 正确的生物学重复应该是"不同供体/样本",而不是"同一供体的不同细胞"
  • 伪批量通过合并同类型细胞的reads,恢复到样本级别的统计单元

要点2:为什么"单细胞假重复"是个问题

  • 例:1名患者提供10000个T细胞 ≠ 10000个独立生物学重复
  • 这些细胞来自同一个人,共享遗传背景,是高度相关的伪重复(pseudoreplication)
  • 正确做法:把这10000个T细胞合并成1个样本,多个患者才是真正的重复

要点3:伪批量分析流程

原始单细胞数据(细胞×基因)
    ↓ 按样本 + 细胞类型分组合并
伪批量矩阵(每个"样本-细胞类型"组合是一行)
    ↓ DESeq2 / edgeR 差异分析
差异基因结果(基于样本级别统计)

要点4:适用场景

  • 多样本/多患者的单细胞研究(必须用)
  • 寻找疾病 vs 健康的差异基因(临床相关分析)
  • 时间点比较(治疗前后)

要点5:局限性

  • 每种细胞类型至少需要3个独立样本,否则无统计效力
  • 细胞数太少(<20个)的伪批量样本建议过滤掉
  • 不适合单样本研究(单样本没有重复)

实战代码

伪批量分析(R,DESeq2)

library(Seurat)      # 单细胞分析
library(DESeq2)      # 差异分析(伪批量最佳搭配)
library(dplyr)
library(ggplot2)

# 假设 seurat_obj 已有:
# - cell_type: 细胞类型注释
# - sample_id: 每个细胞来自哪个供体/样本
# - condition: 疾病状态 (Disease/Control)

# ============================================================
# 步骤1:按"样本 + 细胞类型"分组,对reads计数求和
# ============================================================
# 创建"样本-细胞类型"组合的标识
seurat_obj$sample_celltype <- paste(
  seurat_obj$sample_id,    # 样本ID(如 P001, P002...)
  seurat_obj$cell_type,    # 细胞类型(如 T_cell, B_cell...)
  sep = "_"
)

# 聚合reads:对每个"样本-细胞类型"组合的所有细胞求和
# 使用 AggregateExpression(Seurat v5专用函数)
pseudo_bulk <- AggregateExpression(
  seurat_obj,
  group.by = c("sample_id", "cell_type"),   # 按样本+细胞类型分组
  assays = "RNA",
  slot = "counts",                            # 使用raw counts(DESeq2要求)
  return.seurat = FALSE                       # 返回矩阵而非Seurat对象
)
pb_counts <- pseudo_bulk$RNA   # 取RNA层

# 查看结果维度
cat("伪批量矩阵维度:", dim(pb_counts), "\n")
# 格式:行=基因,列="sample_id_cell_type"

# ============================================================
# 步骤2:只分析某一细胞类型(以T细胞为例)
# ============================================================
target_celltype <- "T_cell"
tcell_cols <- grepl(paste0("_", target_celltype, "$"), colnames(pb_counts))
tcell_pb <- pb_counts[, tcell_cols]    # 只取T细胞列

# 构建样本元数据
tcell_meta <- data.frame(
  col_name = colnames(tcell_pb)
) %>%
  mutate(
    sample_id = sub(paste0("_", target_celltype), "", col_name),  # 提取样本ID
  ) %>%
  left_join(
    # 从Seurat对象提取样本级元数据(每个样本一行)
    seurat_obj@meta.data %>%
      group_by(sample_id, condition) %>%
      summarise(.groups = "drop"),
    by = "sample_id"
  )
rownames(tcell_meta) <- tcell_meta$col_name

# 检查:过滤掉T细胞数量太少的伪批量样本
cell_counts <- table(seurat_obj$sample_id[seurat_obj$cell_type == target_celltype])
keep_samples <- names(cell_counts)[cell_counts >= 20]   # 至少20个T细胞
tcell_pb <- tcell_pb[, rownames(tcell_meta)[tcell_meta$sample_id %in% keep_samples]]
tcell_meta <- tcell_meta[colnames(tcell_pb), ]

cat("保留样本数:", ncol(tcell_pb), "\n")

# ============================================================
# 步骤3:DESeq2 差异分析
# ============================================================
dds <- DESeqDataSetFromMatrix(
  countData = tcell_pb,           # 整数count矩阵(基因×伪批量样本)
  colData   = tcell_meta,         # 样本元数据
  design    = ~ condition         # 统计模型:按疾病状态比较
)

# 过滤低表达基因(加速分析,减少多重检验负担)
keep_genes <- rowSums(counts(dds) >= 10) >= 3   # 至少3个样本中有>=10个reads
dds <- dds[keep_genes, ]

# 运行 DESeq2
dds <- DESeq(dds)

# 提取结果(Disease vs Control)
res <- results(
  dds,
  contrast = c("condition", "Disease", "Control"),
  alpha = 0.05       # FDR 阈值
)
res <- as.data.frame(res)
res <- res[order(res$pvalue), ]    # 按 p 值排序

# 显著差异基因
sig_genes <- res[!is.na(res$padj) & res$padj < 0.05 & abs(res$log2FoldChange) > 1, ]
cat("显著差异基因数:", nrow(sig_genes), "\n")

# ============================================================
# 步骤4:可视化
# ============================================================
# 火山图
res$status <- "NS"
res$status[res$padj < 0.05 & res$log2FoldChange > 1] <- "Up"
res$status[res$padj < 0.05 & res$log2FoldChange < -1] <- "Down"

ggplot(res[!is.na(res$padj), ], aes(x = log2FoldChange, y = -log10(padj),
                                      color = status)) +
  geom_point(alpha = 0.6, size = 1) +
  scale_color_manual(values = c("Up" = "red", "Down" = "blue", "NS" = "gray")) +
  geom_hline(yintercept = -log10(0.05), linetype = "dashed") +
  geom_vline(xintercept = c(-1, 1), linetype = "dashed") +
  labs(title = paste("T细胞伪批量分析:Disease vs Control"),
       x = "Log2 Fold Change", y = "-Log10 adj.p") +
  theme_classic()

面试常问点

  1. 为什么Seurat的FindMarkers用的不是伪批量? FindMarkers把细胞当独立重复做Wilcoxon/t检验,在多样本研究中会假阳性增多;伪批量是更统计正确的方法
  2. 至少需要几个样本才能做伪批量? DESeq2建议每组至少3个,最好6+个(样本越多,统计效力越高)
  3. 伪批量和MAST、edgeR single-cell模式的区别? MAST等专门的scRNA-seq差异工具尝试在细胞层面纳入样本相关性(如随机效应),但计算更复杂;伪批量更简单且有充分的理论支持

速查表

分析目的推荐方法原因
多样本疾病比较伪批量 + DESeq2统计正确,控制假阳性
单样本探索FindMarkers(Wilcoxon)无样本重复时的替代
快速marker找寻FindMarkers速度快,适合探索
严格发表分析伪批量统计规范性要求