【问题标题】:Is ifelse() in R efficient for determining which function to call on a large vector?R 中的 ifelse() 是否可以有效地确定在大向量上调用哪个函数?
【发布时间】:2016-02-11 11:27:51
【问题描述】:

我目前正在编写一个调用特定函数的代码,具体取决于向量中元素的值。那么,我的问题是这是否有效。如果我正确理解了ifelse 算法,那么我作为函数的第二个和第三个参数输入的任何值都会被完整计算,然后根据我的条件的TRUEFALSE 值进行子集化。这与我们在编码中看到的典型if/else 结构形成对比,在这种结构中,我们会评估一个条件,然后只有在我们知道要运行哪个函数时才在元素上运行一个函数。为了测试这一点,我尝试使用以下内容:

test1 <- function() {
  x <- sample(1:1e9, 1e6, replace = TRUE)
  y <- ifelse(x %% 2 == 0, x**2, x/2)
  return(y)
}

test2 <- function() {
  x <- sample(1:1e9, 1e6, replace = TRUE)
  y <- numeric(length(x))
  for (i in 1:length(x)) {
    if (x[i] %% 2 == 0) {
      y[i] <- x[i]**2
    } else {
      y[i] <- x[i]/2
    }
  }
  return(y)
}
microbenchmark::microbenchmark(test1(), test2(), times = 1000)

Unit: milliseconds
    expr       min        lq     mean    median        uq      max neval
 test1()  2.366067  2.494746  8.27343  2.580164  2.706826 1690.049  1000
 test2() 21.773385 23.050818 29.70450 23.712907 29.468783 3169.008  1000

平均值似乎表明ifelse 方法优于if/else

我问的原因是因为我将要解析相对较大的 XML 文件,并且我实现的解析方法将根据树中子项的布局而有所不同,我试图成为尽可能高效。

所以有两个问题:1) 我上面的结论是否正确,ifelseif/else 快,2) ifelse 是否计算 yesno 向量的所有值,然后对它们进行子集化?

提前致谢。

编辑

上面的代码以及一些问题文本已被修改以反映下面的 cmets。

【问题讨论】:

  • ifelse 有一大堆检查输入等,你的test2 函数没有。这可能解释了大部分纳秒的差异。 ifelse 的核心本质上是一个 if/else,与您拥有的相同 + 对 NA 值的一些调整。
  • 您的test2 有错误;尝试自己运行它。此外,您的基准测试命令应该调用test1()test2(),否则它实际上并没有运行代码。
  • 如果你真的想看看性能差异,预先模拟x并将它传递给你的函数。
  • @aaron 好电话!我会更新问题以反映这一点。
  • 另请参阅帮助文件中 Warning 部分下的建议,其中提供了可能首选的构造类型示例。

标签: r performance if-statement


【解决方案1】:

您的编码方式比ifelse 更糟糕,但正如?ifelse警告 部分中所建议的,它可能会做得更好。使用您的简单函数 x^2x / 2,下面的 test3() 函数更快 - 比 ifelse 快 2 到 3 倍,比 test2() 快 30 倍。使用更多计算密集型函数(但仍然是矢量化的!),余量可能会更大。

速度增益(我认为)主要是由于两个来源:

  1. ifelse 执行 test3() 跳过的输入检查和错误处理。 ifelse 更通用、更灵活...test3() 被硬编码为仅返回 numeric 向量。
  2. Does ifelse really calculate both of its vectors every time? Is it slow? 所示,只要测试的TRUE 值至少有1 个,ifelse 将计算其整个TRUE 响应向量,对于FALSE 也是如此。 test3() 通过创建 TRUEFALSE 子向量来绕过额外的计算。

我已经修改了您的 test1()test2() 以简化一点,去掉了数据模拟(因为这不是我们想要测试的)。我添加了使用逻辑子集的test3。我还大大减小了测试向量的大小,因此它运行得相当快。

set.seed(47)
x <- sample(1:1e6, 1e4, replace = TRUE)

test1 <- function(x) {
  ifelse(x %% 2 == 0, x**2, x/2)
}

test2 <- function(x) {
  y <- numeric(length(x))
  for (i in seq_along(x)) {
    if (x[i] %% 2 == 0) {
      y[i] <- x[i]**2
    } else {
      y[i] <- x[i]/2
    }
  }
  return(y)
}

test3 <- function(x) {
    y = numeric(length(x))
    cond = x %% 2 == 0
    y[cond] = x[cond] ^ 2
    y[!cond] = x[!cond] / 2
    return(y)
}

identical(test1(x), test2(x))
# TRUE
identical(test1(x), test3(x))
# TRUE
microbenchmark::microbenchmark(test1(x), test2(x), test3(x), times = 1000)
# Unit: microseconds
#      expr       min         lq       mean     median        uq        max neval cld
#  test1(x)  1563.270  1642.3540  1701.3877  1669.2180  1697.894   3159.743  1000  b 
#  test2(x) 17909.833 18788.9635 23682.1516 19882.8600 20679.436 116206.536  1000   c
#  test3(x)   627.241   668.7445   691.8433   680.6675   696.061   1340.507  1000 a  

【讨论】:

  • 似乎答案是 ifelseif/else 快得多,但代码通常可以以比 ifelse 更快的方式重写,这完全有道理我。谢谢!
猜你喜欢
  • 2021-08-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-04
  • 1970-01-01
  • 1970-01-01
  • 2021-07-01
相关资源
最近更新 更多