CytoTRACE 原理及代码解释

CytoTRACE是一款基于单细胞计数矩阵推测细胞间活性和细胞间相对分化状态的一款软件,这里我们结合实际代码例子,解释CytoTRACE的数学原理:

step 1:
这一步先将预处理后的单细胞矩阵进行表达基因数量的检测,而处理的方法是过滤NA值和低表达的基因,利用行总和大于0进行筛选,并计算细胞相关性矩阵

# 载入相应的数据
load('C:/Users/Downloads/CytoTRACE/data/marrow_10x_expr.rda')
# mat 为13517行3427列的矩阵
mat = marrow_10x_expr
batch = NULL
enableFast = TRUE
ncores = 1
subsamplesize = 1000

range01 <- function(x){(x-min(x))/(max(x)-min(x))}
#inputs
a1 <- mat
a2 <- batch

#Checkpoint: NAs and poor quality genes
#过滤NA值和低表达的基因,利用行总和大于0进行筛选
pqgenes <- is.na(rowSums(mat>0)) | apply(mat, 1, var) == 0
num_pqgenes <- length(which(pqgenes == TRUE))
mat <- mat[!pqgenes,]

# 定义子采样的数据集大小
size <- subsamplesize
# 定义子采样的组别
chunk <- round(ncol(mat)/size)
subsamples <- split(1:ncol(mat), sample(factor(1:ncol(mat) %% chunk)))

这里的子采样分为三组0,1,2;相当于将细胞分为0,1,2三组


step 2:
这一步做基因表达矩阵的标准化,对基因进行过滤并构建马尔可夫矩阵

batches <- parallel::mclapply(subsamples, mc.cores = min(chunk, ncores), function(subsample){
  # 分批次对单细胞矩阵进行log2标准化
  ## 若batch = NULL 则忽略
  mat <- mat[,subsample]
  batch <- batch[subsample]
  
  if(max(mat)<50){
    mat <- 2^mat - 1
  }
  
  #Checkpoint: ERCC standards
  #去除线粒体基因的表达
  if(length(grep("ERCC-", rownames(mat)))>0){
    mat <- mat[-grep("ERCC-", rownames(mat)),]
  }
  
  #Checkpoint: Sequencing depth normalization
  #对每个细胞的测序深度进行标准化
  mat <- t(t(mat)/apply(mat, 2, sum))*1000000
  
  #Checkpoint: NAs and poor quality cells
  # 去除NA和低质量的细胞,每个细胞列的和小于零定义为低质量细胞
  pqcells <- is.na(apply(mat>0, 2, sum)) | apply(mat>0, 2, sum) <= 10
  num_pqcells <- length(which(pqcells == TRUE))
  mat <- mat[,!pqcells]
  
  #Checkpoint: log2-normalize
  # 将表达矩阵进行log2运算
  mat <- log(mat+1,2)
  mat <- data.matrix(mat)
  
  # 矫正批次效应,若batch = NULL则忽略
  #Calculate pre-batch corrected gene counts
  counts <- apply(mat>0, 2, sum)
  #Checkpoint: Batch correction
  if(ncol(a1) == length(a2)){
    #filter poor quality cells from batch vector
    batch <- batch[!pqcells]
    
    #Run Combat
    # 用Combat去除批次效应
    suppressMessages(mat <- sva::ComBat(mat, batch, c()))
    mat <- data.matrix(mat)
    
    #Replace negative values after batch correction with zeroes for compatibility with downstream steps
    # 将单细胞矩阵中的负值替换成 0
    mat[which(mat<0)] <- 0
  }
  #Rescale each single cell with gene counts to convert relative transcript abundances to absolute RNA content prior to cell lysis (credit: Census, Qiu et al., 2017)
  # 用基因计数重新调整每个单细胞,以在细胞裂解前将相对转录本丰度转换为绝对 RNA 含量
  ## 这里的RNA丰度是绝对丰度,将之前log2运算后的mat进行绝对丰度的计算
  census_normalize <- function(mat, counts) {
    xnl <- 2^data.matrix(mat) - 1
    rs <- apply(xnl, 2, sum)
    rnorm <- t(t(xnl) * counts/rs)
    A <- log(rnorm+1,2)
    return(A)
  }
  
  mat2 <- census_normalize(mat, counts)

  #Function to identify the most variable genes
  # 寻找高方差的基因
  mvg <- function(matn) {
    A <- matn
    n_expr <- rowSums(A > 0);
    A_filt <- A[n_expr >= 0.05 * ncol(A),];
    vars <- apply(A_filt, 1, var);
    means <- apply(A_filt, 1, mean);
    # 计算离散度disp
    disp <- vars / means;
    # 筛选出1000个离散度大的基因(从大到小排序)
    # last_disp 为按离散度从大到小排序的第1000个基因的离散度
    last_disp <- tail(sort(disp), 1000)[1];
    # 筛选出离散度大于 last_disp 的基因
    A_filt <- A_filt[disp >= last_disp,];
    return(A_filt)
  }
  
  #Filter out cells not expressing any of the 1000 most variable genes
  # 将离散度大的1000个基因中没有表达的基因删除掉
  mat2.mvg <- mvg(mat2)
  rm1 <- colSums(mat2.mvg) == 0
  mat2 <- mat2[, !rm1]
  counts <- counts[!rm1]
  
  #Calculate similarity matrix
  # 计算马尔可夫矩阵,即将相关性矩阵通过对角线取 0 等操作转换为马尔可夫矩阵
  similarity_matrix_cleaned <- function(similarity_matrix){
    D <- similarity_matrix
    # 设定阈值来控制网络边的生成
    cutoff <- mean(as.vector(D))
    # 将对角线元素替换成 0 
    diag(D) <- 0;
    # 将负数换为 0 
    D[which(D < 0)] <- 0;
    # 小于cutoff的换为 0
    D[which(D <= cutoff)] <- 0;
    Ds <- D
    D <- D / rowSums(D);
    D[which(rowSums(Ds)==0),] <- 0
    return(D)
  }
  # 利用基因的方差信息筛选出来的那1000个基因的RNA绝对定量矩阵计算马尔可夫矩阵
  D <- similarity_matrix_cleaned(HiClimR::fastCor(mvg(mat2)))  
   
  # mat2 为RNA绝对定量矩阵,counts 为pre-batch corrected gene counts可以理解为每个细胞的测序深度(每个细胞中基因表达counts总和),D为马尔可夫矩阵
  return(list(mat2 = mat2,counts = counts, D = D))
})

其中census_normalize函数转化被证实可以检测到更多的差异基因,而返回结果:mat2 为RNA绝对定量矩阵,counts 为pre-batch corrected gene counts可以理解为每个细胞的测序深度(每个细胞中基因表达counts总和),D为马尔可夫矩阵
D矩阵如下:


D矩阵

HiClimR::fastCor(mvg(mat2)) 计算的相关性


HiClimR::fastCor(mvg(mat2))

step 3:
利用nnls回归得到真实的GCS,,结合期望马尔可夫矩阵的信息,对真实的GCS进行矫正,使之更加合理

#Prepare for downstream steps
# 准备下面分析的基因表达矩阵
## 合并每个组别(0,1,2)对应的单细胞表达矩阵
mat2 <- do.call(cbind, lapply(batches, function(x) x$mat2))
# 合并每个组别(0,1,2)每个细胞对应的测序深度
counts <- do.call(c, lapply(batches, function(x) x$counts))
filter <- colnames(a1)[-which(colnames(a1) %in% colnames(mat2))]

#Calculate gene counts signature (GCS) or the genes most correlated with gene counts
# 对利用单细胞表达矩阵mat2的每一行(每个基因在不同细胞中的表达量向量),与细胞测序深度向量 counts 计算相关性
ds2 <- sapply(1:nrow(mat2), function(x) ccaPP::corPearson(mat2[x,],counts))
names(ds2) <- rownames(mat2)
# 提取相关性高的前200个基因并分别在每个细胞中求集合平均数
gcs <- apply(mat2[which(rownames(mat2) %in% names(rev(sort(ds2))[1:200])),],2,mean)

# 定义细胞测序深度并命名
samplesize <- unlist(lapply(lapply(batches, function(x) x$counts), length))
gcs2 <- split(gcs, as.numeric(rep(names(samplesize), samplesize)))
# 提取每个组别(0,1,2)相关性矩阵
D2 <- lapply(batches, function(x) x$D)
## gcs2为每个组别(0,1,2)与基因计数最正相关的前 200 个基因在每个细胞中的几何平均表达量向量 (GCS);D2 为每一个组别(0,1,2)对应的相关性矩阵

#Regress gene counts signature (GCS) onto similarity matrix
# nnls回归的目的是得到期望的GCS,A 为马尔可夫矩阵,b为GCS
## 解读 regressed 函数,将会在后面 cytotrace 函数中出现
regressed <- function(similarity_matrix_cleaned, score){
  out <- nnls::nnls(similarity_matrix_cleaned,score)
  # 计算期望的GCS
  score_regressed <- similarity_matrix_cleaned %*% out$x
  return(score_regressed)
}

#Apply diffusion to regressed GCS using similarity matrix
# 利用马尔可夫随机过程对期望的GCS向量进行迭代,优化矫正期望的GCS向量
## 解读 diffused 函数,将会在后面 cytotrace 函数中出现
diffused <- function(similarity_matrix_cleaned, score, ALPHA = 0.9){
  vals <- score
  # 向量化,v_prev 和v_curr 为向量
  v_prev <- rep(vals);
  v_curr <- rep(vals);
  
  for(i in 1:10000) {
    v_prev <- rep(v_curr);
    v_curr <- ALPHA * (similarity_matrix_cleaned %*% v_curr) + (1 - ALPHA) * vals;
    diff <- mean(abs(v_curr - v_prev));
    # 迭代至误差最小
    if(diff <= 1e-6) {
      break;
    }
  }
  return(v_curr)
}

# 解读 cytotrace 函数
cytotrace <- parallel::mclapply(1:length(D2), mc.cores = ncores, function(i) {
  # 分别每个组别(0,1,2)的马尔可夫矩阵和每个组别(0,1,2)的GCS做nnls回归,完成每个组别(0,1,2)所对应相关性矩阵的矫正
  gcs_regressed <- regressed(D2[[i]], gcs2[[i]])
  # 利用马尔可夫矩阵对期望的GCS向量进行矫正,是期望的GCS向量达到平稳
  gcs_diffused <- diffused(D2[[i]], gcs_regressed)
  # 按照GCS的大小进行排序
  cytotrace <- rank(gcs_diffused)
}
)

cytotrace <- cytotrace_ranked <- unlist(cytotrace)
# 标准化GCS的值到0到1之间
cytotrace <- range01(cytotrace)

所得到的cytotrace为一列长度为细胞数量(3427个)的向量


gcs2代表GCS,即每个细胞中基因表达的几何平均数


gcs2

nnls的计算模型为:


与基因计数最正相关的前 200 个基因在每个细胞中的几何平均表达被定义为基因计数特征向量 (GCS)
在我们的例子中,建立了马尔可夫矩阵与GCS表达的线性关系,相关性矩阵为 A,GCS为 b
并且 score_regressed <- similarity_matrix_cleaned %*% out$x得到的是期望的GCS
利用马尔可夫随机过程和对期望的GCS向量进行矫正,使期望的GCS达到平稳

step 4:
筛选结果

#Calculate genes associated with CytoTRACE
# 计算每一行基因 (每个基因在不同细胞中的表达量向量) 与 cytotrace (姑且命名为cytotrace)计算相关性
cytogenes <- sapply(1:nrow(mat2),
                    function(x) ccaPP::corPearson(mat2[x,], cytotrace))
names(cytogenes) <- rownames(mat2)

#Final steps
# 对结果进行筛选
names(cytotrace) <- names(cytotrace_ranked) <- names(gcs) <- names(counts) <- colnames(mat2)
cytotrace <- cytotrace[colnames(a1)]
cytotrace_ranked <- cytotrace_ranked[colnames(a1)]
gcs <- gcs[colnames(a1)]
counts <- counts[colnames(a1)]

mat2 <- t(data.frame(t(mat2))[colnames(a1),])
names(cytotrace) <- names(cytotrace_ranked) <- names(gcs) <- names(counts) <- colnames(mat2) <- colnames(a1)

总结

该软件的核心是利用细胞间相关性构建马尔可夫矩阵,并利用nnls建立马尔可夫矩阵与GCS(与基因计数最正相关的前 200 个基因在每个细胞中的几何平均表达被定义为基因计数特征向量)的回归关系,得到期望的GCS,利用马尔可夫随机过程对期望的GCS向量进行矫正,使期望的GCS向量达到平稳,那么GCS的值被定义为每个细胞的活性

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,099评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,828评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,540评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,848评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,971评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,132评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,193评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,934评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,376评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,687评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,846评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,537评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,175评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,887评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,134评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,674评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,741评论 2 351

推荐阅读更多精彩内容