如何在 R 中对这段代码进行矢量化?也许使用 apply() 函数?

数据挖掘 机器学习 r 编程
2022-03-14 19:25:43

在不使用 1 或 2 个 for 循环的情况下,我真的很难在 R 代码中复制 dist() 函数的输出。(如果您想知道我为什么要这样做,这是为了让我可以玩距离计算,并提高我的 R 技能 - 所以请只使用涉及 R 的解决方案!)

概述:矩阵被传递给 dist(),它逐行计算欧几里得距离并输出每行之间距离的完整距离矩阵(例如,第 1 行和第 50 行之间的距离将在 distancematrix[1, 50] 和 distancematrix[ 50, 1])。快速代码如下所示:

distancematrix <- as.matrix(dist(myMatrix, method="euclidean", diag = T, upper = T))

我已经使用以下代码在 R 中成功生成了相同的输出:

for (i in 1:nrow(myMatrix)) {
  for (j in 1:nrow(myMatrix)) {
    distancematrix[i, j] <- sum(abs(myMatrix[i,] - myMatrix[j,])) 
  }
}

但是,使用两个嵌套的 for 循环比使用 dist() 慢得多。我已经阅读了很多关于使用 apply() 来优化更慢的 for 循环的内容,但到目前为止我还没有完全理解它。我相信至少可以通过输出一个向量并在最后处理它来避免至少一个 for 循环。但是,我终其一生都无法弄清楚如何删除两个 for 循环。

有人有想法吗?

1个回答

首先应该注意的是,您发布的代码实际上并没有复制dist函数的输出,因为该行:

distancematrix[i, j] <- sum(abs(myMatrix[i,] - myMatrix[j,]))

不计算欧几里得距离;它应该是:

distancematrix[i, j] <- sqrt(sum((myMatrix[i,] - myMatrix[j,]) ^ 2))

这里有两个解决方案依赖apply. 它们被简化了,特别是没有利用距离矩阵的对称性(如果考虑的话,这将导致 2 倍的加速)。首先,生成一些测试数据:

# Number of data points
N <- 2000
# Dimensionality
d <- 10
# Generate data
myMatrix = matrix(rnorm(N * d), nrow = N)

为方便起见,定义:

# Wrapper for the distance function
d_fun <- function(x_1, x_2) sqrt(sum((x_1 - x_2) ^ 2))

第一种方法是apply和的组合sapply

system.time(
    D_1 <- 
        apply(myMatrix, 1, function(x_i) 
            sapply(1:nrow(myMatrix), function(j) d_fun(x_i, myMatrix[j, ]))
    )
)

   user  system elapsed 
 14.041   0.100  14.001 

而第二个仅使用apply(但遍历使用 配对的索引expand.grid):

system.time(
    D_2 <- 
        matrix(apply(expand.grid(i = 1:nrow(myMatrix), j = 1:nrow(myMatrix)), 1, function(I) 
            d_fun(myMatrix[I[["i"]], ], myMatrix[I[["j"]], ])
        )
    )
)

   user  system elapsed 
 39.313   0.498  39.561 

但是,正如预期的那样,两者都比dist

system.time(
    distancematrix <- as.matrix(
        dist(myMatrix, method = "euclidean", diag = T, upper = T)
    )
)

   user  system elapsed 
  0.337   0.054   0.388