前言
MEGENA是一种基于图嵌入的方式构建基因共表达网络的工具,相比较于WGCNA的权重分析,MEGENA直接用图对嵌入来完成,因此更加接近于真实情况
MEGENA理论
Overview of MEGENA
MEGENA的步骤大致分为四步:
第一步是快速构建平面滤波网络(FPFNC);
第二步是通过网络结构的紧凑性进行多尺度的聚类分析;利用k-split将PFN网络做拆分,然后计算每一个sub-cluster的紧凑程度,直到满足Terminate条件
第三步是多尺度的hub分析,目的是识别第二步分出来的sub-cluster里面hub基因;
第四步是分析sub-cluster与trait的相关性
接下来我们就一步一步的介绍下每一步的原理:
(1). Fast Planar Filtered Network Construction
Step 1
基于基因的表达矩阵,首先计算基因之间的相关性
Step 2
构造PFN网络,首先初始化一个空网络G0(V0,E0),其中该网络的节点互不相连,E0 = ∅ 。其次对每一个gene pair(基于gene pair的相似性进行排序),并利用Boyer-Myrvold算法进行gene pair的平面性检验
图的平面性定义:如果一个图可以在二维空间可以绘制成一个没有任何边有交叉的二维图,那么定义该图为平面图
而数学上对平面图的定义是:设无向图G,若能将G画在一个平面上,使得任何两条边仅在顶点处相交,则称G是具有平面性质的图,简称平面图,否则称G是非平面图
左上图为有交叉边的平面图;
左下图为节点之间边的关系({A,C}表示节点A与节点C有关系);
右图为调试后的平面图,可以看到调试后的平面图,此时图中没有交叉边,所以该图为平面图
在构建PFN平面图的时候,由gene pair 的similarities由高到低开始做平面性检验,若该gene pair通过平面性检验则纳入PFN图(G0)中;否则不纳入G0中,Ef = E0∪{i, j}
然后重复上述过程明知道达到该平面图所能容纳的最大边数,此时图为Gf(Vf,Ef)
那么终止PFN construction的条件是:
- 该网络是最大程度嵌入的
- 嵌入网络达到一定的饱和度
- 嵌入网络的边的拒绝数达到一定数量
(2).Multiscale Clustering Analysis (MCA) on PFN
Step 1
首先介绍下度量网络紧凑性和局部紧密性的指标
- SPD:shortest path distances,图中节点之间最短的路径
LPI:Local Path Index,基于Katz指标,用于区分不同邻居节点的不同影响,Katz 指标给邻居节点赋予不同的权重, 对于短路径赋予较大的权重, 而长路径赋予较小的权重。简介下Katz指标:
其中,s(x,y)表示节点x到节点y长度为 l 的路径数量;矩阵A是网络的邻接矩阵,用于表示节点x,y是否有关系;β 为权重衰减因子
而LPI的表达式为:
Q值,Modularity,基于网络强度的不同划分成不同的模块,表达式如下:
其中:
- Aij 为邻接矩阵,用于表征节点 i,j 之间是否有关系;
- ki 和 kj 分别表示节点 i 和 j 的度;
- si 和 sj 分别表示节点 i 和 j 所属的sub-cluster(子图);
m表示为:
其中,节点 i 的度 ki 可以通过邻接矩阵进行加和求解(邻接矩阵是由0和1构成的,0代表节点之间没有关系,1代表节点之间有关系
所以Q值越小,越容易将一个大图分为若干个sub-cluster;反之越难
Step 2
Network split:k-split
这一步主要的目的是对已经构造好的PFN(大网络)进行细分,分为更细致的sub-cluster
- 对于已经构建好的PFN大网络,作者初始化Qmax为一个很大的值,k=2代表先将PFN大网络拆分为两个sub-cluster
然后利用 k-medoids clustering 进行聚类(初始化条件下是聚为两类,两个sub-cluster)
以两个sub-cluster为例子,将大的PFN按照 k-medoids clustering 进行聚类,进而区分为两个sub-cluster
- 分好sub-cluster以后,对PFN大网络计算Qk值,然后与Qmax作为对比;如果满足 Qk ≤ Qmax 或者 ns = 0,则 ns = ns + 1
- 对ns进行判断,作者设立的是 ns=10,如果满足,则退出循环;否则执行 k = k + 1
- 不满足Terminate条件的时候,重复上述步骤
值得注意的是:
- ns是控制循环数的,作者定义ns=10退出循环
- k-medoids是一种聚类方式,目的是将大的PFN图分为若干小的sub-cluster
- Qk是衡量网络强度的指标,Qk值越小,越容易将一个大图分为若干个sub-cluster;反之越难
Step 3
Identification of significantly compact sub-clusters,这一步的主要目的对上一步分出来的sub-cluster做显著性检验
对于子图 l (sub-cluster l),作者计算了子图 l (sub-cluster l)内节点的平均最短距离(SPD),然后除以子图 l (sub-cluster l)的所有节点的最短距离(SPD)之和,定义为compactness
- 表示子图 l (sub-cluster l)内节点的平均最短距离(SPD)
- | Vl | 代表父图 L的所有节点的最短距离(SPD)之和
- α is a scaling parameter to control the degree of compactness
如果子图 Vl 满足:
其中:
- νo is the compactness of the parent cluster
- νl 代表子图 l (sub-cluster l)的compactness
如果满足上述条件的子图 l (sub-cluster l),就定义为 significantly sub-clusters
(3). Multiscale Hub Analysis (MHA)
这一步的目的是将(2)中分出来的sub-cluster里面,鉴定出每个sub-cluster中的hub node(高连接度的node)
Step 1
Grouping similar scales,作者定义中节点 i 与其他节点的连通度为:
其中:
A(i, j) 表示sub-cluster(Vl)中,节点 i 与节点 j 带有weight的邻接矩阵,推测该weight是gene pair的similarities
显然,越高,说明该节点越重要,越可能是hub gene
Step 2
通过上述step 1所述,鉴定每个sub-cluster中的连通度比较高的hub node(hub gene),并检验显著性
(5). Cluster-Trait Association Analysis (CTA)
上面一步完成后,由PFN划分出来的sub-cluster,其中每个sub-cluster的节点代表每个基因,将这些基因对应的表达量矩阵进行PCA分解,选取PC1作为该矩阵的特征,与trait矩阵计算相关性,来表征sub-cluster与性状的联系
https://www.boost.org/doc/libs/1_41_0/libs/graph/doc/boyer_myrvold.html
https://blog.csdn.net/chuhang123/article/details/103309865
代码部分
(1).计算PFN
假设我们获得的基因表达矩阵如下:
datExpr
首先我们要计算每个 gene pair 的相关性
ijw <- calculate.correlation(datExpr,doPerm = cor.perm,output.corTable = FALSE,output.permFDR = FALSE)
ijw表格:
由图可知,gene pair 的相关性是从上由下依次递减的
获得 gene pair 的相关性以后,需要构建PFN大网络,而构建PFN大网络的核心是要检验每嵌入一条新的边以后要满足图的平面性
# 定义检验平面性的函数
iplanarityTesting <- function(epair,rows,cols,N)
{
nrows <- c(rows,epair[1]);ncols <- c(cols,epair[2]);
out <- planaritytest(as.integer(N),nrows,ncols)
return(out);
}
# 初始化变量
edgelist = ijw[,1:3]
max.skipEdges = NULL
maxENum = NULL
doPar = FALSE
num.cores = 1
keep.track = TRUE
#定义
max.skipEdges = ceiling((num.cores * 1000) * 0.9999)
vertex.names <- unique(c(as.character(unique(edgelist[[1]])),
as.character(unique(edgelist[[2]]))))
ijw <- cbind(match(edgelist[[1]], vertex.names), match(edgelist[[2]],
vertex.names), edgelist[[3]])
N <- length(vertex.names)
maxENum = 3 * (N - 2)
sortedEdge = ijw
Ng = N
maxENum = maxENum
# 初始化变量
sortedEdge = ijw
Ng = N
maxENum = maxENum
## Ne是PFN大网络最多所能容纳的边数
Ne <- maxENum
ne <- 0
rows <- c();# Initiate row vector
cols <- c();# initiate col vector
weights <- c();#initiate weights vector
## Check if the final PFG has less edges than the input PFG
if (Ne < ne) {stop("The input PFG has exceeding number of edges.");}
ee <- 1
# 对相关性表格 ijw 按行依次遍历
while (ne < Ne & ee <= nrow(sortedEdge))
{
# 每新嵌入一条边需要检验PFN图的平面性
if (iplanarityTesting(sortedEdge[ee,1:2],rows,cols,Ng))
{
# ijw 第一行的基因
rows <- c(rows,sortedEdge[ee,1])
# ijw 第二行的基因
cols <- c(cols,sortedEdge[ee,2])
# 权重, ijw 中 gene pair 的相关性
weights <- c(weights,sortedEdge[ee,3])
ne <- ne + 1;
## 满足条件的边嵌入PFN
}
ee <- ee + 1;
}
# 当遍历 ijw 的行,嵌入的边达到maxENum并且满足平面性检验,则PFN网络构建完成
if (length(rows) == maxENum & planaritytest(as.integer(Ng),rows,cols)) {print("PFG is complete.")}
PFN <- cbind(rows,cols,weights)
PFN <- data.frame(row = vertex.names[PFN[,1]],
col = vertex.names[PFN[,2]], weight = PFN[, 3])
最后的PFN表格如下,第三列表示 gene pair 的权重
PFN
最后将PFN表格转换为PFN网络:
# g 为父图
g <- graph.data.frame(PFN,directed = FALSE)
(2).对于主函数 do.MEGENA
# 初始化变量
g # g 为父图
do.hubAnalysis = TRUE
mod.pval = 0.05
hub.pval = 0.05
remove.unsig = TRUE
min.size = 10
max.size = 2500
func = function(x) {1-x}
doPar = FALSE
num.cores = 4
n.perm = 100
singleton.size = 3
save.output = FALSE
# do clustering
module.output <- nested.kmeans.all(g = g,single.size = singleton.size,d.func = d.func);
# get the list of unsplit parent modules
parent.idx <- which(is.infinite(module.output$module.alpha) & is.na(module.output$module.pvalue))
split.parent <- intersect(parent.idx,unique(module.output$module.relation[,1]))
parent.idx <- setdiff(parent.idx,split.parent)
# get the list of significant split children
child.modules <- module.output$modules[which(module.output$module.pvalue <= mod.pval & !(is.infinite(module.output$module.alpha) & is.na(module.output$module.pvalue)))];
# get the list of final modules
sig.modules <- c(module.output$modules[parent.idx],child.modules)
sig.modules <- sig.modules[which(sapply(sig.modules,length) >= min.size & sapply(sig.modules,length) <= max.size)]
hub.output <- get.multiScale.hubs(module.output,g,
module.degreeStat = NULL,
doPar = doPar,n.core = num.cores,
remove.unsig = remove.unsig,
alpha.range = NULL,n.perm = n.perm,
pval = mod.pval,padjust.method = "bonferroni",
output.figures = save.output)
node.table <- node.summary(PFN = g,module.output = module.output,hub.output = hub.output,module.pval = mod.pval,hub.pval = hub.pval)
output <- list(module.output = module.output,hub.output = hub.output,node.summary = node.table);
显而易见,最终的output由这几部分组成,module.output和hub.output,因此我们对应看每一个部分具体的意义:
&1. module.output
# 初始化变量
g = g
do.hubAnalysis = TRUE
mod.pval = 0.05
hub.pval = 0.05
remove.unsig = TRUE
min.size = 10
max.size = 2500
func = function(x) {1-x}
doPar = FALSE
num.cores = 4
n.perm = 100
singleton.size = 3
save.output = FALSE
single.size = 3
sig.method = "pval.perm"
k.max = Inf
n.skip = 20
n.perm = 100
module.pvalue = 0.05
d.func = function(x) {1-x}
alpha.range = seq(0.01,10,0.01)
# 取出图的所有节点(vertices,gene)
genes = V(g)$name
# **@备注 1**
con.comp <- get.connectedModule(V(g)$name,g)
output.mod <- vector("list",length(con.comp))
gg <- igraph::induced.subgraph(g,con.comp[[1]])
output.mod[[1]] <- nested.kmeans(sub.g = gg,sig.method = sig.method,n.singleton = single.size,k.max = k.max,n.skip = n.skip,
module.pvalue = module.pvalue,d.func = d.func,alpha.range = alpha.range)
其中,函数get.connectedModule 的作用是选出有节点之间有连接的Module, @备注 1
get.connectedModule <- function(genes,g) { # 以 g 为父图,利用父图的每一个节点创建子图 gg <- igraph::induced.subgraph(g = g,vids = genes); # 如果子图中有节点与其他任意节点都没有连接,单独分出来 if (!igraph::is.connected(gg)) { cls.out <- igraph::clusters(gg) out <- split(igraph::V(gg)$name,cls.out$membership); }else{ # 否则获取子图的所有gene节点 out <- list(genes) } return(out) }
那么函数nested.kmeans()具有什么功能呢?
接下来我们一步一步看,关于nested.kmeans():
step 1
首先要获得每个节点之间边的权重以及LPI值
# 初始化变量
sub.g = gg
k.max = Inf
n.skip = 20
n.perm = 100
module.pvalue = 0.05
sig.method = "pval.perm"
# d.func的作用是为了在构建图的时候使gene pair相关性大的两个gene节点靠得比较近
d.func = function(x) {1-x}
n.singleton = 3
alpha.range = seq(0.01,10,0.01)
# sub.g = gg 代表父图
D <- get.network.metric(sub.g,d.func = d.func)
S <- get.LPI(sub.g)
值得注意的是这里d.func的作用是为了在构建图的时候使gene pair相关性大的两个gene节点靠得比较近;由于gene pair的权重是用相关性来表示,意味着如果两个gene的权重大,则这两个gene的相关性就大,就会在图聚类中靠得比较近,所以它们的图 "距离" 就会小。所以用 1 来减相当于做了一个转换
其中:
- 计算单个节点之间的最短路径的函数为:
get.network.metric <- function(g,d.func = function(x) {1-x}) { dg <- g ## 函数E(dg)代表将图sub.g所有的边取出来,weight代表边的权重 igraph::E(dg)$weight <- d.func(igraph::E(dg)$weight) ## shortest_paths 计算从 from 中给出的源顶点到 to 中给出的目标顶点之间的单个最短路径(即路径本身,而不仅仅是它的长度) igraph::shortest.paths(dg) }
- 计算LPI的函数为:
get.LPI <- function(subNet,eta = 0.01,n.order = 3) { if (n.order < 3) n.order <- 3; A <- igraph::get.adjacency(subNet) LP.index <- A %*% A for (n in 3:n.order) { LP.index <- LP.index + eta^(3-n) * (LP.index %*% A); } #LP.index <- as.matrix(LP.index) return(LP.index) }
step 2
对已经构建好PFN划分利用k-中心聚类划分sub-cluster,并检验sub-cluster的一些属性,看是否满足条件
##### while-loop to construct
# define modules.keep: modules that no longer evaluated as conditions are met.
# 初始化一些变量
## 取出
modules.keep <- list(V(sub.g)$name) # sub.g = gg 代表父图
modules.singleton <- list()
do.test <- 1;
do.update <- TRUE
alpha.defined <- c(Inf)
alpha.range = seq(0.01,10,0.01))
pvalue.calc <- c(NA)
comp.score <- c(NA)
subset.relation <- matrix(0,ncol = 2,nrow = 0)
n.iter <- 1;
while (do.update)
{
#test.update <- list()
test.i <- which(do.test == 1)
for (i in 1:length(test.i))
{
do.test[test.i[1]] <- 0;
# alpha.range = seq(0.01,10,0.01))
alph <- alpha.range[which(alpha.range <= alpha.defined[test.i[1]])]
# **@备注 1**
D.rand <- get.random.D(nv = length(modules.keep[[test.i[i]]]),w = E(sub.g)[modules.keep[[test.i[i]]] %--% modules.keep[[test.i[i]]]]$weight,d.func = d.func,name.vec = V(sub.g)$name);
# **@备注 2** multiscale-clustering in a single run
## comp.p代表的是父图总的SPD值
comp.p <- compact.indiv(modules.keep[[test.i[i]]],D,alpha = alpha.range) # parent
# 构建子图sub.net,如果是第一轮循环,则子图和父图一样
sub.net <- igraph::induced.subgraph(sub.g,modules.keep[[test.i[i]]])
# 根据gene pair之间的距离聚类,划分sub-cluster,**@备注3**
pam.out <- pam.cleanSplit(sub.net,D,k.max,n.skip);
# 选出有节点之间有连接的 Module
pam.out <- do.call(c,lapply(pam.out,get.connectedModule,sub.net));
# remove singletons for downsream analysis
## 筛选出在 Module中没有任何连接的节点
i.single <- which(sapply(pam.out,length) <= n.singleton)
modules.singleton <- c(modules.singleton,pam.out[i.single]);
## 去除在Module中没有任何连接的节点
pam.out <- pam.out[setdiff(1:length(pam.out),i.single)]
# 如果根据gene pair之间的距离聚类,划分sub-cluster的数目大于1个,则:
if (length(pam.out) > 1)
{
# evaluate exceeding point
# **@备注 2**,函数compact.indiv
## comp.c代表每一个sub-cluster的SPD值
## 这里作者用了1000个alpha.range的值做计算
comp.c <- lapply(pam.out,function(v,d,alph) compact.indiv(v,d,alph),d = D,alph = alpha.range)
# comp.p代表的是父图总的SPD值
## 这一步的目的是做标准化,comp.rel 是每一个sub-cluster标准化后的SPD值
comp.rel <- lapply(comp.c,function(x,y) x/y,y = comp.p)
# check if the split is valid: is any sub-module more compact at some alpha?
def.alpha <- rep(NA,length(comp.rel))
comp.sig <- rep(1,length(comp.rel))
comp.sc <- rep(NA,length(comp.rel))
# 进行显著性判断
for (j in 1:length(comp.rel))
{
if (length(comp.rel[[j]]) > 0)
{
if (any(comp.rel[[j]] < 1))
{
# 定义最佳的 α 值
alpha.i <- max(which(comp.rel[[j]] < 1))
def.alpha[j] <- min(c(alpha.range[alpha.i],alpha.defined[test.i[i]]))
# 检验 sub-cluster SPD 的显著性
stat.out <- evaluate.significance(score.o = comp.rel[[j]][alpha.i],nv = length(pam.out[[j]]),D.rand = D.rand,alpha = alph[alpha.i],n.perm = n.perm)
if (sig.method == "pval.perm") {pval = stat.out$pval}else{pval = stat.out$pval.norm}
comp.sig[j] <- pval
comp.sc[j] <- comp.rel[[j]][alpha.i]
}
}
}
subset.relation <- rbind(subset.relation,cbind(rep(test.i[i],length(pam.out)),(length(modules.keep)+1):(length(modules.keep) + length(pam.out))))
# 将父图的所有基因与分 sub-cluster 的基因放到一起
modules.keep <- c(modules.keep,pam.out)
}
if (all(do.test == 0)) do.update <- FALSE
n.iter <- n.iter + 1
}
alpha.defined[is.na(alpha.defined)] <- 0;
names(modules.keep) <- 1:length(modules.keep);
output <- list(modules = modules.keep,singletons = modules.singleton,
module.relation = subset.relation,
module.alpha = alpha.defined,
module.pvalue = pvalue.calc,module.compRel = comp.score)
因此最后的output中最重要的组成部分是modules = modules.keep:
其中modules.keep的第一项代表的是父图中所有的基因
从modules.keep的第二项到第七项代表的是根据gene pair之间的距离,一共划分出6个小的sub-cluster
其中:
- get.random.D函数的功能为构建随机的背景集权重矩阵 @备注 1
get.random.D <- function(nv,w,d.func,name.vec) { T2.output <- random.T2(nv); el <- cbind(T2.output[[1]],sample(w,nrow(T2.output[[1]]),replace = T)); colnames(el) <- c("row","col","weight") g.rand <- graph.data.frame(as.data.frame(el),directed = F); #V(g.rand)$name <- name.vec D.rand <- get.network.metric(g.rand,d.func); return(D.rand) }
- compact.indiv函数的功能是基于单个节点之间的最短路径计算每一个子图的平均SPD值(并做标准化处理): @备注 2
compact.indiv <- function(v,D,alpha) { if (length(v) > 1) { d <- D[match(v,rownames(D)),match(v,colnames(D))] SPD.mu <- mean(d[upper.tri(d)],na.rm = T) out <- SPD.mu/(log(length(v))^alpha) }else{ out <- Inf; } return(out) }
- pam.cleanSplit函数的作用是对构建的子图,基于gene pair之间的距离,划分小的sub-cluster @备注3
pam.cleanSplit <- function(g,D,k.max = 10,n.skip) { if (igraph::vcount(g) > 1) { # 函数pam.split的核心函数是 cluster::pam,k-中心点聚类 out <- pam.split(g,D,k.max = k.max,n.skip = n.skip) modules <- out[[1]] }else{ modules <- list(igraph::V(g)$name); } return(modules) }
其中函数pam.split的核心函数是 cluster::pam,k-中心点聚类,划分小的sub-cluster, cluster::pam的聚类是基于 "图" 来聚类,即"图"上的点距离相近的容易聚到一个小的cluster里面
重点强调一下,对于代码
我们可以知道,所有的基因,根据gene pair之间的距离,一共划分出6个小的sub-cluster:# 构建子图sub.net,如果是第一轮循环,则子图和父图一样 sub.net <- igraph::induced.subgraph(sub.g,modules.keep[[test.i[i]]]) # 根据gene pair之间的距离聚类,划分sub-cluster pam.out <- pam.cleanSplit(sub.net,D,k.max,n.skip) # 选出有节点之间有连接的Module pam.out <- do.call(c,lapply(pam.out,get.connectedModule,sub.net)) # remove singletons for downsream analysis ## 筛选出在Module中没有任何连接的节点 i.single <- which(sapply(pam.out,length) <= n.singleton) modules.singleton <- c(modules.singleton,pam.out[i.single]); ## 去除在Module中没有任何连接的节点 pam.out <- pam.out[setdiff(1:length(pam.out),i.single)]
&2. hub.output
其中比较重要的部分是计算出每一个sub-cluster中的hub基因,每一个sub-cluster中的hub基因定义为在该sub-cluster中度最高的gene节点
# 初始化变量
module.output = module.output ## 经过nested.kmeans.all函数计算得到
network = g
module.degreeStat = NULL
doPar = F
n.core = 1
remove.unsig = TRUE
alpha.range = NULL
n.perm = n.perm
pval = mod.pval
padjust.method = "bonferroni"
if (is.null(module.degreeStat))
{
# 对module.output分出的几个sub-cluster,每一个sub-cluster构建一个子图
subnet.lst <- lapply(module.output$modules,function(x,g) induced.subgraph(g,x),g = network)
# 初始化一个容器 module.degreeStat
module.degreeStat <- vector("list",length(subnet.lst));names(module.degreeStat) <- names(subnet.lst)
for (i in 1:length(subnet.lst))
{
# 对上一步构建的每一个子图,获取子图中每个节点的度
## **@备注1**
module.degreeStat[[i]] <- get.DegreeHubStatistic(subnet.lst[[i]],n.perm = n.perm,doPar = doPar,n.core = n.core)
}
}
module.degreeStat的部分截图:
其中,cl_24,cl_25代表的是k-中心聚类分出来的sub-cluster,gene列表示的是 gene 节点,degree代表节点的度,后面两列代表显著性
其中:
- 函数get.DegreeHubStatistic的功能是计算每个sub-cluster构成的子图节点的度和p_val @备注1
get.DegreeHubStatistic <- function(subnetwork,n.perm = 100,doPar = FALSE,n.core = 4) { # 提取每个sub-cluster构成的子图的 gene 节点 v <- V(subnetwork)$name; k.random <- c() # 构建统计检验的 Background for (n in 1:n.perm) { random.out <- get.randomPlanar(n = vcount(subnetwork),n1 = NULL, ne = ecount(subnetwork), verbose = FALSE) random.net <- graph.edgelist(random.out[[3]],directed = F) k.random <- c(k.random,degree(random.net)) } # 进行假设检验计算 ## 构建统计检验 Background 的 pdf 函数 h.out <- hist(k.random,breaks = 1:(max(k.random)+1) - 0.5,plot = F) FDR <- 1 - cumsum(h.out$counts/length(k.random)); FDR <- data.frame(h.out$mids,FDR) k.cutoff <- min(FDR[[1]][FDR[[2]] < 0.01]) # 获取每个sub-cluster构成的子图 gene 节点的度(即每个节点边的个数) k <- degree(subnetwork) # 计算p_val pval <- vector("numeric",length(k)) for (i in 1:length(k)) { j <- which(FDR[[1]] >= k[i]) if (length(j) > 0) { pval[i] <- FDR[[2]][j[1]] }else{ pval[i] <- 0 } } hub.df <- data.frame(gene = V(subnetwork)$name,degree = > k,pvalue = pval,pvalue.BH = p.adjust(pval,"BH")) return(hub.df) }
网友评论