Reza
Reza

Reputation: 319

Extending a tidyverse function via `...`

I'm trying to extend my foo function below by allowing user to define any number of arguments in place of ....

These ... arguments will exactly be treated as the current 3 arguments (time, outcome, trt_gr).

Is this possible in R?

foo <- function(time = 1, outcome = 1, trt_gr = 1, ...){

  time <- seq_len(time)
  outcome <- seq_len(outcome)
  trt_gr <- seq_len(trt_gr)
  
data <- expand.grid(time = time, outcome = outcome, trt_gr = trt_gr, info. = c("control","treatment"))

data %>% 
  group_by(outcome, time, trt_gr) %>%
  summarise(info. = str_c(sort(info., decreasing = TRUE), 
                          collapse = ' vs. '), .groups = 'drop') 
}

# EXAMPLE OF CURRENT USE:

foo()

#  outcome  time trt_gr info.                
#    <int> <int>  <int> <chr>                
#1       1     1      1 treatment vs. control

Upvotes: 1

Views: 76

Answers (1)

TimTeaFan
TimTeaFan

Reputation: 18561

Yes this is possible. We can replace your arguments with the elipsis ... and allow the function to produce any amount of columns of with custom column names. Here is such a function in the tidyverse style:

library(tidyverse)

foo <- function(...){
  
  dots <- rlang::list2(...) 
  var_nms <- names(dots)
  inp <- purrr::map(dots, seq_len)

  data <- tidyr::expand_grid(!!! inp,
                             info. = c("control","treatment"))
  
  data %>% 
    dplyr::group_by(!!!syms(var_nms)) %>%
    dplyr::summarise(info. = stringr::str_c(sort(info., decreasing = TRUE), 
                                            collapse = ' vs. '), .groups = 'drop') 
}

foo(time = 1, outcome = 1, trt_gr = 1)
#> # A tibble: 1 x 4
#>    time outcome trt_gr info.                
#>   <int>   <int>  <int> <chr>                
#> 1     1       1      1 treatment vs. control

foo(some = 2, new = 1, colnames = 3)
#> # A tibble: 6 x 4
#>    some   new colnames info.                
#>   <int> <int>    <int> <chr>                
#> 1     1     1        1 treatment vs. control
#> 2     1     1        2 treatment vs. control
#> 3     1     1        3 treatment vs. control
#> 4     2     1        1 treatment vs. control
#> 5     2     1        2 treatment vs. control
#> 6     2     1        3 treatment vs. control

Created on 2021-08-26 by the reprex package (v0.3.0)

Update

To answer the added question in the comments. Yes we can vectorize the function above in the following way which also allows to skip columns in a run, when they contain a 0:

library(tidyverse)

foo <- function(...){
  
  dots <- rlang::list2(...) 
  var_nms <- names(dots)
  inp_ls <- map(dots, ~ map(.x, seq_len)) %>% transpose %>% map(compact)
  
  data_ls <- map(inp_ls, 
                 ~ tidyr::expand_grid(!!! .x,
                                      info. = c("control","treatment")))
  
  map2(data_ls, inp_ls, ~ .x %>% 
        dplyr::group_by(!!!syms(names(.y))) %>%
        dplyr::summarise(info. = stringr::str_c(sort(info., decreasing = TRUE), 
                                                collapse = ' vs. '), .groups = 'drop')) 
}

foo(some = c(1,2), new = c(1,0), colnames = c(1,3))
#> [[1]]
#> # A tibble: 1 x 4
#>    some   new colnames info.                
#>   <int> <int>    <int> <chr>                
#> 1     1     1        1 treatment vs. control
#> 
#> [[2]]
#> # A tibble: 6 x 3
#>    some colnames info.                
#>   <int>    <int> <chr>                
#> 1     1        1 treatment vs. control
#> 2     1        2 treatment vs. control
#> 3     1        3 treatment vs. control
#> 4     2        1 treatment vs. control
#> 5     2        2 treatment vs. control
#> 6     2        3 treatment vs. control

Created on 2021-08-26 by the reprex package (v0.3.0)

Upvotes: 4

Related Questions