我有一个带有〜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)
}
答案 0 :(得分:7)
加速代码的一种方法是使用Rcpp软件包,而不是基本R中可能的代码。考虑以下Rcpp实现的完整操作。它需要输入以下内容:
valid
:足够大的组件中所有节点的索引el
,deg
,firstPos
:图表边缘列表(节点i
的邻居{{1}的表示形式},el[firstPos[i]]
,...,el[firstPos[i]+1]
)。el[firstPos[i]+deg[i]-1]
:要采样的子图尺寸size
:重复次数nrep
:weights
V(G)$weight
:RWRNodeweight
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次复制的时间非常快(请注意,对于此基准测试的大部分内容,我测试了原始igraph
和josilber
函数用于1次复制而不是1000次,因为1000次测试需要花费相当长的时间):
random_network
功能加速1200倍,比原始josilber
功能加速4000倍)random_network
功能加速8700倍,比原始josilber
功能加速162000倍)random_network
功能加速32000倍,加速 2040万次 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
}
对于单个复制,这会在问题中的代码的单个复制中产生显着的加速:
正如其他答案中所提到的,并行化可以进一步提高对给定图形大小进行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)
我没有完整的答案,但是有些事情需要考虑以帮助加快速度(假设使用不同的方法没有更快的方法)。
从图表中删除任何不属于您要查找的组件的节点。这将取决于您的网络结构,但看起来您的网络是基因,所以可能有许多基因程度非常低,您可以通过删除它们来获得一些加速。类似这样的代码:
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)
考虑并行运行此功能以利用多个内核。例如,使用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