使用igraph从不同尺寸中采样子图

时间:2015-10-12 15:30:26

标签: r performance igraph subgraph

我有一个带有〜10,000个节点和~145,000个边缘的igraph对象mygraph,我需要从这个图中创建一些子图,但是大小不同。 我需要的是从确定的大小(从5个节点到500个节点)创建子图,其中所有节点都连接在每个子图中。我需要为每个大小创建~1,000个子图(即,大小为5的1000个子图,大小为6的1000个,依此类推),然后根据不同的节点属性计算每个图的一些值。 我有一些代码,但需要很长时间才能进行所有计算。我想使用graphlets函数来获得不同的大小,但每次我在计算机上运行它都会因内存问题而崩溃。

以下是我正在使用的代码:

第一步是创建一个函数来创建不同大小的子图,并进行所需的计算。

random_network<-function(size,G){
     score_fun<-function(g){                                                        
          subsum <- sum(V(g)$weight*V(g)$RWRNodeweight)/sqrt(sum(V(g)$RWRNodeweight^2))
           subsum
           } 

      genes.idx <- V(G)$name
      perm <- c()
      while(length(perm)<1000){
           seed<-sample(genes.idx,1) 
           while( length(seed)<size ){
                tmp.neigh <- V(G)[unlist(neighborhood(G,1,seed))]$name
                tmp.neigh <- setdiff(tmp.neigh, seed)
                if( length(tmp.neigh)>0 )  
                seed<-c(seed,sample(tmp.neigh,1)) else break 
            }
      if( length(seed)==size )
      perm <- c(perm,score_fun(induced.subgraph(G,seed)))
      } 
      perm
     } 

第二步是将函数应用于实际图形

 ### generate some example data
 library(igraph)
 my_graph <- erdos.renyi.game(10000, 0.0003)
 V(my_graph)$name <- 1:vcount(my_graph)
 V(my_graph)$weight <- rnorm(10000)
 V(my_graph)$RWRNodeweight <- runif(10000, min=0, max=0.05)

 ### Run the code to get the subgraphs from different size and do calculations based on nodes
 genesets.length<- seq(5:500)
 genesets.length.null.dis <- list()
 for(k in 5:max(genesets.length){ 
     genesets.length.null.dis[[as.character(k)]] <- random_network(size=k,G=my_graph)
  }

5 个答案:

答案 0 :(得分:7)

加速代码的一种方法是使用Rcpp软件包,而不是基本R中可能的代码。考虑以下Rcpp实现的完整操作。它需要输入以下内容:

  • valid:足够大的组件中所有节点的索引
  • eldegfirstPos:图表边缘列表(节点i的邻居{{1}的表示形式},el[firstPos[i]],...,el[firstPos[i]+1])。
  • el[firstPos[i]+deg[i]-1]:要采样的子图尺寸
  • size:重复次数
  • nrepweights
  • 中存储的边权重
  • V(G)$weightRWRNodeweight
  • 中存储的边权重
V(G)$RWRNodeweight

现在我们在基地R中需要做的就是生成library(Rcpp) cppFunction( "NumericVector scores(IntegerVector valid, IntegerVector el, IntegerVector deg, IntegerVector firstPos, const int size, const int nrep, NumericVector weights, NumericVector RWRNodeweight) { const int n = deg.size(); std::vector<bool> used(n, false); std::vector<bool> neigh(n, false); std::vector<int> neighList; std::vector<double> scores(nrep); for (int outerIter=0; outerIter < nrep; ++outerIter) { // Initialize variables std::fill(used.begin(), used.end(), false); std::fill(neigh.begin(), neigh.end(), false); neighList.clear(); // Random first node int recent = valid[rand() % valid.size()]; used[recent] = true; double wrSum = weights[recent] * RWRNodeweight[recent]; double rrSum = RWRNodeweight[recent] * RWRNodeweight[recent]; // Each additional node for (int idx=1; idx < size; ++idx) { // Add neighbors of recent for (int p=firstPos[recent]; p < firstPos[recent] + deg[recent]; ++p) { if (!neigh[el[p]] && !used[el[p]]) { neigh[el[p]] = true; neighList.push_back(el[p]); } } // Compute new node to add from all neighbors int newPos = rand() % neighList.size(); recent = neighList[newPos]; used[recent] = true; wrSum += weights[recent] * RWRNodeweight[recent]; rrSum += RWRNodeweight[recent] * RWRNodeweight[recent]; // Remove from neighList neighList[newPos] = neighList[neighList.size() - 1]; neighList.pop_back(); } // Compute score from wrSum and rrSum scores[outerIter] = wrSum / sqrt(rrSum); } return NumericVector(scores.begin(), scores.end()); } ") 的参数,这可以很容易地完成:

scores

与原始代码和我们迄今为止看到的所有josilber.rcpp <- function(size, num.rep, G) { n <- length(V(G)$name) # Determine which nodes fall in sufficiently large connected components comp <- components(G) valid <- which(comp$csize[comp$membership] >= size) # Construct an edge list representation for use in the Rcpp code el <- get.edgelist(G, names=FALSE) - 1 el <- rbind(el, el[,2:1]) el <- el[order(el[,1]),] deg <- degree(G) first.pos <- c(0, cumsum(head(deg, -1))) # Run the proper number of replications scores(valid-1, el[,2], deg, first.pos, size, num.rep, as.numeric(V(G)$weight), as.numeric(V(G)$RWRNodeweight)) } 解决方案相比,执行1000次复制的时间非常快(请注意,对于此基准测试的大部分内容,我测试了原始igraphjosilber函数用于1次复制而不是1000次,因为1000次测试需要花费相当长的时间):

  • 大小= 10:0.06秒(比我之前提出的random_network功能加速1200倍,比原始josilber功能加速4000倍)
  • 大小= 100:0.08秒(比我之前提出的random_network功能加速8700倍,比原始josilber功能加速162000倍)
  • 大小= 1000:0.13秒(比我之前提出的random_network功能加速32000倍,加速 2040万次
  • 尺寸= 5000:0.32秒(比我之前提出的josilber功能加速68000倍,加速<强> 2.9亿倍

简而言之,Rcpp可能使每个大小的计算1000个重复从5到500变得可行(这个操作可能总共需要大约1分钟),而计算每个重复的1000个重复速度会非常慢。使用到目前为止提出的纯R代码的大小。

答案 1 :(得分:2)

基本上,您对图表进行采样的算法可以描述为将节点集初始化为随机选择的节点,然后迭代地添加当前集合的邻居,直到没有更多邻居或者您具有所需的子集大小。

这里昂贵的重复操作是确定当前集合的邻居,您可以使用以下方法:

tmp.neigh <- V(G)[unlist(neighborhood(G,1,seed))]$name
tmp.neigh <- setdiff(tmp.neigh, seed)

简而言之,您在每次迭代时查看所选子集中每个节点的邻居。一种更有效的方法是存储邻居向量,并在每次添加新节点时更新它;这将更有效,因为您只需要考虑新节点的邻居。

josilber <- function(size, num.rep, G) {
  score_fun <- function(vert) sum(vert$weight*vert$RWRNodeweight)/sqrt(sum(vert$RWRNodeweight^2))
  n <- length(V(G)$name)

  # Determine which nodes fall in sufficiently large connected components
  comp <- components(G)
  valid <- which(comp$csize[comp$membership] >= size)

  perm <- replicate(num.rep, {
    first.node <- sample(valid, 1)
    used <- (1:n) == first.node  # Is this node selected?
    neigh <- (1:n) %in% neighbors(G, first.node)  # Does each node neighbor our selections?
    for (iter in 2:size) {
      new.node <- sample(which(neigh & !used), 1)
      used[new.node] <- TRUE
      neigh[neighbors(G, new.node)] <- TRUE
    }
    score_fun(V(G)[used])
  })
  perm
}

对于单个复制,这会在问题中的代码的单个复制中产生显着的加速:

  • 对于size = 50,此代码的单个复制需要0.3秒,而已发布代码需要3.8秒
  • 对于size = 100,此代码的单个复制需要0.6秒,而已发布代码需要15.2秒
  • 对于size = 200,此代码的单个复制需要1.5秒,已发布代码需要69.4秒
  • 对于size = 500,此代码的单个复制需要2.7秒(因此1000次重复需要大约45分钟);我没有测试发布代码的单个复制品。

正如其他答案中所提到的,并行化可以进一步提高对给定图形大小进行1000次重复的性能。以下使用doParallel包来并行化重复步骤(调整与@Chris在他的回答中所做的调整非常相似):

library(doParallel)
cl <- makeCluster(4)
registerDoParallel(cl)
josilber2 <- function(size, num.rep, G) {
  score_fun <- function(vert) sum(vert$weight*vert$RWRNodeweight)/sqrt(sum(vert$RWRNodeweight^2))
  n <- length(V(G)$name)

  # Determine which nodes fall in sufficiently large connected components
  comp <- components(G)
  valid <- which(comp$csize[comp$membership] >= size)

  perm <- foreach (i=1:num.rep, .combine='c') %dopar% {
    library(igraph)
    first.node <- sample(valid, 1)
    used <- (1:n) == first.node  # Is this node selected?
    neigh <- (1:n) %in% neighbors(G, first.node)  # Does each node neighbor our selections?
    for (iter in 2:size) {
      new.node <- sample(which(neigh & !used), 1)
      used[new.node] <- TRUE
      neigh[neighbors(G, new.node)] <- TRUE
    }
    score_fun(V(G)[used])
  }
  perm
}

在我的Macbook Air上,josilber(100, 1000, my_graph)需要670秒才能运行(这是非并行版本),而josilber2(100, 1000, my_graph)需要239秒才能运行(这是配置有4名工作人员的并行版本) 。对于size=100情况,因此我们从代码改进中获得了20倍的加速,并且从并行化获得了额外的3倍加速,总速度提高了60倍。

答案 2 :(得分:1)

我没有完整的答案,但是有些事情需要考虑以帮助加快速度(假设使用不同的方法没有更快的方法)。

  1. 从图表中删除任何不属于您要查找的组件的节点。这将取决于您的网络结构,但看起来您的网络是基因,所以可能有许多基因程度非常低,您可以通过删除它们来获得一些加速。类似这样的代码:

    cgraph <- clusters(G)
    tooSmall <- which(cgraph$csize < size)
    toKeep <- setdiff(1:length(V(G)), which(cgraph$membership %in% tooSmall))
    graph <- induced.subgraph(G, vids=toKeep)
    
  2. 考虑并行运行此功能以利用多个内核。例如,使用parallel包和mclapply

    library(parallel)
    genesets.length<- seq(5, 500)
    names(genesets.length) <- genesets.length
    genesets.length.null.dis <- mclapply(genesets.length, mc.cores=7,
                                         function(length) {
                                           random_network(size=length, G=my_graph)
                                         })
    

答案 3 :(得分:1)

我认为在igraph中使用cliques函数会更有效率,因为clique是完全连接的节点的子图。只需将min和max设置为等于您要搜索的子图的大小,它将返回所有大小为5的集团。您可以采取满足您需求的任何子集。不幸的是,通过Erdos-Renyi图的示例,您经常生成最大的clique小于5,因此这不适用于该示例。但是,对于一个比鄂尔多斯 - 仁义图更多聚类的真实网络,它应该可以正常工作。

library(igraph)
##Should be 0.003, not 0.0003 (145000/choose(10000,2))
my_graph <- erdos.renyi.game(10000, 0.003)

cliques(my_graph,min=5,max=5)

答案 4 :(得分:1)

您的代码存在许多问题(您没有预先分配矢量等)。请参阅下面我提出的代码。不过,我只测试了它的大小为100的子图。但是,与代码相比,随着子图尺寸的增加,速度节省会相当大。您也应该安装foreach包。我在带有4个核心,2.1 GHz的笔记本电脑上运行它。

random_network_new <- function(gsize, G) {
  score_fun <- function(g) {
    subsum <- sum(V(g)$weight * V(g)$RWRNodeweight) / sqrt(sum(V(g)$RWRNodeweight^2))
  }

  genes.idx <- V(G)$name

  perm <- foreach (i=seq_len(1e3), .combine='c') %dopar% {
    seed <- rep(0, length=gsize)
    seed[1] <- sample(genes.idx, 1)

    for (j in 2:gsize) {
      tmp.neigh <- neighbors(G, as.numeric(seed[j-1]))
      tmp.neigh <- setdiff(tmp.neigh, seed)
      if (length(tmp.neigh) > 0) {
        seed[j] <- sample(tmp.neigh, 1)
      } else {
        break
      }
    }
    score_fun(induced.subgraph(G, seed))
  }
  perm
}

请注意,我将该函数重命名为random_network_new,将参数重命名为gsize

system.time(genesets <- random_network_new(gsize=100, G=my_graph))                                            
   user   system  elapsed 
1011.157    2.974  360.925 
system.time(genesets <- random_network_new(gsize=50, G=my_graph))
   user  system elapsed 
822.087   3.119 180.358 
system.time(genesets <- random_network_new(gsize=25, G=my_graph))
   user  system elapsed 
379.423   1.130  74.596 
system.time(genesets <- random_network_new(gsize=10, G=my_graph))
   user  system elapsed 
144.458   0.677  26.508 

使用你的代码的一个例子(我的代码大小超过10倍;对于更大的子图,它会更快):

system.time(genesets_slow <- random_network(10, my_graph))
   user  system elapsed 
350.112   0.038 350.492