向函数传递一个向量或未定义数量的参数

pass a function a vector or undefined number of arguments

我希望能够通过 ... 向函数传递未定义数量的参数,但也希望能够向它传递 vector。这是一个愚蠢的例子:

library(tidyverse)
df <- data.frame(gear = as.character(unique(mtcars$gear)),
                 id = 1:3)
myfun <- function(...) {
  ids_lst <- lst(...)
  df2 <- bind_rows(map(ids_lst, function(x) 
    mtcars %>% 
      filter(gear == x) %>% 
      select(mpg)), .id = "gear") %>% 
    left_join(df)
  df2
}
#these all work:
myfun(3)
myfun(3, 4)
myfun(3, 4, 5)

尽管向它传递一个向量不起作用:

myvector <- unique(mtcars$gear)
myfun(myvector)

问题出在函数收集参数的方式以及它如何 returns 它们:

myfun_lst <- function(...) {
  ids_lst <- lst(...)
  ids_lst
}
myfun_lst(3, 4, 5)
# $`3`
# [1] 3

# $`4`
# [1] 4

# $`5`
# [1] 5

myfun_lst(myvector)
# $myvector
# [1] 4 3 5

我认为解决方法是测试输入是否为 vector,例如:

myfun_final <- function(...) {
  if(is.vector(...) & !is.list(...)) {
    ids_lst <- as.list(...)
    names(ids_lst) <- (...)
  } else { 
    ids_lst <- lst(...)
  }
  df2 <- bind_rows(map(ids_lst, function(x) 
    mtcars %>% 
      filter(gear == x) %>% 
      select(mpg)), .id = "gear") %>% 
    left_join(df)
  df2
}

现在,向函数传递一个向量是可行的,但收集参数却不行:

myfun_final(3, 4, 5)
myfun_final(myvector)

有什么好的方法可以解决这个问题? 谢谢

如何测试 ... 的长度是否为 1 以及传递的唯一参数是否为向量?如果不是这样,则考虑 ... 缩放器列表并使用 lst(...).

捕获它们
myfun_final <- function(...) {
  if (...length() == 1L && is.vector(..1))
    ids_lst <- `names<-`(..1, ..1)
  else
    ids_lst <- lst(...)
  
  df2 <- bind_rows(map(ids_lst, function(x) 
    mtcars %>% 
      filter(gear == x) %>% 
      select(mpg)), .id = "gear") %>% 
    left_join(df)
  df2
}

测试

> myfun_final(3)
Joining, by = "gear"
   gear  mpg id
1     3 21.4  2
2     3 18.7  2
3     3 18.1  2
4     3 14.3  2
5     3 16.4  2
6     3 17.3  2
7     3 15.2  2
8     3 10.4  2
9     3 10.4  2
10    3 14.7  2
11    3 21.5  2
12    3 15.5  2
13    3 15.2  2
14    3 13.3  2
15    3 19.2  2
> myfun_final(3,4,5)
Joining, by = "gear"
   gear  mpg id
1     3 21.4  2
2     3 18.7  2
3     3 18.1  2
4     3 14.3  2
5     3 16.4  2
6     3 17.3  2
7     3 15.2  2
8     3 10.4  2
9     3 10.4  2
10    3 14.7  2
11    3 21.5  2
12    3 15.5  2
13    3 15.2  2
14    3 13.3  2
15    3 19.2  2
16    4 21.0  1
17    4 21.0  1
18    4 22.8  1
19    4 24.4  1
20    4 22.8  1
21    4 19.2  1
22    4 17.8  1
23    4 32.4  1
24    4 30.4  1
25    4 33.9  1
26    4 27.3  1
27    4 21.4  1
28    5 26.0  3
29    5 30.4  3
30    5 15.8  3
31    5 19.7  3
32    5 15.0  3
> myfun_final(c(3,4,5))
Joining, by = "gear"
   gear  mpg id
1     3 21.4  2
2     3 18.7  2
3     3 18.1  2
4     3 14.3  2
5     3 16.4  2
6     3 17.3  2
7     3 15.2  2
8     3 10.4  2
9     3 10.4  2
10    3 14.7  2
11    3 21.5  2
12    3 15.5  2
13    3 15.2  2
14    3 13.3  2
15    3 19.2  2
16    4 21.0  1
17    4 21.0  1
18    4 22.8  1
19    4 24.4  1
20    4 22.8  1
21    4 19.2  1
22    4 17.8  1
23    4 32.4  1
24    4 30.4  1
25    4 33.9  1
26    4 27.3  1
27    4 21.4  1
28    5 26.0  3
29    5 30.4  3
30    5 15.8  3
31    5 19.7  3
32    5 15.0  3

当然,您可以更改您的函数,使其同时适用于常规参数 myfun(3, 4, 5) 和向量 myfun(myvector),如上面的答案所示。

另一种选择是通过使用 bang bang bang 运算符取消引号来使用参数拼接 !!!。此运算符仅在某些 {rlang} 和 {tidyverse} 函数中受支持。在您的示例中,您评估了支持参数拼接的 purrr::map 内的点 ... 。因此可能不需要重写您的函数:

library(tidyverse)

# your original function:
myfun <- function(...) {
        ids_lst <- lst(...)
        df2 <- bind_rows(map(ids_lst, function(x) 
                mtcars %>% 
                        filter(gear == x) %>% 
                        select(mpg)), .id = "gear") %>% 
                left_join(df)
        df2
}

myvector <- unique(mtcars$gear)

myfun(!!! myvector) # works

#> Joining, by = "gear"
#>    gear  mpg id
#> 1     4 21.0  1
#> 2     4 21.0  1
#> 3     4 22.8  1
#> 4     4 24.4  1
#> 5     4 22.8  1
#> 6     4 19.2  1
#> 7     4 17.8  1
#> 8     4 32.4  1
#> 9     4 30.4  1
#> 10    4 33.9  1
#> ...


myfun(3, 4, 5) # works

#> Joining, by = "gear"
#>    gear  mpg id
#> 1     3 21.4  2
#> 2     3 18.7  2
#> 3     3 18.1  2
#> 4     3 14.3  2
#> 5     3 16.4  2
#> 6     3 17.3  2
#> 7     3 15.2  2
#> 8     3 10.4  2
#> 9     3 10.4  2
#> 10    3 14.7  2
#> ...

reprex package (v0.3.0)

于 2021-12-30 创建

您可以阅读有关使用 bang bang bang 运算符取消引用的更多信息 here

最后,您应该考虑您的功能的用户。如果您是唯一的用户,请选择适合您的任何内容。如果有其他用户,您应该考虑他们期望该功能如何工作。用户可能不希望一个函数可以处理多个参数,或者同时在一个向量中提供这些参数。在 tidyverse 参数中,用 !!! 拼接是一个公认的概念。在基础 R 中,我们通常会使用 do.call("myfun", as.list(myvector)) 来实现类似的效果。


要添加另一个选项:

purrr 包有一系列 lift 函数,可用于改变函数采用的参数类型。最突出的是 lift_dl ,它将一个以点为参数的函数转换为一个以列表或向量为参数的函数。这也可以用来解决问题而不需要重写函数:

lift_dl(myfun)(myvector)

#> Joining, by = "gear"
#>    gear  mpg id
#> 1     4 21.0  1
#> 2     4 21.0  1
#> 3     4 22.8  1
#> 4     4 24.4  1
#> 5     4 22.8  1
#> 6     4 19.2  1
#> 7     4 17.8  1
#> 8     4 32.4  1
#> 9     4 30.4  1
#> 10    4 33.9  1
#> ...

reprex package (v0.3.0)

创建于 2022-01-01