r - 计算成对差异的有效实现

标签 r

假设我有一个数据框如下:

> foo = data.frame(x = 1:9, id = c(1, 1, 2, 2, 2, 3, 3, 3, 3))
> foo
  x id
1 1  1
2 2  1
3 3  2
4 4  2
5 5  2
6 6  3
7 7  3
8 8  3
9 9  3

我想要一个非常有效的 h(a, b) 实现,它计算属于同一 id 类的 xi、xj 的所有 (a - xi)*(b - xj) 之和。例如我当前的实现是

h(a, b, foo){
  a.diff = a - foo$x
  b.diff = b - foo$x
  prod = a.diff%*%t(b.diff)
  id.indicator = as.matrix(ifelse(dist(foo$id, diag = T, upper = T),0,1)) + diag(nrow(foo))
  return(sum(prod*id.indicator))
}

例如,对于 (a, b) = (0, 1),以下是函数中每个步骤的输出

> a.diff
[1] -1 -2 -3 -4 -5 -6 -7 -8 -9
> b.diff
[1]  0 -1 -2 -3 -4 -5 -6 -7 -8
> prod
      [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9]
 [1,]    0    1    2    3    4    5    6    7    8
 [2,]    0    2    4    6    8   10   12   14   16
 [3,]    0    3    6    9   12   15   18   21   24
 [4,]    0    4    8   12   16   20   24   28   32
 [5,]    0    5   10   15   20   25   30   35   40
 [6,]    0    6   12   18   24   30   36   42   48
 [7,]    0    7   14   21   28   35   42   49   56
 [8,]    0    8   16   24   32   40   48   56   64
 [9,]    0    9   18   27   36   45   54   63   72
> id.indicator
  1 2 3 4 5 6 7 8 9
1 1 1 0 0 0 0 0 0 0
2 1 1 0 0 0 0 0 0 0
3 0 0 1 1 1 0 0 0 0
4 0 0 1 1 1 0 0 0 0
5 0 0 1 1 1 0 0 0 0
6 0 0 0 0 0 1 1 1 1
7 0 0 0 0 0 1 1 1 1
8 0 0 0 0 0 1 1 1 1
9 0 0 0 0 0 1 1 1 1

实际上,最多可以有 1000 个 id 簇,每个簇至少有 40 个,由于 id.indicator 中的条目稀疏以及 prod 中非 block 对角线上的额外计算,使得该方法效率太低不会被使用。

最佳答案

我玩了一局。首先,您的实现:

foo = data.frame(x = 1:9, id = c(1, 1, 2, 2, 2, 3, 3, 3, 3))

h <- function(a, b, foo){
  a.diff = a - foo$x
  b.diff = b - foo$x
  prod = a.diff%*%t(b.diff)
  id.indicator = as.matrix(ifelse(dist(foo$id, diag = T, upper = T),0,1)) + 
     diag(nrow(foo))
  return(sum(prod*id.indicator))
}

h(a = 1, b = 0, foo = foo)
#[1] 891

接下来,我尝试使用适当的稀疏矩阵实现(通过 Matrix 包)和索引矩阵函数的变体。我还使用 tcrossprod,我经常发现它比 a %*% t(b) 快一点。

library("Matrix")

h2 <- function(a, b, foo) {
  a.diff <- a - foo$x
  b.diff <- b - foo$x
  prod <- tcrossprod(a.diff, b.diff) # the same as a.diff%*%t(b.diff)
  id.indicator <- do.call(bdiag, lapply(table(foo$id), function(n) matrix(1,n,n)))
  return(sum(prod*id.indicator))
}

h2(a = 1, b = 0, foo = foo)
#[1] 891

请注意,此函数依赖于 foo$id 进行排序。

最后,我尝试避免创建完整的 n × n 矩阵。

h3 <- function(a, b, foo) {
  a.diff <- a - foo$x
  b.diff <- b - foo$x
  ids <- unique(foo$id)
  res <- 0
  for (i in seq_along(ids)) {
    indx <- which(foo$id == ids[i])
    res <- res + sum(tcrossprod(a.diff[indx], b.diff[indx]))
  }
  return(res)
}

h3(a = 1, b = 0, foo = foo)
#[1] 891

对您的示例进行基准测试:

library("microbenchmark")
microbenchmark(h(a = 1, b = 0, foo = foo), 
               h2(a = 1, b = 0, foo = foo),
               h3(a = 1, b = 0, foo = foo))
# Unit: microseconds
#                        expr      min        lq      mean    median        uq       max neval
#  h(a = 1, b = 0, foo = foo)  248.569  261.9530  493.2326  279.3530  298.2825 21267.890   100
# h2(a = 1, b = 0, foo = foo) 4793.546 4893.3550 5244.7925 5051.2915 5386.2855  8375.607   100
# h3(a = 1, b = 0, foo = foo)  213.386  227.1535  243.1576  234.6105  248.3775   334.612   100

现在,在这个示例中,h3 是最快的,h2 非常慢。但我想对于更大的例子来说,两者都会更快。不过,对于较大的示例,h3 可能仍会获胜。虽然还有大量优化空间,但 h3 应该更快、内存效率更高。因此,我认为您应该选择 h3 的变体,它不会创建不必要的大矩阵。

关于r - 计算成对差异的有效实现,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42495354/

相关文章:

r - 选择具有特定条件的子数据集,而不使用应用和子集函数

c++ - 在 Windows 10 中将 GSL 库链接到 RcppGSL

r - XTS to.weekly 返回不同的每周端点

r - 只有 .R 文件中的源函数

r - 在 ggplot 箱线图中显示平均值

r - 猫和打印有什么区别?

r - ggvis:当 x 变量是分类变量时,如何绘制 abline?

r - 使用 cast () 后 R 中出现意外的数字常量

r:根据每一列绘制每一列

用 apply 替换 for 循环以提高性能(使用 weighted.mean)