查找大型矩阵行之间的最小距离:分配限制错误

Find lowest distances between rows of a large matrix: Allocation limit error

我想计算一个大矩阵的所有行之间的距离。对于每一行,我需要找到距离最短的另一行。最终输出应该是一个列表,其中包含距离最短的行的 ID(参见下面示例中的 low_dis_ids)。

我能够找到针对小样本的解决方案(如下例)。但是,我无法使用更大的样本执行这些步骤,因为具有距离的矩阵变得太大了。有没有办法省略这么大的矩阵?我只需要带有 ID 的列表(如 low_dis_ids)。

可重现的例子:

set.seed(123)

# Calculation of distances with small samplesize is working well
N <- 100
data_100 <- data.frame(x1 = rnorm(N, 5, 10),
                       x2 = rnorm(N, 5, 10),
                       x3 = rnorm(N, 5, 10),
                       x4 = rnorm(N, 5, 10),
                       x5 = rnorm(N, 5, 10))

# Matrix with all distances (no problem for the smaller samplesize)
dist_100 <- as.matrix(dist(data_100))

# Find the row with the smallest distance
for(i in 1:nrow(dist_100)) {
  dist_100[i, i] <- Inf
}

low_dis <- numeric()
for(i in 1:nrow(dist_100)) {
  low_dis[i] <- as.numeric(sort(dist_100[ , i]))[1]
}

low_dis_ids <- list()
for(i in 1:length(low_dis)) {
  low_dis_ids[[i]] <- as.numeric(names(dist_100[ , i][dist_100[ , i] == low_dis[i]]))
}

# low_dis_ids is the desired output and stores the rows with the smallest distances



# The same procedure is not working for larger samplesizes
N <- 100000
data_100000 <- data.frame(x1 = rnorm(N, 5, 10),
                          x2 = rnorm(N, 5, 10),
                          x3 = rnorm(N, 5, 10),
                          x4 = rnorm(N, 5, 10),
                          x5 = rnorm(N, 5, 10))
dist_100000 <- dist(data_100000)

# Error: cannot allocate vector of size 37.3 Gb

您绝对可以避免因使用 dist 而产生的大型矩阵。一种这样的解决方案是一次计算一行的距离,我们可以编写一个函数,给定整个数据框和一个行 id,找到哪一行对应于最小距离。例如:

f = function(rowid, whole){
  d = colSums((whole[rowid,] - t(whole))^2) # calculate distance
  d[rowid] = Inf # replace the zero
  which.min(d)
}

colSums 函数优化得相当好,因此速度相对较快。我怀疑 which.min 也是一种更快且可能更整洁的循环遍历距离向量的方法。

为了制定一个适用于任何此类数据框的解决方案,我编写了另一个短函数,将其应用于每一行并为您提供行 ID 向量

mindists = function(dat) do.call(c,lapply(1:nrow(dat),f,whole = as.matrix(dat)))

如果您想要列表而不是向量,只需省略 do.call 函数即可。我这样做是为了更容易检查输出是否符合您的预期。

all(do.call(c,low_dis_ids) == mindists(data_100))
[1] TRUE

这也适用于我笔记本电脑上的较大示例。它并不快,因为您正在对 f 进行 nrow(data) 调用,但它确实避免了创建一个大对象。可能有更好的解决方案,但这是第一个浮现在脑海中的有效解决方案。希望对您有所帮助。

编辑:

已编辑,因为 Roland 有一个额外的 C++ 答案 我对较小的数据集进行了快速基准测试。在这种情况下,C++ 答案肯定更快。 如果您是纯粹的 R 程序员(无需学习 C++ 和 RCpp),我认为这个答案的一些额外推销是更容易理解的。使用 lapply 的并行替换,R 版本很容易并行化。我会注意到这并不是要剥夺 Rolands 的回答,我个人喜欢 Rcpp,只是为了为这个问题的任何未来读者提供额外的信息。

使用 Rcpp,因为基础 R 解决方案太慢:

library(Rcpp)
library(inline)
cppFunction(
"  IntegerVector findLowestDist(const NumericMatrix X) {
    const int n = X.nrow();
    const int m = X.ncol();
    IntegerVector minind(n);
    NumericVector minsqdist(n);
    double d;
    for (int i = 0; i < n; ++i) {
      if (i == 0) {
        d = 0;
        for (int k = 0; k < m; ++k) {
          d += pow(X(i, k) - X(1, k), 2.0);

        }
        minsqdist(i) = d;
        minind(i) = 1;
      } else {
        d = 0;
        for (int k = 0; k < m; ++k) {
          d += pow(X(i, k) - X(0, k), 2.0);

        }
        minsqdist(i) = d;
        minind(i) = 0;
      }

      for (int j = 1; j < n; ++j) {
        if (i == j) continue;
        d = 0;
        for (int k = 0; k < m; ++k) {
          d += pow(X(i, k) - X(j, k), 2.0);

        }
        if (d < minsqdist(i)) {
          minsqdist(i) = d;
          minind(i) = j;
        }
      }
    }
    return minind + 1;
  }"
)

all.equal(findLowestDist(as.matrix(data_100)),
          unlist(low_dis_ids))
#[1] TRUE

findLowestDist(as.matrix(data_100000))
#works

算法或许可以改进。