草庐IT

利用nnls进行反卷积运算

小潤澤 2023-09-28 原文

相比较SVR而言,这里有另外一种解决单细胞反卷积的方法,nnls(非负最小二乘)
文章链接:《Bulk tissue cell type deconvolution with multi-subject single-cell expression reference》

核心思想

  1. Xjp 代表给定tissue的 sample j 中gene g的mRNA分子数
  2. Xjpc 代表给定tissue的 sample j ,某一cell c中gene g的mRNA分子数
  3. Ckj 代表cell type k
  4. mkj 代表cell type k的细胞数


这个式子代表给定tissue的 sample j ,cell type k 中gene g 的平均相对表达量
上式可以改编为

1.

代表cell type k中细胞总mRNA分子数的平均值(除以cell type k的总细胞数记作平均)

  1. 记作total number of cells in the bulk tissue

  2. 记作proportion of cells from cell type k

  3. 记作 bulk tissue 中 gene g 的相对表达量

因此基于上述关系我们容易得知:


这样的正比关系也暗示我们可以用线性模型来衡量这种关系,因此我们的目标就是估计 pkj 来表示细胞成分

代码分析

1. 读取数据并运算

library(MuSiC)

GSE50244.bulk.eset = readRDS('C:/Users/lenovo/Downloads/GSE50244bulkeset.rds')
GSE50244.bulk.eset

EMTAB.eset = readRDS('C:/Users/lenovo/Downloads/EMTABesethealthy.rds')
EMTAB.eset


XinT2D.eset = readRDS('C:/Users/lenovo/Downloads/XinT2Deset.rds')
XinT2D.eset


Est.prop.GSE50244 = music_prop(bulk.eset = GSE50244.bulk.eset, sc.eset = EMTAB.eset, clusters = 'cellType',
                               samples = 'sampleID', select.ct = c('alpha', 'beta', 'delta', 'gamma',
                                                                   'acinar', 'ductal'), verbose = F)

对应函数music_prop分步解析:

# 读入数据
bulk.eset = GSE50244.bulk.eset ## 读入bulk 的数据
sc.eset = EMTAB.eset  ## 读入scRNA 的数据
markers = NULL
clusters = 'cellType'
samples = 'sampleID'
select.ct =  c('alpha', 'beta', 'delta',
           'gamma','acinar', 'ductal')   ## cell type 的名称
cell_size = NULL
ct.cov = FALSE
verbose = TRUE
iter.max = 1000
nu = 0.0001
eps = 0.01
centered = FALSE
normalize = FALSE

其中bulk.eset代表的是不同的bulk seq的数据,该例子中一共89个sample:


bulk.eset

本项目的scRNA seq数据如下:
首先是sample的情况

sc.eset sampleID

1097代表的是总共的细胞数目,形如1,2,3,.....这样的属于不同的sample(全为 1 的属于同一个sample,全为 2 的属于另一个sample,以此类推)
其次是cell type的情况:
sc.eset cellTypeID

1097代表的是总共的细胞数目,形如1,2,3,.....这样的属于不同的cell type(全为 1 的属于同一个cell type,全为 2 的属于另一个cell type,以此类推)

接下来对数据进行处理:

# bulk 中选择基因表达不全为0的基因
bulk.gene = rownames(bulk.eset)[rowMeans(exprs(bulk.eset)) != 0]
bulk.eset = bulk.eset[bulk.gene, , drop = FALSE]

# 把这部分的基因定义为scRNA的markers
sc.markers = bulk.gene
# 对scRNA 数据进行标准化,并计算 Sigma 和 S (M.S)
sc.basis = music_basis(sc.eset, non.zero = TRUE, markers = sc.markers, clusters = clusters, samples = samples, select.ct = select.ct, cell_size = cell_size, ct.cov = ct.cov, verbose = verbose)

# sc.basis$Disgn.mtx 代表着单细胞不同 cell type 的基因表达矩阵,取bulk和scRNA基因的交集
cm.gene = intersect(rownames(sc.basis$Disgn.mtx), bulk.gene)
m.sc = match(cm.gene, rownames(sc.basis$Disgn.mtx))
m.bulk = match(cm.gene, bulk.gene)

# 筛选scRNA矩阵的交集基因
D1 = sc.basis$Disgn.mtx[m.sc, ]; 
M.S = colMeans(sc.basis$S, na.rm = T);

# 定义 bulk 表达矩阵的相对表达量
Yjg = relative.ab(exprs(bulk.eset)[m.bulk, ]); N.bulk = ncol(bulk.eset);

Sigma = sc.basis$Sigma[m.sc, ]

# 对scRNA的cell type表达情况进行筛选
valid.ct = (colSums(is.na(Sigma)) == 0)&(colSums(is.na(D1)) == 0)&(!is.na(M.S))
D1 = D1[, valid.ct]
M.S = M.S[valid.ct]
Sigma = Sigma[, valid.ct]
  
# 初始化
Est.prop.allgene = NULL
Est.prop.weighted = NULL
Weight.gene = NULL
r.squared.full = NULL
Var.prop = NULL

# 对bulk 表达矩阵的每个 sample 进行遍历
for(i in 1:N.bulk){
  if(sum(Yjg[, i] == 0) > 0){
    # 筛选非零表达的基因
    D1.temp = D1[Yjg[, i]!=0, ];
    Yjg.temp = Yjg[Yjg[, i]!=0, i];
    Sigma.temp = Sigma[Yjg[,i]!=0, ];
  }else{
    D1.temp = D1;
    Yjg.temp = Yjg[, i];
    Sigma.temp = Sigma;
  }

  # 对每个 sample 的bulk表达矩阵 (向量)与 scRNA 表达矩阵做 nnls
  lm.D1.weighted = music.iter(Yjg.temp, D1.temp, M.S, Sigma.temp, iter.max = iter.max,
                              nu = nu, eps = eps, centered = centered, normalize = normalize)
  Est.prop.allgene = rbind(Est.prop.allgene, lm.D1.weighted$p.nnls)
  Est.prop.weighted = rbind(Est.prop.weighted, lm.D1.weighted$p.weight)
  weight.gene.temp = rep(NA, nrow(Yjg)); weight.gene.temp[Yjg[,i]!=0] = lm.D1.weighted$weight.gene;
  Weight.gene = cbind(Weight.gene, weight.gene.temp)
  r.squared.full = c(r.squared.full, lm.D1.weighted$R.squared)
  Var.prop = rbind(Var.prop, lm.D1.weighted$var.p)
}
colnames(Est.prop.weighted) = colnames(D1)
rownames(Est.prop.weighted) = colnames(Yjg)
colnames(Est.prop.allgene) = colnames(D1)
rownames(Est.prop.allgene) = colnames(Yjg)
names(r.squared.full) = colnames(Yjg)
colnames(Weight.gene) = colnames(Yjg)
rownames(Weight.gene) = cm.gene
colnames(Var.prop) = colnames(D1)
rownames(Var.prop) = colnames(Yjg)

## 其中 Est.prop.weighted 表示细胞组分

music_basis内置函数中,有两个重要的参数,一个是 Sigma ,另一个是 M.S

  1. Sigma
Sigma <- sapply(unique(clusters), function(ct){
   #  apply 计算某个 cell type 在不同 sample 中每个基因平均相对表达量的方差
   apply(
        ##  sapply 计算的是某个 cell type 在不同 sample 中每个基因的平均相对表达量
        sapply(unique(samples), function(sid){
            y = exprs(x)[,clusters %in% ct & samples %in% sid, drop = FALSE]
            rowSums(y)/sum(y)
   }), 1, var, na.rm = TRUE)
 })

# 以其中一个细胞类型 'delta' 为例
test = sapply(unique(samples), function(sid){
   y = exprs(x)[,clusters %in% 'delta' & samples %in% sid, drop = FALSE]
   rowSums(y)/sum(y)
})

test(cell type 'delta')

列 1,2,3...代表不同的sample,行代表不同的基因,这张表说明 细胞类型 'delta' 在不同的sample中的基因平均相对表达量;相对基因表达量的计算为 cell type A 中 属于 sample 1 的所有细胞,所组成的表达矩阵中行元素求和(行为基因)再除以全部元素的求和,rowSums(y)/sum(y)
Sigma[1:20.1:5]
Sigma 代表每个 cell type 在所有 sample 中相对基因表达量均方差

另外

y = exprs(x)[,clusters %in% 'delta' & samples %in% '1', drop = FALSE]
y(sample 1 cell type delta)

y 表示 sample 1 中属于 cell type delta 的细胞表达矩阵,行表示基因,列表示单个的细胞

  1. M.S
S <- sapply(unique(clusters), function(ct){
    # 计算 某个 cell type 在所有 sample 中表达谱特征值的均值
    my.rowMeans(
        ## sapply 计算的是某个 cell type 在不同 sample 中表达谱的特征值
       sapply(unique(samples), function(sid){
            y = exprs(x)[, clusters %in% ct & samples %in% sid, drop = FALSE]
            sum(y)/ncol(y)
 }), na.rm = TRUE)
})

# 以其中一个细胞类型 "delta" 为例
test = my.rowMeans(sapply(unique(samples), function(sid){
  y = exprs(x)[, clusters %in% "delta" & samples %in% sid, drop = FALSE]
  sum(y)/ncol(y)
}), na.rm = TRUE)

test(cell type 'delta')

列 1,2,3... 代表不同的sample,里面的值代表个 cell type 在不同 sample 中表达谱的特征值,这个特征值利用的是 cell type A 中 属于 sample 1 的所有细胞,所组成的表达矩阵中全部元素求和再除以细胞个数,sum(y)/ncol(y)
因此,
S
S代表每个 cell type 在每个 sample 中的表达谱特征值,所以 M.S(sc.basis$S 即为上述的S) 代表的每个 cell type 在所有 sample 中表达谱特征值的均值

M.S = colMeans(sc.basis$S, na.rm = T)  # sc.basis$S 即为上述的 S
M.S

另外

y = exprs(x)[,clusters %in% 'delta' & samples %in% '1', drop = FALSE]
y(sample 1 cell type delta)

y 表示 sample 1 中属于 cell type delta 的细胞表达矩阵,行表示基因,列表示单个的细胞

分析music.iter和music.basic(music.iter的内置函数)的源码:

# 对于函数 music.iter
## 读入数据
Y = Yjg.temp   # 某个 bulk 的表达矩阵
D = D1.temp    # scRNA 的表达矩阵
S = M.S
Sigma = Sigma.temp
iter.max = 1000
nu = 0.0001
eps = 0.01
centered = FALSE
normalize = FALSE


k = ncol(D); # number of cell types
# 计算 bulk 和 scRNA 基因的交集,得到相应的表达矩阵
common.gene = intersect(names(Y), rownames(D))
Y = Y[match(common.gene, names(Y))];
D = D[match(common.gene, rownames(D)), ]
Sigma = Sigma[match(common.gene, rownames(Sigma)), ]
X = D
Y = Y*100
## nnls计算
lm.D = music.basic(Y, X, S, Sigma, iter.max = iter.max, nu = nu, eps = eps)

#<-----------------分割线----------------->
# 对于函数music.basic
k = ncol(X)
## nnls 计算某个 bulk (Y 矩阵) 的表达矩阵和 scRNA (X 矩阵) 的表达矩阵
lm.D = nnls(X, Y)
r = resid(lm.D)

## 定义基因的权重,lm.D$x 代表的是 nnls 回归系数
weight.gene = 1/(nu + r^2 + colSums((lm.D$x*S)^2*t(Sigma)))

## 响应变量为 bulk 表达矩阵乘开根号后的weight.gene
Y.weight = Y*sqrt(weight.gene)
## 决策变量为 scRNA 表达矩阵对应元素乘根号后的weight.gene
D.weight = sweep(X, 1, sqrt(weight.gene), '*')

## 再次利用nnls计算
lm.D.weight = nnls(D.weight, Y.weight)
## 细胞组分比例为 p.weight
p.weight = lm.D.weight$x/sum(lm.D.weight$x)
p.weight.iter = p.weight
r = resid(lm.D.weight)

## 循环的目的是不停的迭代更新 weight.gene 使得 胞组分比例 p.weight 收敛
for(iter in 1:iter.max){
  weight.gene = 1/(nu + r^2 + colSums( (lm.D.weight$x*S)^2*t(Sigma)  ))
  Y.weight = Y*sqrt(weight.gene)
  D.weight = X * as.matrix(sqrt(weight.gene))[,rep(1,k)]
  ## 不断更迭scRNA的weight和bulk的weight,使得最终细胞组分 p.weight 达到稳定
  lm.D.weight = nnls(D.weight, Y.weight )
  p.weight.new = lm.D.weight$x/sum(lm.D.weight$x)
  r.new = resid(lm.D.weight)
  if(sum(abs(p.weight.new - p.weight)) < eps){
    p.weight = p.weight.new;
    r = r.new
    R.squared = 1 - var(Y - X%*%as.matrix(lm.D.weight$x))/var(Y)
    fitted = X%*%as.matrix(lm.D.weight$x)
    var.p = diag(solve(t(D.weight)%*%D.weight)) * mean(r^2)/sum(lm.D.weight$x)^2
    return(list(p.nnls = (lm.D$x)/sum(lm.D$x), q.nnls = lm.D$x, fit.nnls = fitted(lm.D), resid.nnls = resid(lm.D),
                p.weight = p.weight, q.weight = lm.D.weight$x, fit.weight = fitted, resid.weight =  Y - X%*%as.matrix(lm.D.weight$x),
                weight.gene = weight.gene, converge = paste0('Converge at ', iter),
                rsd = r, R.squared = R.squared, var.p = var.p));
  }
  p.weight = p.weight.new;
  r = r.new;
}

其中,lm.D$x 代表 nnls 模型的回归系数

由此可见,作者并不是简单的直接运用scRNA表达矩阵和bulk矩阵,利用nnls直接计算细胞组分 p.weight,而是先定义一个 weight.gene来衡量基因的贡献程度,然后再分别得到矫正后的scRNA表达矩阵 D.weight 和校正后的 bulk 表达矩阵 Y.weight (进行表达矩阵数值的矫正),利用矫正后的矩阵进行nnls的计算(bulk 矩阵的 Y.weight 为Y.weight = Y*sqrt(weight.gene);scRNA 矩阵 D.weight 为D.weight = sweep(X, 1, sqrt(weight.gene), '*'))。通过不断迭代使得 细胞组分 p.weight 收敛

并且思考weight.gene = 1/(nu + r^2 + colSums((lm.D$x*S)^2*t(Sigma))),其中SigmaS=M.S分别具备不同的统计学意义。Sigma 越大代表相同 cell type 中该基因在不同sample中的波动越大;S=M.S越大代表该 cell type中所有基因从整体上偏高表达,并且S=M.S代表每个 cell type 在所有 sample 中表达谱特征值的均值,该值作用是衡量某cell type的整体表达水平,而回归系数lm.D$x代表各个cell type在bulk中的成分比例;lm.D$x*S代表了每个细胞类型在bulk中整体基因表达的贡献(结果为一列向量,向量长度与cell type数相同),他们的和就代表了bulk的整体基因表达水平;所以colSums((lm.D$x*S)^2*t(Sigma))即考虑了不同sample之间基因表达的波动,也考虑了各个cell type中基因的整体表达水平;r2越高代表nnls模型拟合越差,反之越差好;因此weight.gene = 1/(nu + r^2 + colSums((lm.D$x*S)^2*t(Sigma)))越大代表该基因对nnls回归的贡献越大,从而不断迭代使得模型收敛

有关利用nnls进行反卷积运算的更多相关文章

  1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  2. ruby-on-rails - 按天对 Mongoid 对象进行分组 - 2

    在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev

  3. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

    我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

  4. ruby - 触发器 ruby​​ 中 3 点范围运算符和 2 点范围运算符的区别 - 2

    请帮助我理解范围运算符...和..之间的区别,作为Ruby中使用的“触发器”。这是PragmaticProgrammersguidetoRuby中的一个示例:a=(11..20).collect{|i|(i%4==0)..(i%3==0)?i:nil}返回:[nil,12,nil,nil,nil,16,17,18,nil,20]还有:a=(11..20).collect{|i|(i%4==0)...(i%3==0)?i:nil}返回:[nil,12,13,14,15,16,17,18,nil,20] 最佳答案 触发器(又名f/f)是

  5. ruby - 如何进行排列以有效地定制输出 - 2

    这是一道面试题,我没有答对,但还是很好奇怎么解。你有N个人的大家庭,分别是1,2,3,...,N岁。你想给你的大家庭拍张照片。所有的家庭成员都排成一排。“我是家里的friend,建议家庭成员安排如下:”1岁的家庭成员坐在这一排的最左边。每两个坐在一起的家庭成员的年龄相差不得超过2岁。输入:整数N,1≤N≤55。输出:摄影师可以拍摄的照片数量。示例->输入:4,输出:4符合条件的数组:[1,2,3,4][1,2,4,3][1,3,2,4][1,3,4,2]另一个例子:输入:5输出:6符合条件的数组:[1,2,3,4,5][1,2,3,5,4][1,2,4,3,5][1,2,4,5,3][

  6. ruby - 即使失败也继续进行多主机测试 - 2

    我已经构建了一些serverspec代码来在多个主机上运行一组测试。问题是当任何测试失败时,测试会在当前主机停止。即使测试失败,我也希望它继续在所有主机上运行。Rakefile:namespace:specdotask:all=>hosts.map{|h|'spec:'+h.split('.')[0]}hosts.eachdo|host|begindesc"Runserverspecto#{host}"RSpec::Core::RakeTask.new(host)do|t|ENV['TARGET_HOST']=hostt.pattern="spec/cfengine3/*_spec.r

  7. ruby - 是否可以覆盖 gemfile 进行本地开发? - 2

    我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI

  8. ruby - 在 Windows 机器上使用 Ruby 进行开发是否会适得其反? - 2

    这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby​​-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub

  9. ruby - 带括号和 splat 运算符的并行赋值 - 2

    我明白了:x,(y,z)=1,*[2,3]x#=>1y#=>2z#=>nil我想知道为什么z的值为nil。 最佳答案 x,(y,z)=1,*[2,3]右侧的splat*是内联扩展的,所以它等同于:x,(y,z)=1,2,3左边带括号的列表被视为嵌套赋值,所以它等价于:x=1y,z=23被丢弃,而z被分配给nil。 关于ruby-带括号和splat运算符的并行赋值,我们在StackOverflow上找到一个类似的问题: https://stackoverflow

  10. ruby - 捕获 Ruby Logger 输出以进行测试 - 2

    我有一个像这样的ruby​​类:require'logger'classTdefdo_somethinglog=Logger.new(STDERR)log.info("Hereisaninfomessage")endend测试脚本行如下:#!/usr/bin/envrubygem"minitest"require'minitest/autorun'require_relative't'classTestMailProcessorClasses当我运行这个测试时,out和err都是空字符串。我看到消息打印在stderr上(在终端上)。有没有办法让Logger和capture_io一起玩得

随机推荐