Search code examples
rdplyrpurrrrlangtidyeval

Giving user defined functions non-quoted arguments in purrr


I'm learning quasiquotation and quotation with . My function looks at if any forbidden_values occur in any of the mentioned variables selectlist, and marks a row with any positive result as 1, else 0.

  • How can I make rlang interpret non-quoted input to the argument selectlist in the function environment instead of the global environment?

  • As I'm supplying a list of values, why do I have to use !! instead of !!!?

  • Why can I supply the anonymous formula any function with the dot-dot-dot operator?

This function works only with variable names to selectlist in quotes " " .

selectlist <- function(df, selectlist, forbidden_values){
  enquos(selectlist)
  enquos(forbidden_values)


  df %>% mutate(is_injured = purrr::pmap_int(select(.,!!selectlist), ~any(c(...) %in% !!forbidden_values)))

}

Works

selectlist(expected, selectlist=c("PrimaryInjury","SecondaryInjury"),forbidden_values=c("Rust","Insect","Snow break"))
  plantid year PrimaryInjury SecondaryInjury OtherInjury is_injured
1        1    1          <NA>            <NA>        <NA>          0
2        1    2        Insect            <NA>        <NA>          1
3        1    3          <NA>            <NA>        <NA>          0
4        2    1          <NA>            <NA>        <NA>          0
5        2    2          <NA>            <NA>        <NA>          0
6        2    3          Rust            <NA>        <NA>          1
7        3    1          <NA>            <NA>        <NA>          0
8        3    2          <NA>            <NA>        <NA>          0
9        3    3          <NA>            <NA>        <NA>          0
10       4    1          Rust            <NA>        <NA>          1
11       4    2          <NA>            <NA>        <NA>          0
12       4    3        Insect            <NA>        <NA>          1
13       5    1    Snow break            <NA>        <NA>          1
14       5    2          Rust            <NA>        <NA>          1
15       5    3          <NA>            <NA>        <NA>          0

Doesn't work

selectlist(expected, selectlist=c(PrimaryInjury,SecondaryInjury),forbidden_values=c("Rust","Insect","Snow break"))

Error in splice(dot_call(capture_dots, frame_env = frame_env, named = named, : object 'PrimaryInjury' not found

Thank you for helping...

Example dataframe: expected

plantid <- rep(c(1,2,3,4,5), times=c(3,3,3,3,3))
year <- rep(1:3, length.out=length(plantid))
set.seed(42)
PrimaryInjury <- sample(c(NA,NA,NA,"Rust","Insect","Snow break"), 15, replace=TRUE)
SecondaryInjury <- rep(NA, length.out=length(plantid))
OtherInjury <- rep(NA, length.out=length(plantid))

expected <- data.frame(plantid,year,PrimaryInjury,SecondaryInjury, OtherInjury)

#All in selectlist must be characters.
expected$PrimaryInjury <- as.character(expected$PrimaryInjury)
expected$SecondaryInjury <- as.character(expected$SecondaryInjury)
expected$OtherInjury <- as.character(expected$OtherInjury)

dput of expected

structure(list(plantid = c(1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 
5, 5, 5), year = c(1L, 2L, 3L, 1L, 2L, 3L, 1L, 2L, 3L, 1L, 2L, 
3L, 1L, 2L, 3L), PrimaryInjury = c(NA, "Insect", NA, NA, NA, 
"Rust", NA, NA, NA, "Rust", NA, "Insect", "Snow break", "Rust", 
NA), SecondaryInjury = c(NA_character_, NA_character_, NA_character_, 
NA_character_, NA_character_, NA_character_, NA_character_, NA_character_, 
NA_character_, NA_character_, NA_character_, NA_character_, NA_character_, 
NA_character_, NA_character_), OtherInjury = c(NA_character_, 
NA_character_, NA_character_, NA_character_, NA_character_, NA_character_, 
NA_character_, NA_character_, NA_character_, NA_character_, NA_character_, 
NA_character_, NA_character_, NA_character_, NA_character_)), row.names = c(NA, 
-15L), class = "data.frame")


Solution

  • If it is ok to pass unquoted variables separately, we can use three dots

    selectlist <- function(df, forbidden_values, ...){
    
        df %>% mutate(is_injured = purrr::pmap_int(select(.,...), 
                                   ~any(c(...) %in% forbidden_values)))
    }
    
    selectlist(expected, forbidden_values= c("Rust","Insect","Snow break"), 
                          PrimaryInjury, SecondaryInjury)
    
    
    #   plantid year PrimaryInjury SecondaryInjury OtherInjury is_injured
    #1        1    1          <NA>            <NA>        <NA>          0
    #2        1    2        Insect            <NA>        <NA>          1
    #3        1    3          <NA>            <NA>        <NA>          0
    #4        2    1          <NA>            <NA>        <NA>          0
    #5        2    2          <NA>            <NA>        <NA>          0
    #6        2    3          Rust            <NA>        <NA>          1
    #7        3    1          <NA>            <NA>        <NA>          0
    #8        3    2          <NA>            <NA>        <NA>          0
    #9        3    3          <NA>            <NA>        <NA>          0
    #10       4    1          Rust            <NA>        <NA>          1
    #11       4    2          <NA>            <NA>        <NA>          0
    #12       4    3        Insect            <NA>        <NA>          1
    #13       5    1    Snow break            <NA>        <NA>          1
    #14       5    2          Rust            <NA>        <NA>          1
    #15       5    3          <NA>            <NA>        <NA>          0