【问题标题】:fast way in R to do two nested for loops [duplicate]R中的快速方法来执行两个嵌套的for循环[重复]
【发布时间】:2017-12-18 01:35:18
【问题描述】:

我需要取两个向量的任意两个元素之间的差。 如果A<-c(1,2)B<-c(3,4) 那么我的结果R 应该是c(3-1,3-2,4-1,4-2)

有了这个sn-p

myfunction <- function(N)
{
  A = runif(N)
  B = runif(N)
  R = c()
  for(a in A){
    for(b in B){
      R=c(b-a,R)
    }
  }
  R
}
print(system.time(result <- myfunction(300)))

这次我明白了

   user  system elapsed 
  14.27    0.01   14.39 

有没有更快的方法?

【问题讨论】:

  • outer(A, B, -) ?见here,引号弄乱了。
  • 不错@zx8754,没想到baseR解决方案会这么快!
  • @zx8754 太晚了,我敲了它:-) 这实际上是完全相同的问题,减法等等。
  • @zx8754 谢谢你的复制。我永远找不到它!
  • 感谢 Google。但仍然是一个很好的问题和努力。

标签: r for-loop optimization


【解决方案1】:

最快的基础解决方案是使用outer

as.vector(outer(B,A,"-"))

令我惊讶的是,map2_dbl 实际上比 outer 快​​很多:

不出所料,map2_dbl 似乎更快,但那是因为它没有计算 A 和 B 中值的每个组合

      test elapsed relative
3 CP(A, B)    7.54   47.125 # using expand.grid
2 JL(A, B)    0.16    1.000 # using map2_dbl
1 JM(A, B)    3.13   19.563 # using outer

但是:

> A <- 1:3
> B <- 3:1
> JL(A,B)
[1] -2  0  2
> JM(A,B)
[1]  2  1  0  1  0 -1  0 -1 -2

这适用于长度为 1000 的两个向量,并且有 100 次重复。我没有包含您自己的解决方案,因为该解决方案非常慢,原因有两个:

  • R 中的for 循环比过去快了很多,但仍然不如使用循环编码为C 或等效的函数那样最佳。此处测试代码中使用的函数就是这种情况。
  • 你“增长”了你的结果对象。代码的每一次循环,R 都会变大一个值,因此 R 必须在内存中寻找新的位置来存储它。这实际上是代码中最大的瓶颈。不惜一切代价尽量避免这种构造,因为它是导致代码极其缓慢的最重要原因之一。

基准代码:

library(tidyverse)

JM <- function(A,B){
  as.vector(outer(B,A,"-"))
}

JL <- function(A,B){
  map2_dbl(.x = A, 
           .y = B, 
           .f = ~ c(.x - .y))
}

CP <- function(A,B){
  as.data.frame(expand.grid(A,B)) %>%
    mutate(Var3 = Var2-Var1)
}

library(rbenchmark)

A <- runif(1000)
B <- runif(1000)

benchmark(JM(A,B),
          JL(A,B),
          CP(A,B),
          replications = 100,
          columns = c("test","elapsed","relative"))

【讨论】:

  • 我刚才试了N = 100000,此时outer在我的机器上占用了太多内存(Error: cannot allocate vector of size 74.5 Gb)。 map2_dbl 在 0.14 秒内仍然可以正常工作。
  • @zx8754 你有代码,如果你对此感兴趣,可以使用你自己的 CPU。我也想通了为什么map2_dbl 这么快:因为它不是解决问题的方法:-)
  • 啊,为什么map_dbl 更快是有道理的。很高兴知道outer 解决方案。
  • @JorisMeys +1 谢谢你的建议
【解决方案2】:

您可以使用expand.grid 来向量化该方法:

A <- runif(300)
B <- runif(300)

library(dplyr)
R <- as.data.frame(expand.grid(A,B)) %>%
       mutate(Var3 = Var2-Var1)  

前5行输出:

      Var1     Var2          Var3
1 0.8516676 0.325261 -0.5264066246
2 0.2126453 0.325261  0.1126156694
3 0.5394620 0.325261 -0.2142010126
4 0.1364876 0.325261  0.1887734290
5 0.3248651 0.325261  0.0003958747 

这需要:

user  system elapsed 
0.02    0.00    0.02

你的函数占用了:

 user  system elapsed 
42.39    0.43   42.90    

【讨论】:

  • purrr::map2_dbl 更快。并不是说它对 300 个值很重要,但我假设 OP 正在寻求扩大规模。
  • 嗯...好的。首先,您应该表明您测试了性能差异(也将其与我的方法进行比较)。其次,我知道purrr:map 更快,因为我实际测试了我的方法。第三,我给了你目前唯一的赞成票。第四,我们是同时发的,这也是我的帖子存在的原因。第五,网点不是真实的,你不能用它们买任何东西。
  • 我的评论似乎让你很不高兴。这不是我的本意,抱歉。我喜欢认为我只是对找到问题的最佳解决方案感兴趣。我应该说得更清楚。
  • 下次只需编辑您的原始答案。关注@Joris Meys 示例
【解决方案3】:

使用purrr::map2

library(tidyverse)

N = 300
A = runif(N)
B = runif(N)
R = c()

print(
  system.time(
    result <- map(
      .x = A,
      .f = ~ c(.x - B)) %>% unlist
  )
)

花费时间:

 user  system elapsed 
 0.02       0    0.02 

如果我现在引起了您的注意,请查看 this repo 以了解 purrr 的精彩介绍。

【讨论】:

  • 其他选项包括apply函数族,但我喜欢purrr::map,因为我认为它更直观一点
  • 由于这是关于速度的,而其他答案也提供了这一点,您介意添加一些与 OP 相比运行此代码所需的实际时间吗?
  • 显然这样更快。它不会计算所有组合,因此它没有按照 OP 的要求进行操作。试试A &lt;- 1:10B &lt;- 10:1 你会看到的。
  • 哈哈,你是对的@Joris Meys,对此感到抱歉!
  • @JanLauGe 嗯...现在看起来好像您在答案中复制粘贴了 Chi Pak 的代码。老实说,我看不出map 在这里是一个明智的解决方案。这是一个很棒的功能,但用途完全不同。
猜你喜欢
  • 1970-01-01
  • 2017-06-28
  • 1970-01-01
  • 2021-09-15
  • 1970-01-01
  • 2021-02-27
  • 2012-05-08
  • 2021-02-04
  • 2020-05-20
相关资源
最近更新 更多