【问题标题】:How to avoid a double for loop when accessing all combinations of colums of a matrix?访问矩阵的所有列组合时如何避免双循环?
【发布时间】:2016-01-22 17:53:27
【问题描述】:

我一直试图在 Internet 上找到解决方案,但未能解决我的具体问题。 我有一个矩阵 D 告诉我一组矩形(列)的最大和最小 x 和 y 坐标。现在我想知道它们中的一些相交的频率。我想评估其中两个是否相交的方法需要遍历所有可能的列组合(也“向后”)。现在对我来说第一个直观的解决方案是嵌套一个 for 循环,通过列进入另一个执行相同操作的循环。这是一个例子:

n <- 5

D <- matrix(c(1,2,1,2,1,3,1,3,2,4,2,4,3,5,1,2,3,4,2,4),nrow=4,ncol=n)

E <- mat.or.vec(nr=n, nc=n)
for (i in 1:n){
    for (j in 1:n){
        if (i != j &&
           (D[1,i] <= D[1,j] & D[1,j] < D[2,i]) &&
           ((D[3,i] <= D[3,j] & D[3,j] < D[4,i]) | (D[3,i] < D[4,j] & D[4,j] <= D[4,i]))) { 
            E[i,j] <- 1
        }
    }
}

     [,1] [,2] [,3] [,4] [,5]
[1,]    0    1    0    0    0
[2,]    1    0    1    0    0
[3,]    0    0    0    0    1
[4,]    0    0    0    0    0
[5,]    0    0    0    0    0

我一直在尝试各种替代解决方案,包括使用foreachouterapply 的不同版本以及它们的组合。我发现很多版本都给了我相同的正确结果,但是由于某种原因,双 for 循环的执行速度比任何其他方法都快。
问题是,矩阵中有大约 4000 列,我必须运行整个结果函数大约 50 次。所以循环花费的时间仍然太长。谁能告诉我为什么其他解决方案的性能更差(因为我认为循环是最糟糕的事情)并且可能会建议一些可以快速处理大量数据的方法?

编辑: 我尝试了使用 918 个矩形建议的两种解决方案并对其进行了基准测试。

D1 <- t(D)
D1 <- D1[,c(1,3,2,3,2,4,1,4)]
ID <- paste0('rect',seq_len(nrow(D1)))
polys <- SpatialPolygons(mapply(function(poly, id) {
  xy <- matrix(poly, ncol=2, byrow=TRUE)
  Polygons(list(Polygon(xy)), ID=id)
  }, split(D1, row(D1)), ID))
polys.df <- SpatialPolygonsDataFrame(polys,data.frame(id=ID,row.names=ID))

res <-  gOverlaps(polys.df, byid = TRUE) | 
        gContains(polys.df, byid = TRUE) |
        gWithin(polys.df, byid = TRUE)
diag(res) <-0

intersecting_rects <- function(edge1, edge2){
  #* index 1 is left edge, index 2 is right edge
  #* index 3 is lower edge, index 4 is upper edge
  (edge1[1] < edge2[2] && edge1[2] > edge2[1] &&
     edge1[3] < edge2[4] && edge1[4] > edge2[3])
}
microbenchmark(
  poly = {
    res <-  gOverlaps(polys.df, byid = TRUE) | 
      gContains(polys.df, byid = TRUE) | 
      gWithin(polys.df, byid = TRUE) | 
      gCovers(polys.df, byid = TRUE)
    diag(res) <- 0
  },
  full_loop = {
    E <- mat.or.vec(nr=n, nc=n)
    for (i in 1:n){
      for (j in 1:n){
        E[i, j] <- as.numeric(intersecting_rects(D[, i], D[, j]))
      }
    }
  },
  partial_loop = {
    E <- mat.or.vec(nr=n, nc=n)
    for (i in 1:n){
      for (j in (i:n)[-1]){
        E[i, j] <- as.numeric(intersecting_rects(D[, i], D[, j]))
      }
    }
    E[lower.tri(E)] <- t(E)[lower.tri(E)]
  }
)
Unit: seconds
         expr      min       lq      mean    median        uq       max neval
         poly 5.280785 5.985167  6.277424  6.288105  6.567685  7.141447   100
    full_loop 8.328631 9.700908 10.091188 10.112840 10.490162 11.450817   100
 partial_loop 4.335070 4.921649  5.203672  5.188610  5.503550  6.000614   100

我们看到,部分循环已经比完整循环有了显着改进,但仅比空间对象的解决方案略好。因此,我选择了 Rolands 解决方案。再次感谢!

【问题讨论】:

  • 请提供一个可重现的最小示例。 stackoverflow.com/questions/5963269/…
  • 如果您的矩形数量相对较少,我会考虑为每个矩形分配一个位域,然后用它们“绘制”(或)矩阵。
  • 嗯,我有大约 4000 个,所以这是一个相对较小的数字吗?
  • 如果我错了,请纠正我,但E 应该是一个对称矩阵,不是吗?也就是说,既然E[2, 3] == 1,不应该E[3,2] == 1吗?换句话说,当矩形 2 与矩形 3 相交时,矩形 3 不也与矩形 2 相交吗?我问的原因是,如果这是真的,你不需要遍历矩阵的整个维度。你只需要做上矩阵或三角矩阵,它可以把你的循环减半。
  • 另外,for 循环的名声不好。如果使用得当,它真的不是那么糟糕的工具。 for 循环在必须重新评估它们正在创建的对象类型或用于制作不断扩展的对象时往往表现最差。您已经预先定义了E 的尺寸并且没有更改其大小或类型这一事实应该可以最大限度地减少与for 循环相关的问题。我认为你必须非常有创意才能让某些东西运行得更快。

标签: r


【解决方案1】:

这不是我的专业领域,但您应该为此使用空间包。

library(sp)
library(rgeos)
n <- 5

D <- matrix(c(1,2,1,2,1,3,1,3,2,4,2,4,3,5,1,2,3,4,2,4),nrow=4,ncol=n)

D1 <- t(D)
D1 <- D1[, c(1, 3, 2, 3, 2, 4, 1, 4)]
ID <- paste0('rect', seq_len(nrow(D1)))

# Create SP
#http://stackoverflow.com/a/26620550/1412059
polys <- SpatialPolygons(mapply(function(poly, id) {
  xy <- matrix(poly, ncol=2, byrow=TRUE)
  Polygons(list(Polygon(xy)), ID=id)
}, split(D1, row(D1)), ID))

# Create SPDF
polys.df <- SpatialPolygonsDataFrame(polys, data.frame(id=ID, row.names=ID))

plot(polys.df, col=rainbow(50, alpha=0.5))

然后您可以测试它们是否重叠:

gOverlaps(polys.df, byid = TRUE)
#      rect1 rect2 rect3 rect4 rect5
#rect1 FALSE FALSE FALSE FALSE FALSE
#rect2 FALSE FALSE  TRUE FALSE FALSE
#rect3 FALSE  TRUE FALSE FALSE FALSE
#rect4 FALSE FALSE FALSE FALSE FALSE
#rect5 FALSE FALSE FALSE FALSE FALSE

例如,rect2rect3 重叠,您尚未确定:

plot(polys.df[2:3,], col=rainbow(50, alpha=0.5))

或者它们是否相互包含?

gContains(polys.df, byid = TRUE)

rect2包含rect1

plot(polys.df[1:2,], col=rainbow(50, alpha=0.5))

等等……

最后你可以这样做:

res <-  gOverlaps(polys.df, byid = TRUE) | 
        gContains(polys.df, byid = TRUE) | 
        gWithin(polys.df, byid = TRUE) | 
        gCovers(polys.df, byid = TRUE)
diag(res) <- 0

#      rect1 rect2 rect3 rect4 rect5
#rect1     0     1     0     0     0
#rect2     1     0     1     0     0
#rect3     0     1     0     0     1
#rect4     0     0     0     0     0
#rect5     0     0     1     0     0

当然,只测试两个条件就足够了,然后在对角线处镜像矩阵(或者甚至保留三角稀疏矩阵以节省内存)。

【讨论】:

    【解决方案2】:

    我更喜欢 Roland 的回答,而不是我将在这里给出的回答,原因有很多。

    1. 代码更能说明正在做什么。 (对于您从现在开始的六个月后回到这很重要)
    2. 它更通用,适用于空间对象可以处理的任何形状
    3. 它有很好的工具来说明问题。

    尽管如此,我将提供一种替代解决方案,它可能比空间方法运行得更快。但是,这仅适用于矩形。

    首先,我们将依赖基于this question's answer 的重叠矩形的逻辑测试。然后我们可以编写一个函数来执行这个测试

    intersecting_rects <- function(edge1, edge2){
    #* index 1 is left edge, index 2 is right edge
    #* index 3 is lower edge, index 4 is upper edge
      (edge1[1] < edge2[2] && edge1[2] > edge2[1] &&
        edge1[3] < edge2[4] && edge1[4] > edge2[3])
    }
    

    我们本可以将其直接包含在 for 循环中,但我发现这样更容易阅读。

    接下来,让我们计算矩阵E。然而,在这种情况下,我们只会在j 上运行循环,其中j 大于i

    n <- 5
    D <- matrix(c(1,2,1,2,1,3,1,3,2,4,2,4,3,5,1,2,3,4,2,4),nrow=4,ncol=n)
    
    E<-mat.or.vec(nr=n,nc=n)
    for (i in 1:n){
      for (j in (i:n)[-1]){
        E[i, j] <- as.numeric(intersecting_rects(D[, i], D[, j]))
      }
    }
    
    E[lower.tri(E)] <- t(E)[lower.tri(E)]
    E
    

    还不错!但与罗兰的回答相比如何呢?让我们全部设置并运行一个粗略的比较(主要警告:这个比较是在一组非常小的矩形上进行的,我不认为这会很好地缩放,但如果你可以在你的 100 个矩形上运行它,它应该给出你很清楚什么是最快的)

    library(sp)
    library(rgeos)
    library(microbenchmark)
    
    #* Rectangles Comparison Function
    intersecting_rects <- function(edge1, edge2){
      (edge1[1] < edge2[2] && edge1[2] > edge2[1] &&
        edge1[3] < edge2[4] && edge1[4] > edge2[3])
    }
    
    n <- 5
    D <- matrix(c(1,2,1,2,1,3,1,3,2,4,2,4,3,5,1,2,3,4,2,4),nrow=4,ncol=n)
    
    
    #* Set up the Spatial Obects
    D1 <- t(D)
    D1 <- D1[, c(1, 3, 2, 3, 2, 4, 1, 4)]
    ID <- paste0('rect', seq_len(nrow(D1)))
    
        # Create SP
        #https://stackoverflow.com/a/26620550/1412059
    polys <- SpatialPolygons(mapply(function(poly, id) {
      xy <- matrix(poly, ncol=2, byrow=TRUE)
      Polygons(list(Polygon(xy)), ID=id)
    }, split(D1, row(D1)), ID))
    
        # Create SPDF
    polys.df <- SpatialPolygonsDataFrame(polys, data.frame(id=ID, row.names=ID))
    
    #* Benchmark the speeds
    microbenchmark(
      poly = {
        res <-  gOverlaps(polys.df, byid = TRUE) | 
                gContains(polys.df, byid = TRUE) | 
                gWithin(polys.df, byid = TRUE) | 
                gCovers(polys.df, byid = TRUE)
        diag(res) <- 0
      },
      full_loop = {
        E <- mat.or.vec(nr=n, nc=n)
        for (i in 1:n){
          for (j in 1:n){
            E[i, j] <- as.numeric(intersecting_rects(D[, i], D[, j]))
          }
        }
      },
      partial_loop = {
        E <- mat.or.vec(nr=n, nc=n)
        for (i in 1:n){
          for (j in (i:n)[-1]){
            E[i, j] <- as.numeric(intersecting_rects(D[, i], D[, j]))
          }
        }
        E[lower.tri(E)] <- t(E)[lower.tri(E)]
      }
    )
    
    Unit: microseconds
             expr      min        lq       mean    median        uq      max neval cld
             poly 2656.800 2720.8745 2812.72787 2767.2080 2811.4875 4200.736   100   b
        full_loop  108.795  116.5650  122.77029  120.8170  127.2690  175.947   100  a 
     partial_loop   69.500   76.3905   87.01193   85.7745   94.1325  166.857   100  a 
    

    因此,如果您真的对速度感兴趣,for 循环可能不是一个糟糕的选择。

    【讨论】:

    • 我猜我的答案对于大量多边形的扩展性要好得多。也许 OP 可以用她的实际数据集进行基准测试。 (可能还有更快的解决方案来创建我在粗略搜索中没有找到的空间多边形。)
    • @Roland,我实际上对此持怀疑态度,但这只是因为gOverlaps 等中的逻辑被推广到 n 边多边形。但我想这需要一个更大的实验来找出答案。
    • 正如本要点 (gist.github.com/nutterb/727755cf2a0568760556) 中所述,我需要一种更好的方式来度过我的星期五早晨。为什么我觉得这些谜题如此有趣?不管怎样,Gist 创建了一个随机的 n 个矩形集合,可以进行比较。只要我的方法正确设置了矩形,我认为这是一个有效的比较。
    • 有趣。但是,如果我们不进行所有 4 次测试,我们可能会改进我的解决方案的时间安排。如果 OP 只需要一个方向的结果,例如三角矩阵,前两个应该就足够了。
    • 嘿,谢谢你们两个,下周当我回去工作时,我将尝试这两种解决方案并在此处发布结果。但我已经可以说我喜欢 Rolands 的回答,它给了我一个很好的情节来检查我的计算是否有意义。
    猜你喜欢
    • 1970-01-01
    • 2017-08-16
    • 2013-10-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多