InferCNV:从肿瘤单细胞RNA-Seq数据中推断拷贝数变化

github网址:https://github.com/broadinstitute/infercnv
wiki说明文档:https://github.com/broadinstitute/inferCNV/wiki


一、InferCNV简介

拷贝数变异(Copy number variation, CNV):由基因组发生重排而导致的,一般指长度1KB以上的基因组大片段的拷贝数增加或者减少, 主要表现为亚显微水平的缺失和重复。CNV是基因组结构变异(Structural variation, SV) 的重要组成部分。

拷贝数改变(copy number alterations, CNA):体细胞染色体结构的变化,导致 DNA 片段拷贝的增加或丢失,并且在许多类型的癌症中普遍存在。CNV和CNA的定义是等价的,只是叫法不同。

在基因组变异中,拷贝数变异 (CNV) 是癌症的重要遗传驱动因素 。 CNV 是基因组事件,其中特定基因的拷贝数因个体而异,甚至因细胞而异。肿瘤细胞与导致基因扩增和基因缺失的体细胞CNV变化有关。然而,由于缺乏单细胞全基因组测序,很难在单细胞中检测和量化CNV事件。相比之下,单细胞RNA测序(scRNA-seq)技术的快速发展能够获得单细胞整个基因表达谱的数据,在单细胞中确定 CNV 非常具有挑战性。由于基因表达的不均匀覆盖和动态变化,推断CNV的方法面临困难。但是,现在也已经开发出一些算法针对scRNA转录组数据推断CNV事件,其中,inferCNV是一个常用的从肿瘤单细胞RNA-Seq数据推断拷贝数变化分析工具。


InferCNV用于探索肿瘤单细胞RNA-Seq数据,以确定体细胞是否存在大规模染色体拷贝数变异(Copy number variation,CNV),例如整个染色体或大段染色体的增加或者减少(gains or deletions)。InferCNV实现的方法是,通过用一组“正常”细胞做参照,探索肿瘤基因组不同位置基因的表达强度来完成的。InferCNV生成的热图显示每条染色体上基因的相对表达强度,与正常细胞相比,能显而易见的看到肿瘤基因组的哪些区域过多或过少表达。

InferCNV 提供了对多个残差表达式过滤器的访问,以探索最小化噪声并进一步揭示支持CNA的信号。此外,inferCNV 包括预测CNA区域和根据异质性模式定义细胞簇的方法。

InferCNV 是TrinityCTAT工具包的一个组成部分,该工具包专注于利用RNA-Seq更好地了解癌症转录组。要了解有关 Trinity CTAT 的更多信息,请访问 TrinityCTAT。

二、inferCNV安装

2.1 基于容器镜像的安装

使用docker镜像
拉出docker镜像,在当前工作路径启动镜像

sudo docker pull trinityctat/infercnv:latest
docker run --rm -it -v `pwd`:`pwd` trinityctat/infercnv:latest bash

使用Singularity奇点
从docker repo 生成 Singularity镜像,绑定当前工作目录路径,启动镜像

sudo singularity build infercnv.latest.simg docker://trinityctat/infercnv:latest
singularity shell --writable-tmpfs -e -B `pwd` infercnv.latest.simg
2.2 本地安装

安装依赖软件:JAGS
安装infercnv软件之前,需要先安装JAGS(Just Another Gibbs Sampler) 。

# 手动安装
sudo yum install blas-devel lapack-devel
wget https://sourceforge.net/projects/mcmc-jags/files/JAGS/4.x/Source/JAGS-4.3.0.tar.gz
tar xf JAGS-4.3.0.tar.gz  
cd JAGS-4.3.0 
./configure --libdir=/usr/local/lib64 
make -j 20 && make install

JAGS也可通过conda安装(推荐)

conda install -c conda-forge jags r-rjags

通过BioConductor 安装 infercnv

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

library(infercnv)
packageVersion("infercnv")
# [1] ‘1.10.1’

三、快速入门

3.1 运行示例数据

如果通过BioConductor安装了infercnv,可以使用自带的示例数据:

library(infercnv)
infercnv_obj = CreateInfercnvObject(raw_counts_matrix=system.file("extdata", "oligodendroglioma_expression_downsampled.counts.matrix.gz", package = "infercnv"),
                                    annotations_file=system.file("extdata", "oligodendroglioma_annotations_downsampled.txt", package = "infercnv"),
                                    delim="\t",
                                    gene_order_file=system.file("extdata", "gencode_downsampled.EXAMPLE_ONLY_DONT_REUSE.txt", package = "infercnv"),
                                    ref_group_names=c("Microglia/Macrophage","Oligodendrocytes (non-malignant)")) 
infercnv_obj = infercnv::run(infercnv_obj,
                             cutoff=1, # cutoff=1 works well for Smart-seq2, and cutoff=0.1 works well for 10x Genomics
                             out_dir=tempfile(), 
                             cluster_by_groups=TRUE, 
                             denoise=TRUE,
                             HMM=TRUE)

运行时间较长,如在服务器上运行,可设置num_threads参数(如num_threads=40)。
查看运行后的结果目录:


3.2 软件要求
  • JAGS
  • R (支持R version 3.6.0 以上版本)
  • 依赖的R包: graphics, grDevices, RColorBrewer, gplots, futile.logger, stats, utils, methods, ape, Matrix, fastcluster, dplyr, HiddenMarkov, ggplot2, edgeR, coin, caTools, digest, reshape, rjags, fitdistrplus, future, foreach, doParallel, BiocGenerics, SummarizedExperiment, SingleCellExperiment, tidyr, parallel, coda, gridExtra, argparse
  • Python 3 (可选,运行python脚本生成infercnv输入文件)
3.3 输入数据要求

inferCNV的输入文件:

  • 单细胞RNA-Seq的原始基因计数矩阵;
  • 注释文件,指示哪些细胞是肿瘤细胞,哪些是正常细胞;
  • 基因/染色体位置信息;

四、inferCNV输入

输入文件的包含3个文件:raw_counts_matrix,annotations_file,gene_order_file

4.1 基因 x 细胞的原始计数矩阵

InferCNV软件对smart-seq2和10x平台的单细胞转录组数据都兼容,并且可能与其他平台同样兼容(未测试)。计数矩阵可以是任一常规的单细胞转录组技术中基因表达定量分析所产生,是一个基因(行)与细胞(列)的基因表达矩阵,统计每个细胞在单个基因上的reads计数。
格式如下所示:


该矩阵由以制表符(\t)分隔的文件提供。 (注意,也支持稀疏矩阵 - 请参阅 Running-InferCNV)

4.2 样本注释文件

样本注释文件用于定义细胞的不同的细胞类型,也可根据样本(患者)信息对细胞进行分组。格式只是两列,以制表符分隔,没有列标题。

MGH54_P2_C12    Microglia/Macrophage
MGH36_P6_F03    Microglia/Macrophage
MGH54_P16_F12   Oligodendrocytes (non-malignant)
MGH54_P12_C10   Oligodendrocytes (non-malignant)
MGH36_P1_B02    malignant_MGH36
MGH36_P1_H10    malignant_MGH36

第一列是细胞名称,第二列表示细胞的细胞类型。对于正常细胞,如果你有不同类型的正常细胞(如免疫细胞、正常成纤维细胞等),你可以直接用细胞类型进行标注;当然,你也可以将这些正常的细胞,统一标注为“正常”。如果单独定义多个“正常”类型,将根据每个正常细胞分组探索正常细胞的表达分布,而不是将它们全部视为单个正常组。inferCNV还将根据正常细胞分组在热图中进行聚类和绘制。

样本(患者)信息在属性名称中编码为“恶性{patient}”,与正常细胞进行区分;将根据样本(患者)信息对肿瘤细胞在inferCNV热图中进行聚类和绘制。

只有在样本注释文件中列出的细胞才会被inferCNV分析。如果感兴趣的细胞群是总计数矩阵的子集,注释文件选取指定的细胞群即可,不需要重新创建感兴趣的细胞群子集的新计数矩阵,因此,则此功能非常有用。

4.3 基因排序文件(Gene ordering file)

基因排序文件提供每个基因的染色体位置。格式是制表符分隔的,没有列标题,只提供基因名称、染色体和基因跨度:

WASH7P  chr1    14363   29806
LINC00115       chr1    761586  762902
NOC2L   chr1    879584  894689
MIR200A chr1    1103243 1103332
SDF4    chr1    1152288 1167411
UBE2J2  chr1    1189289 1209265

要分析的基因计数矩阵中的每个基因都应在基因排序文件中有相应的基因名称和位置信息。否则,只有那些同时存在于基因计数矩阵和基因排序文件中的基因才会被 inferCNV 分析。

注意,文件的基因必须是排序的,按染色体的位置升序排列。一些基因组位置文件可以从常见的参考文献中获得,另外, TrinityCTAT 上也提供。


如果你想自己构建某个物种的基因组基因位置文件,请参阅“创建基因组位置文件”的说明。

五、运行inferCNV

运行inferCNV的方式有以下几种:

  • 两步执行法 (2 steps execution)
  • 分步执行法 (Step by step execution)
  • WebApp (R shiny)执行
  • 在Terra平台上执行

问题1:怎么理解 InferCNV 两步执行法和 InferCNV 分步执行法?
我的理解是, InferCNV 两步执行法类似于黑箱操作,通过infercnv::run()完成inferCNV内部的所有步骤,傻瓜操作,用户不需要在意中间步骤,适合新手,我们一般都是这么操作。喂一个输入数据给infeCNV程序,然后等着它慢慢执行完,生成一堆结果。而InferCNV 分步执行法一步步执行操作,相当于infercnv::run()的代码分解。

5.1 InferCNV 两步执行法

step1:CreateInfercnvObject创建InferCNV对象
根据以上3个输入文件,读取基因计数矩阵、细胞类型注释文件和基因排序文件,创建一个 InferCNV 对象:

# create the infercnv object
infercnv_obj = CreateInfercnvObject(raw_counts_matrix="singleCell.counts.matrix",
                                    annotations_file="cellAnnotations.txt",
                                    delim="\t",
                                    gene_order_file="gene_ordering_file.txt",
                                    ref_group_names=c("normal"))

其中,ref_group_names参数的设置是指cellAnnotations.txt文件中定义的各种正常细胞类型(非肿瘤)。

注意:如果你没有参考细胞,可以设置 ref_group_names=NULL,在这种情况下,所有细胞基因的平均信号将用于定义基线(当做参照)。当inferCNV分析的细胞之间存在足够大的差异,也就是说,这些细胞在同一染色体位置并非都缺失,这种情况,设置 ref_group_names=NULL也可以很好地发挥作用。

注意:inferCNV会过滤掉了低质量的细胞,默认是 min_max_counts_per_cell = c(100, +Inf)。通过colSums函数计算raw.data基因表达矩阵中每个细胞的总UMI数,如细胞的总UMI数低于100,该细胞被剔除。如果需要进一步对每个细胞通过最小/最大UMI计数进行数据过滤,可以设置额外的过滤器,例如:min_max_counts_per_cell=c(1e5,1e6)。

CreateInfercnvObject参数说明:

CreateInfercnvObject(
    raw_counts_matrix,
    gene_order_file,
    annotations_file,
    ref_group_names,
    delim = "\t",
    max_cells_per_group = NULL,
    min_max_counts_per_cell = c(100, +Inf),
    chr_exclude = c("chrX", "chrY", "chrM")
)

step2:运行infercnv::run()
创建 infercnv_obj 对象后,你可以通过内置的 'infercnv::run()' 方法运行标准的infercnv分析,如下所示:

# perform infercnv operations to reveal cnv signal
infercnv_obj = infercnv::run(infercnv_obj,
                             cutoff=1,  # use 1 for smart-seq, 0.1 for 10x-genomics
                             out_dir="output_dir",  # dir is auto-created for storing outputs
                             cluster_by_groups=T,   # cluster
                             denoise=T,
                             HMM=T
                             )

infercnv::run()的源码位置详见链接,run()函数包含大量的参数,下面列出主要参数的说明:

  1. cutoff阈值:cutoff决定哪些基因将用于infercnv 分析。计算每个基因在所有细胞(跨细胞)的平均UMI数,如该基因的的平均UMI数值低于设置的cutoff,则该基因将被剔除。对于 smart-seq技术(全长转录本测序,通常使用细胞板测定而不是液滴),cutoff设置为1效果很好。对于 10x(以及可能的其他 3' 端测序和液滴测定,其中计数矩阵往往更稀疏),cutoff设置为0.1的值通常效果很好。
  2. out_dir参数:该参数是输出结果的文件名称。如果该目录不存在,则直接创建。
  3. cluster_by_groups参数:“cluster_by_groups”设置表示是否根据患者类型(由细胞注释文件中定义)对肿瘤细胞分别执行单独的聚类,默认是FALSE。
5.2 InferCNV分步执行法

通过上述infercnv::run() 方法执行的常规infercnv分析流程,具体操作如下:


设置 run(denoise=TRUE) 启用去噪分析程序,infeCNV提供了有几个去噪滤波器;

设置 run(HMM=TRUE) 启用 CNV 预测分析,infeCNV提供了多种 inferCNV HMM 预测方法;

inferCNV算法的详细步骤如下:
1) 过滤基因:从原始计数矩阵中删除细胞表达量太低的基因(cutoff过滤),和少于“min_cells_per_gene”中表达的基因;默认min_cells_per_gene=3,即至少在3个细胞中表达,否则删除;

# step2: Removing lowly expressed genes
# Remove genes that aren't sufficiently expressed, according to min mean count cutoff.
infercnv_obj <- require_above_min_mean_expr_cutoff(infercnv_obj, cutoff)
# require each gene to be present in a min number of cells for ref sets  
infercnv_obj <- require_above_min_cells_ref(infercnv_obj, min_cells_per_gene=min_cells_per_gene)

2) 基于测序深度的归一化(总和归一化):几乎每个工具都有自己的一套归一化方法。seurat的归一化是log1p(value/colSums[cell-idx] *scale_factor),infeCNV的归一化不同于是seurat,也不同于每百万计数 (cpm) 等指标,为了去除测序深度的影响,此处将每个细胞的总UMI计数调成固定数值(同一水平线),即所有细胞的UMI计数总和的中位数:value/colSums[cell-idx] *normalize_factor,这里的normalize_factor是每个细胞UMI总数的中位数。每个细胞的基因的表达量也成比例缩放。

# step3: normalization by sequencing depth
infercnv_obj <- normalize_counts_by_seq_depth(infercnv_obj)
#-------------------
counts.matrix <- [email protected] 
data <- counts.matrix 
cs = colSums(data) 
## make fraction of total counts: 
data <- sweep(data, STATS=cs, MARGIN=2, FUN="/") 
normalize_factor = median(cs) 
data <- data * normalize_factor

3) log转换:单个矩阵值 (x) 转换为log(x+1):这个处理跟seurat几乎等价,seurat是log1p(),inferCNV是log(x+1)。也就是说seurat的缩放常量是scale_factor,而seurat的缩放常量是normalize_factor,细胞UMI总数的中位数。

# step4: log transformation of data
infercnv_obj <- log2xplus1(infercnv_obj)
#-------------------
[email protected] <- log2([email protected] + 1)

4)以正常细胞的基因表达为中心:所有细胞的基因表达值减去正常(对照)细胞每个基因的平均值。由于此处减法是进行log转换后执行的,因此这显著地导致了相对于正常细胞平均值的对数倍(log-fold-change)变化差异。如果参照有几组,对每个参照的平均值继续求均值mean(gene_means) 。

# step8: removing average of reference data (before smoothing)
infercnv_obj <- subtract_ref_expr_from_obs(infercnv_obj, inv_log=FALSE, use_bounds=ref_subtract_use_mean_bounds)
#-------------------
ref_groups = infercnv_obj@reference_grouped_cell_indices
ref_grp_gene_means <- .get_normal_gene_mean_bounds([email protected], ref_groups, inv_log=inv_log)
[email protected] <- .subtract_expr([email protected], ref_grp_gene_means, use_bounds=use_bounds)

get_indiv_gene_group_means_bounds_fun <- function(x) {
  grp_means = c()
    grp_means <- vapply(ref_groups, function(ref_group) {
      mean(x[ref_group])
    }, double(1))
  names(grp_means) <- names(ref_groups)
  return(as.data.frame(t(data.frame(grp_means))))
}

subtract_normal_expr_fun <- function(row_idx) {
    ...
    gene_means_mean <- mean(gene_means)  
    x <- as.numeric(expr_matrix[row_idx, , drop=TRUE])

    row_init = rep(0, length(x))
    row_init <- x - gene_means_mean
    ...
}

5)异常数值的处理:对于按正常细胞中心化处理的基因相对表达量(log-fold-change values),abs(log(x+1)) 超过 'max_centered_threshold' (default=3) 的任何数值将被3,-3替换。如数值大于3,被替换成3;如小于-3,则被替换成-3,使所有的数值在正负3范围内,没有异常数值。

# step9: apply max centered expression threshold
if (is.character(max_centered_threshold) && max_centered_threshold == "auto") {
  threshold = mean(abs(get_average_bounds(infercnv_obj)))
}

infercnv_obj <- apply_max_threshold_bounds(infercnv_obj, threshold=threshold)
apply_max_threshold_bounds <- function(infercnv_obj, threshold) {
    [email protected][[email protected] > threshold] <- threshold
    [email protected][[email protected] < (-1 * threshold)] <- -1 * threshold
}

6)按染色体平滑每个细胞的数据:通过此步骤实现了很关键的一步操作,如下所示,inferCNV热图也发生了很大的变化。它是如何具体实现的呢?平滑的方式smooth_method有"pyramidinal", "runmeans", "coordinates"三种,默认是pyramidinal。window_length是水平滑动的平均窗口的长度,此数值只能是奇数,默认值是101。


文献中对inferCNV算法的部分说明:

InferCNV uses smoothed averages over gene windows;
InferCNV uses a corrected moving average of gene expression data to determine CNV profiles. Genes are sorted by absolute genomic position. That is, they are first ordered by chromosome, and then by genomic start position within the chromosome. The algorithm’s authors reason that averaging out the expression of genomically adjacent genes removes genespecific expression variability and yields profiles that reflect chromosomal copy number variations. To further refine the CNV profile of tumor cells, InferCNV constructs the CNV profile of a known normal sample, and then for each gene and each cell, the normal sample is subtracted from the tumor sample to determine the final tumor CNV profile.

这段话简单翻译一下:InferCNV是将基因表达谱转化成使用基因窗口的平滑均值,实现对CNV的推断;
InferCNV 使用基因表达数据的校正移动平均值来确定 CNV 谱。 基因按绝对基因组位置排序。 也就是说,基因首先按染色体排序,然后按染色体内的基因组起始位置排序。 该算法的作者认为,平均基因组相邻基因的表达可以消除基因特异性表达变异性,并产生反映染色体拷贝数变化的表达谱。 为了进一步细化肿瘤细胞的CNV谱,InferCNV构建了已知正常样本的CNV表达谱,然后对于每个基因和每个细胞,从肿瘤样本中减去正常样本,以确定最终的肿瘤CNV表达谱。

另外,关于Smoothing_methods的方法,wiki中有部分说明,详见链接。
每种Smoothing_method方法都是针对单个细胞的基因表达数据,且每条染色体单独运行平滑处理。inferCNV提供的三种pyramidinal,runmeans,coordinates三种具体说明如下:
1) Runmeans
针对给定基因,对该基因两侧 (window_size-1)/2 内的基因简单取平均值;
2) Pyramidal smoothing
针对给定基因,对该基因两侧侧 (window_size-1)/2 内的基因进行加权求均值。n = (window_size-1)/2给定基因附近的第 m 个基因的权重为(n+1-m)/(n+1);
3) Coordinates based method
此功能针对1.1.3版本以上版本。 对给定基因两侧的 window_size 碱基对内基因的加权平均值。中心距当前基因中心 m 个碱基对的基因的权重为 1-(m/window_size)。当表达的基因之间存在很大的差距时,这种方法可以为 CNV 提供“最干净”的限制。

问题2:window_length为什么只能是奇数,默认值设置101,有具体的含义吗?
因为在代码中,有这样的处理:tail_length = (window_length - 1)/2,window_length为奇数,tail_length 就为正整数,偶数。所以window_length若为偶数,tail_length 会有小数点,不利于后面的分析。window_length为101,tail_length 为50,类似于经验值。window_length这个数值在下游分析中处理权重时会用到。

非常重要的处理在.smooth_center_helper函数中。

# step10: For each cell, smooth the data along chromosome with gene windows
  if (smooth_method == 'runmeans') {
    infercnv_obj <- smooth_by_chromosome_runmeans(infercnv_obj, window_length)
  } else if (smooth_method == 'pyramidinal') {
    infercnv_obj <- smooth_by_chromosome(infercnv_obj, window_length=window_length, smooth_ends=TRUE)
  } else if (smooth_method == 'coordinates') {
    infercnv_obj <- smooth_by_chromosome_coordinates(infercnv_obj, window_length=window_length)
  } else {
    stop(sprintf("Error, don't recognize smoothing method: %s", smooth_method))
  }
#-------------------
# 以smooth_by_chromosome为例,chr=1
chr_genes_indices = which(gene_chr_listing == chr)
[email protected][chr_genes_indices, , drop=FALSE]
dim(chr_data)
# [1] 852 184
smoothed_chr_data = .smooth_window(data=chr_data, window_length=window_length)
[email protected][chr_genes_indices, ] <- smoothed_chr_data
smooth_by_chromosome <- function(infercnv_obj, window_length, smooth_ends=TRUE) {  
    gene_chr_listing = infercnv_obj@gene_order[[C_CHR]] 
    chrs = unlist(unique(gene_chr_listing))  
    for (chr in chrs) { 
        chr_genes_indices = which(gene_chr_listing == chr) 
        flog.info(paste0("smooth_by_chromosome: chr: ",chr)) 
        [email protected][chr_genes_indices, , drop=FALSE] 
        if (nrow(chr_data) > 1) { 
            smoothed_chr_data = .smooth_window(data=chr_data,  window_length=window_length) 
        } 
    }

.smooth_center_helper <- function(obs_data, window_length){
    nas = is.na(obs_data)
    vals = obs_data[! nas]
    custom_filter_denominator = ((window_length-1)/2)^2 + window_length
    custom_filter_numerator = c(seq_len((window_length-1)/2), ((window_length-1)/2)+1, c(((window_length-1)/2):1))  
    custom_filter = custom_filter_numerator/rep(custom_filter_denominator, window_length)
    smoothed = filter(vals, custom_filter, sides=2)  
    ind = which(! is.na(smoothed))
    vals[ind] = smoothed[ind]  
    obs_data[! nas] = vals
    return(obs_data)
}

举例:以chr1染色体为例,假设属于该chr1的基因有852个,基因按染色体起始位置有序排列。该细胞基因矩阵大小为852 *184,使用默认的滑动窗口长度window_length为101。inferCNV会对每个细胞的chr1中的852个基因的表达进行平滑处理。对于每个细胞,沿着每个染色体排列的基因使用加权运行平均值平滑表达强度。默认情况下,这是一个包含101个基因窗口,采用金字塔加权策略。
1)首先,总共有852个基因,window_length窗口大小是101,101个位点的权重是不相同的,按(1:50,51,50:1)成对称金字塔型,51位点的权重最大。如下就是加权系数比率图,距离指定基因越近的,加权比率越大,反之,距离越远,加权比率越小。跟我们遗传学上的基因连锁遗传比较类似。

image.png

2)单个细胞的852个基因的表达未平滑之前的数值是vals,通过smoothed = filter(vals, custom_filter, sides=2)处理后,生成平滑后的表达值。由下图可以看出,处理前后差异很明显。下图的x轴是852个基因的序号,y轴分别是基因归一化后的表达值和平滑处理后的基因表达值。
image.png

那问题也来了,为什么要这么处理?
问题3:单个细胞的852个基因为什么要用filter函数处理?
常规分析CNV事件使用的基因组的序列,而我们用转录组的表达谱并不能真实的反映基因组序列的CNV信息。就如前面文献提到的,针对单个基因,用基因组相邻基因的表达平均值可以消除基因特异性表达变异性,并产生反映染色体拷贝数变化的表达谱。 简单理解下,就是通过平滑处理单细胞转录组的表达谱,使其能真实的反映细胞真实的CNV表达谱,剔除基因转录表达过程中的“过表达”的干扰。

接下来,我们查阅下filter这个函数:
该函数的详细说明详见链接,它是stats包中的一个函数,用作时间序列的线性滤波。官方的说明是:对单变量时间序列或多变量时间序列的每个序列分别应用线性滤波。
先看下线性滤波的一些定义:
平滑处理(smoothing):也叫模糊处理(bluring),常用来减少图像上的噪点或者失真,还能用来降低图像分辨率。在尽量保持图像细节特征的前提下,对图像的噪点进行抑制,消除图像中的噪声成分叫做图像的平滑化或滤波操作

滤波器:是信号处理中最重要的研究对象,滤波器可以将原始信号的有用信息通过各种组合来凸显出来,抽出对象的特征作为图像识别的特征模式,消除图像数字化时所混入的噪声。
一个包含加权系数的窗口,将窗口放在图像之上,透过窗口看我们得到的图像。

滤波:是将信号中特定频段滤除,是抑制和防止干扰的一项重要措施。滤波可分为低通滤波和高通滤波;低通:模糊,高通:锐化
滤波的方式:线性滤波和非线性滤波;
如果运算只是对各像素灰度值进行简单处理(如乘一个权值)最后求和,就称为线性滤波;而如果对像素灰度值的运算比较复杂,而不是最后求和的简单运算,则是非线性滤波

filter(x, filter, method = c("convolution", "recursive"),
sides = 2, circular = FALSE, init)

filter相关参数的说明:



filter案例说明:

x <- 1:100

# example1
filter(x, rep(1, 3), sides = 2)
# Time Series:
#   Start = 1 
# End = 100 
# Frequency = 1 
# [1]  NA   6   9  12  15  18  21  24  27  30  33  36  39  42  45  48  51  54
# [19]  57  60  63  66  69  72  75  78  81  84  87  90  93  96  99 102 105 108
# [37] 111 114 117 120 123 126 129 132 135 138 141 144 147 150 153 156 159 162
# [55] 165 168 171 174 177 180 183 186 189 192 195 198 201 204 207 210 213 216
# [73] 219 222 225 228 231 234 237 240 243 246 249 252 255 258 261 264 267 270
# [91] 273 276 279 282 285 288 291 294 297  NA

# example2
filter(x, rep(1, 3), sides = 1)
# Time Series:
#   Start = 1 
# End = 100 
# Frequency = 1 
# [1]  NA  NA   6   9  12  15  18  21  24  27  30  33  36  39  42  45  48  51
# [19]  54  57  60  63  66  69  72  75  78  81  84  87  90  93  96  99 102 105
# [37] 108 111 114 117 120 123 126 129 132 135 138 141 144 147 150 153 156 159
# [55] 162 165 168 171 174 177 180 183 186 189 192 195 198 201 204 207 210 213
# [73] 216 219 222 225 228 231 234 237 240 243 246 249 252 255 258 261 264 267
# [91] 270 273 276 279 282 285 288 291 294 297

# example3
filter(x, rep(1, 3), sides = 1, circular = TRUE)
# Time Series:
#   Start = 1 
# End = 100 
# Frequency = 1 
# [1] 200 103   6   9  12  15  18  21  24  27  30  33  36  39  42  45  48  51
# [19]  54  57  60  63  66  69  72  75  78  81  84  87  90  93  96  99 102 105
# [37] 108 111 114 117 120 123 126 129 132 135 138 141 144 147 150 153 156 159
# [55] 162 165 168 171 174 177 180 183 186 189 192 195 198 201 204 207 210 213
# [73] 216 219 222 225 228 231 234 237 240 243 246 249 252 255 258 261 264 267
# [91] 270 273 276 279 282 285 288 291 294 297

y=filter(x, filter, method="convolution", side=2,...)—线性滤波函数,
x为待转化的向量数据,
method=convolution(卷积方法):使用x内部样本组成线性模型(系数ai由filter参数设置的,side参数设置卷积方法是单边或者双边),、
线性滤波卷积的计算公式是:y[i] = x[i] + f[1]*y[i-1] + … + f[p]*y[i-p]
1)案例1的计算(side=2):加权系数rep(1, 3)=c(1,1,1)
y[1]因为缺少左邻居,取NA;
y[2]=y[1]*1+y[2]*1+y[3]*1=1+2+3=6;
y[3]=y[2]*1+y[3]*1+y[4]*1=2+3+4=9;
其他依次计算;
y[100]因为缺少右邻居,取NA;
2)案例2的计算(side=1):加权系数rep(1, 3)=c(1,1,1)
y[1]、y[2]因为缺少左邻居,取NA;
y[3]=y[1]*1+y[2]*1+y[3]*1=1+2+3=6;
y[4]=y[2]*1+y[3]*1+y[4]*1=2+3+4=9;
其他依次计算;
3)案例3的计算(sides = 1, circular = TRUE):加权系数rep(1, 3)=c(1,1,1)
y[1]=y[99]*1+y[100]*1+y[1]*1=99+100+1=200;
y[2]=y[100]*1+y[1]*1+y[2]*1=100+1+2=103;
y[3]=y[1]*1+y[2]*1+y[3]*1=1+2+3=6;
y[4]=y[2]*1+y[3]*1+y[4]*1=2+3+4=9;

inferCNV的线性滤波:

  1. inferCNV通过smoothed = filter(vals, custom_filter, sides=2)进行线性滤波,side=2,我们把852个基因理解为时间序列,滑动窗口是50(100/2)。前面也分配了101个基因位点的的加权比率。针对某个基因,前50个基因+该基因+后50个基因的加权比率。
    2)前50个和后50个基因
    前50个基因是没有左侧的基因邻居,所以卷积滤波后为NA;同理,后50个基因是没有右侧基因邻居的,所以线性卷积滤波后都归于NA。后面的分析会把这些100个基因去除掉。
    前50个基因:



    后50个基因:



    3)中间的其他基因滤波计算:
    y[51]=y[1]*p1+y[2]*p2+...+y[50]*p50+y[51]*p51+y[52]*p52+...+y[101]*p101;
    后面的依次计算;
    至此,第6步的基因表达量在染色体水平平滑处理告一段落,后面的处理应该是对此步骤的去噪,优化和完善,结果的修正。

7)以细胞为中心:假设大多数基因不在CNV区域,每个细胞的平滑后的基因表达减去其所有细胞表达的中位数,以其中位值表达强度(为零)为中心。

# step11: Center cells/observations after smoothing. This helps reduce the effect of complexity.
infercnv_obj <- center_cell_expr_across_chromosome(infercnv_obj, method="median")
#------------------------------
[email protected] <- .center_columns([email protected], method)
.center_columns <- function(expr_data, method) {
     # Center within columns (cells)
    if (method == "median") {
        row_median <- apply(expr_data, 2, function(x) { median(x, na.rm=TRUE) } )
        expr_data <- t(apply(expr_data, 1, "-", row_median))
    }
    else {
       # by mean
        row_means <- apply(expr_data, 2, function(x) { mean(x, na.rm=TRUE) } )
        expr_data <- t(apply(expr_data, 1, "-", row_means))
    }
    return(expr_data)
}

8)相对于正常细胞的调整:再次从肿瘤细胞中减去正常细胞的平均值。这将进一步补偿了平滑过程后产生的差异。

# step12:Subtract average reference (adjustment)
infercnv_obj <- subtract_ref_expr_from_obs(infercnv_obj, inv_log=FALSE, use_bounds=ref_subtract_use_mean_bounds)
ref_grp_gene_means <- .get_normal_gene_mean_bounds([email protected], ref_groups, inv_log=inv_log) 
[email protected] <- .subtract_expr([email protected], ref_grp_gene_means, use_bounds=use_bounds)

9)log数值转换被还原:在前面的归一化处理中进行了log转换,在最后的步骤中进行log还原,使得推断CNV的扩增和缺失证据围绕平均值更加对称。(注意,当丢失或增加一个拷贝时,相应的值0.5和1.5在对数空间中是不对称的。相反,0.5和2在对数空间中是对称的。因此,我们反转对数变换以更好地反映增益和损失的对称性)。

# step14:invert log transform  (convert from log(FC) to FC)
infercnv_obj <- invert_log2(infercnv_obj)
[email protected] <- 2^[email protected]

以上的步骤只是生成了“初步推断CNV的对象”(preliminary infercnv object)。通过上面的步骤处理,支持CNV最明显的信号在此处的inferCNV基因表达热图中通常已很明显。后续应用附加滤波可作为提高信噪比的一种方法。此外,可以使用HMMs预测CNV事件发生的区域。

先到这吧,infeCNV的每个步骤处理还是很巧妙和用心的,后面有时间再看。


参考资料:
https://bioconductor.org/packages/devel/bioc/manuals/infercnv/man/infercnv.pdf
https://broadinstitute.github.io/2019_scWorkshop/functional-analysis.html#infercnv-honeybadger
https://www.biorxiv.org/content/biorxiv/early/2021/10/19/2021.10.18.463991.full.pdf

你可能感兴趣的:(InferCNV:从肿瘤单细胞RNA-Seq数据中推断拷贝数变化)