跳转至

746. 单细胞CNV推断InferCNV

一句话概述:从单细胞RNA-seq数据中推断每个细胞的拷贝数变异(染色体上某些区域的基因多了或少了)——就像从"表达量的异常"反推"DNA数量的异常",用来区分肿瘤细胞和正常细胞。


核心知识点速查表

概念白话解释关键工具
CNV拷贝数变异,DNA某段区域多拷贝或缺失InferCNV, CopyKAT
扩增(Gain)某段染色体多了 → 上面的基因表达升高红色区域
缺失(Loss)某段染色体少了 → 上面的基因表达降低蓝色区域
参考细胞已知的正常细胞,作为基线对比必须提供
移动平均对染色体上连续基因的表达取滑动窗口平均消除噪声
CopyKAT替代工具,自动区分肿瘤/正常R包

一、原理(白话版)

1.1 核心思路

正常的CNV检测:用DNA测序 → 直接看覆盖度 → 覆盖度高=扩增,低=缺失
InferCNV的思路:没有DNA数据 → 用RNA表达间接推断

逻辑链:
  如果染色体7上的一大段区域的基因表达都偏高
  → 不太可能是所有基因碰巧都上调
  → 更可能是这段DNA本身多了一份(扩增)
  → 推断:这个细胞在chr7上有CNV gain

关键前提:
  需要"正常细胞"作为参考(baseline)
  异常 = 目标细胞 - 正常细胞的平均值

1.2 InferCNV的工作流程

1. 将基因按染色体位置排序
2. 对每个细胞,计算每个基因相对于参考细胞的表达偏差
3. 沿染色体滑动窗口平均(消除单基因噪声)
4. 用HMM或BayesNet进一步降噪
5. 输出CNV热图和分类结果

二、InferCNV分析流程

2.1 安装

# 安装InferCNV
if (!requireNamespace("BiocManager", quietly=TRUE))
    install.packages("BiocManager")
BiocManager::install("infercnv")  # 安装InferCNV

# 验证
library(infercnv)
packageVersion("infercnv")

2.2 准备输入文件

# ===== InferCNV需要三个输入文件 =====

# 1. 表达矩阵(原始counts,不是归一化后的)
# 从Seurat提取:
library(Seurat)
seurat_obj <- readRDS("seurat.rds")
counts <- GetAssayData(seurat_obj, layer="counts")  # 原始计数矩阵
write.table(as.matrix(counts), "counts_matrix.tsv", 
            sep="\t", quote=FALSE)  # 保存为TSV

# 2. 细胞注释文件(每个细胞属于什么类型)
# 格式:细胞barcode\t分类
annotations <- data.frame(
  row.names = colnames(seurat_obj),  # 细胞barcode
  cell_type = seurat_obj$cell_type   # 细胞类型
)
write.table(annotations, "cell_annotations.tsv",
            sep="\t", col.names=FALSE, quote=FALSE)

# 3. 基因坐标文件(每个基因在染色体上的位置)
# 格式:基因名\t染色体\t起始位置\t结束位置
# InferCNV提供了预制文件,也可以从GTF生成
# 下载预制文件:
# https://data.broadinstitute.org/Trinity/CTAT/cnv/
# 从GTF文件生成基因坐标文件
# 提取基因坐标(简化版)
grep -P "^chr" genes.gtf | \
  awk '$3=="gene"' | \
  grep 'gene_name' | \
  sed 's/.*gene_name "\([^"]*\)".*/\1/' | \
  paste - <(grep -P "^chr" genes.gtf | awk '$3=="gene" {print $1, $4, $5}') | \
  sort -k2,2V -k3,3n > gene_ordering_file.tsv

2.3 运行InferCNV

# ===== 创建InferCNV对象并运行 =====
library(infercnv)

# 创建InferCNV对象
infercnv_obj <- CreateInfercnvObject(
  raw_counts_matrix = "counts_matrix.tsv",  # 原始计数矩阵
  annotations_file = "cell_annotations.tsv",  # 细胞注释
  gene_order_file = "gene_ordering_file.tsv",  # 基因坐标
  ref_group_names = c("Normal_Epithelial", "Fibroblast", "Endothelial")
  # 参考细胞类型(已知的正常细胞!)
)

# 运行InferCNV分析
infercnv_obj <- infercnv::run(
  infercnv_obj,
  cutoff = 0.1,  # 基因过滤:至少在cutoff比例的参考细胞中表达
  out_dir = "infercnv_output/",  # 输出目录
  cluster_by_groups = TRUE,  # 按注释分组聚类
  denoise = TRUE,  # 去噪(推荐开启)
  HMM = TRUE,  # 使用隐马尔可夫模型推断CNV状态
  num_threads = 8,  # 线程数
  no_prelim_plot = FALSE,  # 生成中间结果图
  analysis_mode = "subclusters"  # 分析模式:subclusters更敏感
)

# 输出文件
# infercnv_output/
# ├── infercnv.png              # CNV热图(核心结果!)
# ├── infercnv.observations.txt # 观测细胞的CNV值
# ├── infercnv.references.txt   # 参考细胞的CNV值
# ├── HMM_CNV_predictions.*.txt # HMM预测的CNV区域
# └── run.final.infercnv_obj    # R对象(可继续分析)

2.4 结果解读与可视化

# ===== 结果解读 =====

# 读取HMM预测结果
hmm_results <- read.table(
  "infercnv_output/HMM_CNV_predictions.HMMi6.leiden.hmm_mode-subclusters.Pnorm_0.5.repr_intensities.observations.txt",
  header=TRUE, sep="\t"
)

# CNV状态编码:
# 0.5 = 纯合缺失 (complete loss)
# 1   = 杂合缺失 (loss of one copy)
# 1.5 = 正常 (neutral)
# 2   = 单倍增 (gain of one copy)
# 3   = 扩增 (amplification)

# 统计每个细胞的CNV负荷
cnv_score <- apply(hmm_results, 1, function(x) {
  sum(abs(x - 1.5))  # 偏离正常值(1.5)的总量
})

# 将CNV score添加回Seurat对象
seurat_obj$cnv_score <- cnv_score[match(
  colnames(seurat_obj), names(cnv_score)
)]

# 绘制CNV score在UMAP上的分布
library(ggplot2)
FeaturePlot(seurat_obj, features="cnv_score", 
            cols=c("gray90", "red")) +
  ggtitle("CNV Burden Score")
ggsave("cnv_score_umap.png", width=8, height=6, dpi=300)

# 区分肿瘤vs正常细胞(基于CNV score阈值)
seurat_obj$is_tumor <- ifelse(
  seurat_obj$cnv_score > quantile(cnv_score, 0.75, na.rm=TRUE),
  "Tumor", "Normal"
)

三、替代工具:CopyKAT

# ===== CopyKAT:自动区分肿瘤/正常 =====
# CopyKAT的优势:不需要预先指定参考细胞

# 安装
devtools::install_github("navinlabcode/copykat")

library(copykat)

# 运行CopyKAT
copykat_result <- copykat(
  rawmat = as.matrix(counts),  # 原始计数矩阵
  id.type = "S",  # S=symbol, E=ensemble
  ngene.chr = 5,  # 每条染色体至少5个基因
  win.size = 25,  # 滑动窗口大小
  KS.cut = 0.1,  # KS检验阈值
  sam.name = "sample1",  # 样本名
  n.cores = 8  # CPU数
)

# 结果
head(copykat_result$prediction)
# cell.names  copykat.pred
# AAACCTGA    aneuploid     ← 肿瘤(异倍体)
# AAACCTGC    diploid       ← 正常(二倍体)
# AAACCTGG    not.defined   ← 不确定

# 将预测结果添加到Seurat
seurat_obj$copykat_pred <- copykat_result$prediction$copykat.pred[
  match(colnames(seurat_obj), copykat_result$prediction$cell.names)
]

四、常见报错与解决

报错信息原因解决方案
Gene ordering file error基因名不匹配确保基因名格式一致(HUGO symbols)
No reference cells参考组名字拼写错误检查ref_group_names与注释文件的一致性
HMM: convergence error数据太稀疏降低cutoff值或增加min.cells
Memory error细胞数太多下采样到1万细胞以内
CopyKAT: all diploidCNV太轻微检测不到降低KS.cut阈值或用InferCNV的HMM模式
Empty chromosome该染色体上基因太少过滤掉基因数<5的染色体

五、面试高频问题

Q1: InferCNV推断CNV的原理?

A: 将基因按染色体位置排列,计算目标细胞相对于正常参考细胞的表达偏差。如果染色体某个区域的基因系统性偏高/偏低,推断该区域发生了CNV扩增/缺失。使用滑动窗口平均和HMM去噪。

Q2: 参考细胞的选择很重要吗?

A: 非常重要。参考细胞应该是已知的正常细胞(如成纤维细胞、内皮细胞)。如果参考细胞本身有CNV,会导致假阴性。如果参考细胞选错(混入肿瘤细胞),结果会严重偏差。

Q3: InferCNV和基于DNA的CNV检测比较?

A: InferCNV从RNA间接推断,分辨率低于DNA方法(只能看到大片段CNV,>5Mb),假阳性率更高。但优势是不需要额外的DNA测序,可以在单细胞水平区分肿瘤亚群。


六、速查表

# ===== InferCNV速查 =====

# 安装
BiocManager::install("infercnv")

# 三个输入文件
# 1. counts_matrix.tsv(原始counts)
# 2. cell_annotations.tsv(细胞类型)
# 3. gene_ordering_file.tsv(基因染色体坐标)

# 运行
obj <- CreateInfercnvObject(raw_counts, annotations, gene_order,
                            ref_group_names=c("Normal"))
obj <- infercnv::run(obj, cutoff=0.1, out_dir="output/",
                     denoise=TRUE, HMM=TRUE, num_threads=8)

# CNV状态
# 0.5=纯合缺失  1=杂合缺失  1.5=正常  2=单倍增  3=扩增

# CopyKAT(无需参考细胞)
# devtools::install_github("navinlabcode/copykat")
# result <- copykat(rawmat, id.type="S", n.cores=8)
# result$prediction → aneuploid/diploid