R中的ifelse()对于确定在大向量上调用哪个函数是否有效?

R中的ifelse()对于确定在大向量上调用哪个函数是否有效?,r,performance,if-statement,R,Performance,If Statement,我目前正在编写一个调用特定函数的代码,具体取决于向量中元素的值。那么,我的问题是这是否有效。如果我正确理解了ifelse算法,那么我作为函数的第二个和第三个参数输入的任何值都将全部计算,然后根据我的条件的TRUE或FALSE值进行子集设置。这与我们在编码中看到的典型的if/else结构形成对比,在这种结构中,我们会评估一个条件,然后在元素上运行一个函数,直到我们知道要运行哪个函数。为了验证这一点,我尝试使用以下方法: test1 <- function() { x <- samp

我目前正在编写一个调用特定函数的代码,具体取决于向量中元素的值。那么,我的问题是这是否有效。如果我正确理解了
ifelse
算法,那么我作为函数的第二个和第三个参数输入的任何值都将全部计算,然后根据我的条件的
TRUE
FALSE
值进行子集设置。这与我们在编码中看到的典型的
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

test1您的编码方式比
ifelse
更糟糕,但正如
的警告部分所建议的那样,ifelse
可能做得更好。使用简单的函数,
x^2
x/2
,下面的
test3()
函数速度更快—大约是
ifelse
的2到3倍,比
test2()快30倍。如果函数的计算量更大(但仍然是矢量化的!),边界可能会更大

速度增加(我认为)主要是由于两个原因:

  • ifelse
    执行
    test3()
    跳过的输入检查和错误处理
    ifelse
    更通用、更灵活
    test3()
    硬编码为仅返回一个
    数值(矢量)
  • 如中所示,
    ifelse
    将计算其整个
    TRUE
    响应向量,前提是至少有1个
    TRUE
    测试值,同样地,其
    FALSE
    也会计算
    test3()
    通过创建
    TRUE
    FALSE
    子向量绕过额外的计算

  • 我修改了您的
    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  
    
    set.seed(47)
    
    x您的编码方式比
    ifelse
    更糟糕,但正如
    的警告部分所建议的那样,ifelse
    可能做得更好。使用简单的函数,
    x^2
    x/2
    ,下面的
    test3()
    函数速度更快—大约是
    ifelse
    的2到3倍,比
    test2()快30倍。如果函数的计算量更大(但仍然是矢量化的!),边界可能会更大

    速度增加(我认为)主要是由于两个原因:

  • ifelse
    执行
    test3()
    跳过的输入检查和错误处理
    ifelse
    更通用、更灵活
    test3()
    硬编码为仅返回一个
    数值(矢量)
  • 如中所示,
    ifelse
    将计算其整个
    TRUE
    响应向量,前提是至少有1个
    TRUE
    测试值,同样地,其
    FALSE
    也会计算
    test3()
    通过创建
    TRUE
    FALSE
    子向量绕过额外的计算

  • 我修改了您的
    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  
    
    set.seed(47)
    
    x
    ifelse
    有一大堆输入检查等,而您的
    test2
    函数没有这些检查。这可能是纳秒级差异的主要原因。
    ifelse
    的核心本质上是一个
    if/else
    ,与您对
    NA
    值的调整相同。您的
    test2
    中有一个错误;试着自己运行它。另外,您的基准测试命令应该调用
    test1()
    test2()
    ,否则它实际上不会运行代码。如果您确实想看到性能差异,请预先模拟
    x
    ,并将其传递给您的函数。@aaron很好的调用!我将更新问题以反映这一点。另请参阅帮助文件中
    警告
    部分下的建议,其中给出了一个可能首选的构造类型的示例。
    ifelse
    有一整套检查输入等功能,而
    test2
    函数没有这些功能。这可能是纳秒级差异的主要原因。
    ifelse
    的核心本质上是一个
    if/else
    ,与您对
    NA
    值的调整相同。您的
    test2
    中有一个错误;试着自己运行它。另外,您的基准测试命令应该调用
    test1()
    test2()
    ,否则它实际上不会运行代码。如果您确实想看到性能差异,请预先模拟
    x
    ,并将其传递给您的函数。@aaron很好的调用!我将更新问题以反映这一点。另外,请参阅帮助文件中“警告”部分下的建议,其中给出了一个可能首选的构造类型示例。答案似乎是
    ifelse
    if/else
    快得多,但代码通常可以以比ifelse更快的方式重写,这对我来说完全有意义。谢谢似乎答案是
    ifelse
    if/else
    快得多,但代码通常可以以比
    ifelse
    更快的方式重写,这对我来说完全有意义。谢谢