为什么case_when不能返回长度不同的向量?

5

这个失败了:

library(tidyverse)
myFn <- function(nmbr){
  case_when(
    nmbr > 3 ~ letters[1:3],
    TRUE ~ letters[1:2]
  )
}

myFn(4)
# Error: `TRUE ~ letters[1:2]` must be length 3 or one, not 2
# Run `rlang::last_error()` to see where the error occurred.

为什么会失败?为什么case_when的分支不能返回长度不同的向量?我想让myFn正常工作,这样我就可以做如下操作:

tibble(fruit = c("apple", "grape"),
       count = 3:4) %>% 
  mutate(bowl = myFn(count)) %>% 
  unnest(col = "bowl")

并获得

# A tibble: 5 x 3
fruit count  bowl
<chr> <int> <int>
1 apple     3     a
2 apple     3     b
3 grape     4     a
4 grape     4     b
5 grape     4     c

我可以通过编写不使用向量化的 myFn,并在其中使用 if/else,然后将其包装在 map 中,使其工作,但为什么我必须这样做呢?


1
由于data.frame(因此tibble)的一个基本原则是每列长度完全相同。 - r2evans
1
是的,在 mutate 中的调用必须始终返回与输入行数相同的长度(或 1),而 summarise 必须始终返回长度为 1。 - r2evans
1
哎呀,我写向量化函数的时候遇到了很大的困难;那些看起来应该能够正常工作的东西却总是让我措手不及。有没有好的 Vignette、书籍章节、文章或者 Stack Overflow 帖子可以作为教程?有什么技巧、诀窍和陷阱需要注意吗? - David T
@henrik - 我已经喜欢上它了。很高兴看到一本关于R地狱的书是由一个叫_Burns_的家伙写的。我想买一本,但亚马逊上二手书要102美元。他把pdf放在网上真是太好了。 - David T
@henrik - 另一方面,这篇文章是在 dplyr 出现之前写的,所以它无法讨论 case_whenrecode 等内容。 - David T
显示剩余4条评论
2个回答

5

根据我的评论,你的函数需要为每一行输入返回一个元素。但是,这些元素中的每一个可以是长度为0或更多(并且任意复杂度)的list。请尝试以下方法:

myFn <- function(nmbr){
  case_when(
    nmbr > 3 ~ list(letters[1:3]),
    TRUE ~ list(letters[1:2])
  )
}
tibble(fruit = c("apple", "grape"),
       count = 3:4) %>%
  mutate(bowl = myFn(count))
# # A tibble: 2 x 3
#   fruit count bowl     
#   <chr> <int> <list>   
# 1 apple     3 <chr [2]>
# 2 grape     4 <chr [3]>
tibble(fruit = c("apple", "grape"),
       count = 3:4) %>%
  mutate(bowl = myFn(count)) %>%
  unnest(col = "bowl")
# # A tibble: 5 x 3
#   fruit count bowl 
#   <chr> <int> <chr>
# 1 apple     3 a    
# 2 apple     3 b    
# 3 grape     4 a    
# 4 grape     4 b    
# 5 grape     4 c    

0
扩展r2Evans的答案,您可以在返回之前取消列表。这是我作为嵌套算法一部分使用的代码。
test_fn <- function(StockCode) {
  L <- dplyr::case_when(stringr::str_starts(StockCode, "A") ~ list(7500),
                        stringr::str_starts(StockCode, "FL") ~ list(6000),
                        stringr::str_starts(StockCode, "PIPE") ~ list(6500),
                        stringr::str_starts(StockCode, "RHS") ~ 
                          list(c(8000, 12000)),
                        stringr::str_starts(StockCode, "RND") ~ list(6000),
                        stringr::str_starts(StockCode, "SQ") ~ list(6000),
                        TRUE ~
                          list(c(9000, 10500, 12000, 13500, 15000, 16500, 18000))) %>%
    unlist()
  return(L)
}

test_fn("RHS")
test_fn("CH")

输出将是分配给列表的向量。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接