提问人:abalter 提问时间:10/25/2023 最后编辑:Markabalter 更新时间:10/25/2023 访问量:113
R ifelse in dplyr across 评估条件,它不应该
R ifelse in dplyr across evaluating condition it's not supposed to
问:
更新:
我修复了我的reprex。
源语言:
我有一个语句作为函数。即使在条件为真的情况下,我在尝试计算跌穿值时也遇到错误。ifelse
across
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
答:
1赞
Derf
10/25/2023
#1
问题在于它是如何工作的。它计算 and 表达式中的整个向量,然后返回符合 的值,如注释中@RitchieSacramento所述(此处也对此进行了进一步讨论。所以实际上正在被评估,从而导致错误。ifelse()
yes
no
condition
days_in_month(0:2)
- 因此,您可能只想使用基本并对其进行矢量化,而不是 。
ifelse
if(){}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()
yes
no
TRUE
FALSE
yes
no
test
TRUE
FALSE
no
days_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
如前所述,这不是问题,而是您如何使用 .它计算每个值,但会产生错误。mutate
across
ifelse
days_in_month(0)
我们可以修复输入,这样它就不会像函数那样传递无意义的月份值。你可以通过使用一些数学来转换为“上个月”来做到这一点,因此它变成了 ,例如,使用 .那么原来的功能就好了。例如:0
days_in_month
1:12
c(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
评论
across
x
.x
across
x
x
.x
across