这是一个高级的一般性问题。有一些类似的例子,有不同的,更简洁的例子。也许它无法回答。 conn
是一个矩阵。
for (i in 2:dim(conn)[1]) {
for (j in 2:dim(conn)[1]) {
if ((conn[i, 1] == conn[1, j]) & conn[i, 1] != 0) {
conn[i, j] <- 1
conn[j, i] <- 1
}
else {
conn[i, j] <- 0
conn[j, i] <- 0
}
}
}
这是来自clusterCons包的cluscomp
。
我的问题很简单:是否有可能加快循环或向量化? 作为一个R初学者,我看不到它,也不想最终感到沮丧,因为它可能是不可能的。 我会接受任何可以说是或否的答案,并暗示所涉及的潜在努力量。
答案 0 :(得分:2)
以下是我将如何编写它,使用outer
替代双循环。请注意,它仍在进行比所需更多的计算,但肯定更快。我假设conn
是一个方阵。
原始代码:
f1 <- function(conn) {
for (i in 2:dim(conn)[1]) {
for (j in 2:dim(conn)[1]) {
if ((conn[i, 1] == conn[1, j]) & conn[i, 1] != 0) {
conn[i, j] <- 1
conn[j, i] <- 1
} else {
conn[i, j] <- 0
conn[j, i] <- 0
}
}
}
return(conn)
}
我的建议:
f2 <- function(conn) {
matches <- 1*outer(conn[-1,1], conn[1,-1], `==`)
matches[conn[-1,1] == 0, ] <- 0
ind <- upper.tri(matches)
matches[ind] <- t(matches)[ind]
conn[-1,-1] <- matches
return(conn)
}
一些示例数据:
set.seed(12345678)
conn <- matrix(sample(1:2, 5*5, replace=TRUE), 5, 5)
conn
# [,1] [,2] [,3] [,4] [,5]
# [1,] 2 2 1 2 1
# [2,] 1 1 2 2 1
# [3,] 2 2 1 2 1
# [4,] 2 2 2 2 1
# [5,] 1 1 2 2 1
结果:
f1(conn)
# [,1] [,2] [,3] [,4] [,5]
# [1,] 2 2 1 2 1
# [2,] 1 0 1 1 0
# [3,] 2 1 0 0 1
# [4,] 2 1 0 1 0
# [5,] 1 0 1 0 1
identical(f1(conn), f2(conn))
# [1] TRUE
一个更好的例子,时间比较:
set.seed(12345678)
conn <- matrix(sample(1:2, 1000*1000, replace=TRUE), 1000, 1000)
system.time(a1 <- f1(conn))
# user system elapsed
# 59.840 0.000 57.094
system.time(a2 <- f2(conn))
# user system elapsed
# 0.844 0.000 0.950
identical(a1, a2)
# [1] TRUE
也许不是你能得到的最快的方法(我毫不怀疑这里的其他人可以使用例如编译器或Rcpp找到更快的速度),但我希望它足够短和快。
编辑:因为已经指出(从该代码的抽取位置的上下文)conn
是一个对称矩阵,我的解决方案可以缩短一点:
f2 <- function(conn) {
matches <- outer(conn[-1,1], conn[1,-1],
function(i,j)ifelse(i==0, FALSE, i==j))
conn[-1,-1] <- as.numeric(matches)
return(conn)
}
答案 1 :(得分:2)
非矩阵解 - 应该非常快,假设conn是非负的且对称的......
connmake = function(conn){
ordering = order(conn[,1])
breakpoints = which(diff(conn[ordering,1]) != 0)
if (conn[ordering[1], 1] != 0){
breakpoints = c(1, breakpoints + 1, nrow(conn) + 1)
} else {
breakpoints = c(breakpoints + 1, nrow(conn) +1)
}
output = matrix(0, nrow(conn), nrow(conn))
for (i in 1:(length(breakpoints) - 1)){
output[ ordering[breakpoints[i]:(breakpoints[i+1] -1)],
ordering[breakpoints[i]:(breakpoints[i+1] -1)]] = 1
}
output[,1] = conn[,1]
output[1,] = conn[,1]
output
}
使用早期基准测试的一些测试代码。 (原始代码实现为orig()
,f2()
是之前的建议。)
size = 2000
conn = matrix(0, size, size)
conn[1,] = sample( 1:20, size, replace = T)
conn[,1] = conn[1,]
system.time(orig(conn) -> out1)
#user system elapsed
#20.54 0.00 20.54
system.time(f2(conn) -> out2)
#user system elapsed
#0.39 0.02 0.41
system.time(connmake(conn) -> out3)
#user system elapsed
#0.02 0.00 0.01
identical(out1, out2)
#[1] TRUE
identical(out1, out3)
#[1] TRUE
请注意,对于包含0的conn,f2实际上是失败的,但不是我的问题,是吗?具有负值的conn可以简单地通过例如处理来处理。通过安全偏移增加相关值。非对称conn将需要更多的思考,但应该是可行的......
一般的教训是,与成对比较相比,排序很快。成对比较是O(N ^ 2),而R中最慢的排序算法是O(N ^ 4/3)。一旦数据被排序,比较就变得微不足道了。
答案 2 :(得分:1)
有些事情会浮现在脑海中。
首先,您可以通过循环对角线下方或对角线上方的条目来缩短大约一半的时间。如果矩阵是正方形,则任何一个都可以。如果dim(conn)[1] > dim(conn)[2]
,那么您将需要使用类似
for (j in 2:dim(conn)[2]) {
for (i in j:dim(conn)[1]) {
...
}
}
其次,人们可能会尝试使用apply
之类的,因为它们通常会产生大量的时间减少。但是,在这种情况下,每个[i,j]单元格都会引用列头[1,j]
和行头[i,1]
,这意味着我们不能只将单元格,行或列发送到* pply。为了清楚代码,我可能会保留for
循环。任何基于* pply的技巧都会如此巧妙,以至于我忘记了它从现在起一年后的运作方式。
最后,这似乎是一个经典的例子,使用从R调用的C会更快,更快。这可能看起来像很多工作,但它比你想象的要容易得多,甚至(对于这个特殊的例如)如果你不知道C.第一个从R中调用C对我有意义的简短例子是here,但它没有利用Rcpp,所以我不会就此止步。或者,如果您从任何简单的Rcpp代码示例开始,那么您可以修改它以执行您想要的操作。如果您只想修改其他人的代码,请从this StackOverflow thread开始。