首页 > 解决方案 > 如果 case_when 找到某个结果,则停止执行

问题描述

这里

x <- 1:50
case_when(
  x %% 35 == 0 ~ "fizz buzz",
  x %% 5 == 0 ~ "fizz",
  x %% 7 == 0 ~ "buzz",
  TRUE ~ as.character(x)
)

一旦 x 可被 6 整除,如何使用stop()停止执行?我不想要多条错误消息,只想要一条。

标签: rerror-handlingcase-when

解决方案


如果您查看 的内部结构case_when,您会发现它是按照向量的顺序执行每个向量,而不是按照数据的顺序执行。也就是说,在您的示例中,您的第一个表达式在函数甚至查看您的第二个表达式之前x %% 35 == 0计算整个长度。它评估所有表达式 (LHS),然后才开始检查匹配项。因此,您的数据评估不会短路。xx %% 5 == 0

如果您不担心过度计算而只想​​截断数据,那么

x <- 1:50
ret <- case_when(
  x %% 35 == 0 ~ "fizz buzz",
  x %% 5 == 0 ~ "fizz",
  x %% 7 == 0 ~ "buzz",
  TRUE ~ as.character(x)
)
ret[!cumany(x %% 6 == 0)]
# [1] "1"    "2"    "3"    "4"    "fizz"

如果您想/需要留在case_when通话中,那么也许

x <- 1:50
ret <- case_when(
  cumany(x %% 6 == 0) ~ NA_character_,
  x %% 35 == 0 ~ "fizz buzz",
  x %% 5 == 0 ~ "fizz",
  x %% 7 == 0 ~ "buzz",
  TRUE ~ as.character(x)
)
ret
#  [1] "1"    "2"    "3"    "4"    "fizz" NA     NA     NA     NA     NA     NA     NA     NA     NA     NA     NA     NA    
# [18] NA     NA     NA     NA     NA     NA     NA     NA     NA     NA     NA     NA     NA     NA     NA     NA     NA    
# [35] NA     NA     NA     NA     NA     NA     NA     NA     NA     NA     NA     NA     NA     NA     NA     NA    

## filtered
na.omit(ret)
# [1] "1"    "2"    "3"    "4"    "fizz"
# attr(,"na.action")
#  [1]  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
# [40] 45 46 47 48 49 50
# attr(,"class")
# [1] "omit"

## or more succinctly
ret[!is.na(ret)]
# [1] "1"    "2"    "3"    "4"    "fizz"

然后你可以na.omit或类似的。但同样,这不是中断或短路,因此绝对不会节省执行时间。(并且没有能力提供一个无限大的向量,假设它会在%% 6找到其中一个条件时自行中断。)

为了短路 LHS 的评估,您需要打破矢量化评估case_when并自己做所有事情。不幸的是,有一个“问题”:case_when保持通用性的方式是在调用环境的上下文中评估每个 LHS 表达式(和 RHS,就此而言)。例如,它x %% 35 == 0在父框架(即调用 的环境case_when)中进行评估。该case_when函数不知道返回的向量会先验多长时间,也不知道如何正确索引(逐步执行)所有存在的变量。例如,x %% 35 == 0我们想先检查x[1] %% 35 == 0,然后x[2] %% 35 == 0,等等,这似乎很直观……但是在(x+y) %% 35 == 0哪里呢?y可能是相同长度的向量、可整除长度的回收向量或单个值,这(x[50]+y[50]) %% 35 == 0将导致NA(或索引错误)。

这是一种环绕的方法case_when,仅在找到约束之前进行评估,然后停止。

x <- 1:50
ret <- numeric(length(x))
for (i in seq_along(x)) {
  # constraint
  if (x[i] %% 6 == 0) break
  # regular piece-wise execution
  ret[i] <- case_when(
    x[i] %% 35 == 0 ~ "fizz buzz",
    x[i] %% 5 == 0 ~ "fizz",
    x[i] %% 7 == 0 ~ "buzz",
    TRUE ~ as.character(x[i])
  )
}
ret <- head(ret, i - 1)
ret
# [1] "1"    "2"    "3"    "4"    "fizz"

此循环迭代 6 次,其中前 5 次正常执行,在第 6 次迭代时发现x[i] %% 6 == 0为真,并且breaks.


如果我不解决这些方法的相对(低)效率问题,我将失职。

x <- 1:50
microbenchmark::microbenchmark(
  one = {
    ret <- case_when(
      x %% 35 == 0 ~ "fizz buzz",
      x %% 5 == 0 ~ "fizz",
      x %% 7 == 0 ~ "buzz",
      TRUE ~ as.character(x)
    )
    ret[!cumany(x %% 6 == 0)]
  },
  two = {
    ret <- case_when(
      cumany(x %% 6 == 0) ~ NA_character_,
      x %% 35 == 0 ~ "fizz buzz",
      x %% 5 == 0 ~ "fizz",
      x %% 7 == 0 ~ "buzz",
      TRUE ~ as.character(x)
    )
    ret[!is.na(ret)]
  },
  three = {
    ret <- numeric(length(x))
    for (i in seq_along(x)) {
      # constraint
      if (x[i] %% 6 == 0) break
      # regular piece-wise execution
      ret[i] <- case_when(
        x[i] %% 35 == 0 ~ "fizz buzz",
        x[i] %% 5 == 0 ~ "fizz",
        x[i] %% 7 == 0 ~ "buzz",
        TRUE ~ as.character(x[i])
      )
    }
    ret <- head(ret, i - 1)
  }
)
# Unit: microseconds
#   expr    min      lq     mean  median      uq     max neval
#    one  136.6  143.55  168.975  152.60  167.55   478.3   100
#    two  156.9  171.10  199.213  180.05  206.80   427.3   100
#  three 4772.7 5336.75 5854.889 5605.25 6073.20 12001.8   100

这里应该清楚的是,使用这组表达式(一些模运算),在 R 中计算比我们需要的更多的效率要高得多,但使用矢量化方法比试图限制x我们处理的数量要高得多。

如果你很好奇,这种方法在500K 长third时仍然没有效率......x

x <- 1:500
# Unit: microseconds
#   expr    min     lq     mean  median      uq     max neval
#    one  216.9  245.5  287.715  261.55  289.20   601.4   100
#    two  220.9  260.8  300.539  277.75  295.75   691.5   100
#  three 5578.7 6164.9 6802.093 6531.20 6884.25 13667.9   100
x <- 1:5000
# Unit: microseconds
#   expr    min      lq     mean  median      uq      max neval
#    one 1468.2 1644.50 3809.862 1708.65 1879.90 196632.1   100
#    two  780.9  852.25  986.799  889.90  952.45   6761.6   100
#  three 8061.9 8785.15 9836.741 9249.85 9803.70  17088.5   100
x <- 1:50000
# Unit: milliseconds
#   expr     min       lq     mean   median       uq      max neval
#    one 15.9505 20.33195 26.18902 22.60755 26.75880 230.6372   100
#    two  6.8114  8.33300 12.92443  8.95825 14.18375 236.4153   100
#  three 34.6127 43.44130 48.28222 47.23290 53.26485  71.2169   100
x <- 1:500000
# Unit: milliseconds
#   expr      min       lq     mean   median       uq      max neval
#    one 201.1099 220.5286 278.7940 238.9214 280.8388 548.7299   100
#    two  82.8113 104.9474 139.0557 118.3804 136.0794 380.3658   100
#  three 295.7582 310.8903 335.8939 322.4250 349.4466 567.1915   100

但最终达到了 5M 的平价:

x <- 1:5000000
# Unit: seconds
#   expr      min       lq     mean   median       uq      max neval
#    one 2.713632 2.794410 3.371636 3.175023 3.820303 4.682576    10
#    two 1.105257 1.278336 1.535301 1.371372 1.854551 2.281774    10
#  three 3.082974 3.116061 3.292641 3.314118 3.476838 3.513049    10

(这将随着计算成本的变化而显着变化。)


推荐阅读