monkey
monkey

Reputation: 1597

How to move R code into functions to generalise behaviour

I have a huge messy piece of R code with loads of ugly repetition. There is an opportunity to massively reduce it. Starting with this piece of code:

table <-
  risk_assigned %>%
  group_by(rental_type, room_type) %>%
  summarise_all(funs( sum(!is.na(.)) / length(.) ) ) %>%
  select(-c(device_id, ts, room, hhi, temp)) %>%
  adorn_pct_formatting()

I would like to generalise it into a function so it can be reused.

   LayKable = function(kableDetails) {
     table <-
       risk_assigned %>%
       group_by(kableDetails$group1 , kableDetails$group2) %>%
       summarise_all(funs( sum(!is.na(.)) / length(.) ) ) #%>%
       select(-c(device_id, ts, room, hhi, temp)) %>%
       adorn_pct_formatting()

    ...

    kable <- table
    return(kable)
   }

   kableDetails <- list(
     group1 = "rental_type", 
     group2 = "room_type"
   )

   newKable <- LayKable(kableDetails)

This rather half-hearted attempt serves to explain what I want to do. How can I pass stuff into this function inside a list (I'm a C programmer, pretending it's a struct).

Upvotes: 1

Views: 45

Answers (1)

Andy Baxter
Andy Baxter

Reputation: 7626

When passing function arguments to a dplyr verb inside a function you have to use rlang terms. But should be simple to define a function you can pass a number of grouping terms to:

library(dplyr)

test_func <- function(..., data = mtcars) {
# Passing `data` as a default argument as it's nice to be flexible!  


  data %>% 
    group_by(!!!enquos(...)) %>% 
    summarise(across(.fns = sum), .groups = "drop")
}

test_func(cyl, gear)

#> # A tibble: 8 x 11
#>     cyl  gear   mpg  disp    hp  drat    wt  qsec    vs    am  carb
#>   <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1     4     3  21.5  120.    97  3.7   2.46  20.0     1     0     1
#> 2     4     4 215.   821    608 32.9  19.0  157.      8     6    12
#> 3     4     5  56.4  215.   204  8.2   3.65  33.6     1     2     4
#> 4     6     3  39.5  483    215  5.84  6.68  39.7     2     0     2
#> 5     6     4  79    655.   466 15.6  12.4   70.7     2     2    16
#> 6     6     5  19.7  145    175  3.62  2.77  15.5     0     1     6
#> 7     8     3 181.  4291.  2330 37.4  49.2  206.      0     0    37
#> 8     8     5  30.8  652    599  7.76  6.74  29.1     0     2    12

Update - adding a list

I see your ideal would be to write a list of arguments for each function call and pass these rather than write out the arguments in each call. You can do this using do.call to pass a list of named arguments to a function. Again, when using dplyr verbs you can quote variable names in constructing your list (so that R doesn't try to find them in the global environment when compiling the list) and !!enquo each one in the calls to then use them there:

library(dplyr)
test_func2 <- function(.summary_var, .group_var, data = mtcars) {
  
  data %>% 
    group_by(!!enquo(.group_var)) %>% 
    summarise(mean  = mean(!!enquo(.summary_var)))
  
}

# Test with bare arguments
test_func2(hp, cyl)

#> # A tibble: 3 x 2
#>     cyl  mean
#>   <dbl> <dbl>
#> 1     4  82.6
#> 2     6 122. 
#> 3     8 209.


# Construct and pass list
args <- list(.summary_var = quote(hp), .group_var = quote(cyl))

do.call(test_func2, args = args)

#> # A tibble: 3 x 2
#>     cyl  mean
#>   <dbl> <dbl>
#> 1     4  82.6
#> 2     6 122. 
#> 3     8 209.

A handy guide to tidy evaluation where most of these ideas are explained more clearly.

Created on 2021-12-21 by the reprex package (v2.0.1)

Upvotes: 2

Related Questions