R ifelse in dplyr across 评估条件,它不应该

R ifelse in dplyr across evaluating condition it's not supposed to

提问人:abalter 提问时间:10/25/2023 最后编辑:Markabalter 更新时间:10/25/2023 访问量:113

问:

更新:

我修复了我的reprex。

源语言:

我有一个语句作为函数。即使在条件为真的情况下,我在尝试计算跌穿值时也遇到错误。ifelseacross

library(tidyverse)

tibble(x = 1:3) %>%
  mutate(y = across(x, ~ifelse(.x==1, 1, days_in_month(.x-1))))
#> Error in `mutate()`:
#> ℹ In argument: `y = across(x, ~ifelse(.x == 1, 1, days_in_month(.x -
#>   1)))`.
#> Caused by error in `across()`:
#> ! Can't compute column `x`.
#> Caused by error in `month.numeric()`:
#> ! Values are not in 1:12
#> Backtrace:
#>      ▆
#>   1. ├─tibble(x = 1:3) %>% ...
#>   2. ├─dplyr::mutate(...)
#>   3. ├─dplyr:::mutate.data.frame(...)
#>   4. │ └─dplyr:::mutate_cols(.data, dplyr_quosures(...), by)
#>   5. │   ├─base::withCallingHandlers(...)
#>   6. │   └─dplyr:::mutate_col(dots[[i]], data, mask, new_columns)
#>   7. │     └─mask$eval_all_mutate(quo)
#>   8. │       └─dplyr (local) eval()
#>   9. ├─dplyr::across(x, ~ifelse(.x == 1, 1, days_in_month(.x - 1)))
#>  10. │ ├─base::withCallingHandlers(...)
#>  11. │ └─fn(col, ...)
#>  12. │   ├─base::ifelse(.x == 1, 1, days_in_month(.x - 1))
#>  13. │   └─lubridate::days_in_month(.x - 1)
#>  14. │     ├─lubridate::month(x, label = TRUE, locale = "C")
#>  15. │     └─lubridate:::month.numeric(x, label = TRUE, locale = "C")
#>  16. │       └─base::stop("Values are not in 1:12")
#>  17. └─base::.handleSimpleError(...)
#>  18.   └─dplyr (local) h(simpleError(msg, call))
#>  19.     └─rlang::abort(bullets, call = error_call, parent = cnd)

tibble(x = 1:3) %>%
  rowwise() %>%
  mutate(y = ifelse(x==1, 1, days_in_month(x-1)))
#> # A tibble: 3 × 2
#> # Rowwise:
#>       x     y
#>   <int> <dbl>
#> 1     1     1
#> 2     2    31
#> 3     3    28

创建于 2023-10-24 使用 reprex v2.0.2

R dplyr Tidyverse 润滑剂

评论

1赞 Ian Campbell 10/25/2023
你希望这段代码在做什么?如果你只是在匿名函数内部使用,为什么要使用?你打算使用吗?在此示例中,始终是长度为 3 的整数向量。在下面的示例中,是一个长度为 1 的整数向量。acrossx.xacrossxx
0赞 abalter 10/25/2023
@IanCampbell -- 是的。我的意思是使用.我已经修好了。.x
0赞 abalter 10/25/2023
@neilfws,所以,问题在于不能做我想做的事情。问题在于理解 中的这种奇怪行为。所以我创建了一个简单的例子。across

答:

1赞 Derf 10/25/2023 #1

问题在于它是如何工作的。它计算 and 表达式中的整个向量,然后返回符合 的值,如注释中@RitchieSacramento所述(此处也对此进行了进一步讨论。所以实际上正在被评估,从而导致错误。ifelse()yesnoconditiondays_in_month(0:2)

  • 因此,您可能只想使用基本并对其进行矢量化,而不是 。ifelseif(){}else{}
library(tidyverse)

foo=function(x){
  if(x==1){1}else{days_in_month(x)}
}

foo = Vectorize(foo)

tibble(x = 1:3) %>%
  mutate(y = across(x, ~foo(x)))
  • 另一方面,@RitchieSacramento根据您的代码提出了一个更直接的解决方案,该解决方案将导致错误的部分“0”替换为“1”,这样就不会引发错误。days_in_month
tibble(x = 1:3) %>% 
  mutate(across(x, ~ifelse(.x==1, 1, days_in_month(pmax(1, .x-1)))))

然后,在性能方面,这是统计数据,

library(rbenchmark)
set.seed(2023)
foo=function(x){
    if(x==1) {1}else{ days_in_month(x-1)}
  }
foo = Vectorize(foo)
x = sample(1:13,100,replace=T)
benchmark(
vectorize_if={
  tibble(x ) %>%
    mutate(y = across(x, ~foo(x)))
},
method2={
  tibble(x) %>%   mutate(across(x, ~ifelse(x==1, 1, days_in_month(pmax(1, x-1)))))  
})

          test replications elapsed relative user.self sys.self user.child sys.child
2      method2          100    0.37    1.000      0.28        0         NA        NA
1 vectorize_if          100    0.86    2.324      0.56        0         NA        NA

评论

1赞 lotus 10/25/2023
这与 无关,问题在于,如果任何测试值为 或,则使用完整条件向量计算 和 参数。唯一不计算 or 参数的情况是返回 all 或 all 。OP 的论点被完全评估,因为没有月份抛出错误。mutate()ifelse()yesnoTRUEFALSEyesnotestTRUEFALSEnodays_in_month(0:2)0
1赞 lotus 10/25/2023
矢量化之所以有效,是因为它导致所有测试的长度均为 1,在这种情况下,只评估一个条件。但这是不好的做法,因为已经矢量化了,而你实际上是在去矢量化它,使高效函数的效率大大降低。最好做一些类似的事情来解决错误的根本原因。ifelse()tibble(x = 1:3) %>% mutate(across(x, ~ifelse(.x==1, 1, days_in_month(pmax(1, .x-1)))))
1赞 lotus 10/25/2023
很高兴你改善你的。
1赞 lotus 10/25/2023
只是额外的评论,说对 3 个值的基准测试没有意义 - 你需要在一个更长的向量上进行测试,否则你真正看到的只是函数开销。此外,将函数定义移出基准测试。foo
1赞 Derf 10/25/2023
啊,我明白了。值的长度越大,差异就越大。再次感谢。@RitchieSacramento
0赞 Jon Spring 10/25/2023 #2

如前所述,这不是问题,而是您如何使用 .它计算每个值,但会产生错误。mutateacrossifelsedays_in_month(0)

我们可以修复输入,这样它就不会像函数那样传递无意义的月份值。你可以通过使用一些数学来转换为“上个月”来做到这一点,因此它变成了 ,例如,使用 .那么原来的功能就好了。例如:0days_in_month1:12c(12, 1:11)(.x - 2) %% 12 + 1

days_in_month((1:12 - 2) %% 12 + 1)

#Dec Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov 
# 31  31  28  31  30  31  30  31  31  30  31  30

所以:

tibble(x = 1:3) %>%
  mutate(across(x, ~ifelse(
    .x==1, 1, days_in_month((.x - 2) %% 12 + 1)),
    .names = "{.col}_y"))

# A tibble: 3 × 2
      x   x_y
  <int> <dbl>
1     1     1
2     2    31
3     3    28