Search code examples
rrlangtidyeval

Why does tidy evaluation throw an error unless an argument is printed or forced?


Code

## 1. Get Hours into Tibble  
## ---------------------
  hrsThreshold <- function(input_data, group1, group2, group2_String) {

    # print ({{group2}})   # for some ridiculous reason, code doesn't work without this print statement in!
    
  # Hours
    hrs <-
    # thresholds
      input_data %>% 
      mutate( score = rowSums(select(., starts_with("less_than")), na.rm = TRUE)) %>%

    # scores & summary
      group_by({{ group1 }}, {{ group2 }}, score) %>%
      summarise(n_points = n()) %>%
      mutate(percent = n_points / sum(n_points)) %>%
      mutate(score  = as_factor(score)) %>%

    # add percentage labels.
      group_by({{ group1 }}, {{ group2 }}) %>%

      mutate(ymax = cumsum(percent),
             ymin = c(0, head(cumsum(percent), n = -1))) %>%

      mutate(label_pos = (ymax+ymin) / 2,
             label = ifelse(percent < 0.01, "", paste0(round(percent*100, 0), "%"))) %>%

      ungroup()
    
    hrs
    

  # Hours All
    hrs_all <-
      hrs %>%
      drop_na( {{ group2 }} ) %>%
      select(-label) %>%

    # Sum n_points
      group_by({{ group1 }}, score) %>%
      summarise_at(
        vars(n_points),
        .funs = sum) %>%

      mutate(percent = n_points / sum(n_points)) %>%

    # redo geometry of rectangles.
      group_by({{ group1 }}) %>%
      mutate(ymax = cumsum(percent),
             ymin = c(0, head(cumsum(percent), n = -1))) %>%
      mutate(label_pos = (ymax+ymin) / 2,
             label = ifelse(percent < 0.01, "", paste0(round(percent*100, 0), "%"))) %>%

    # Change room_type = All
      mutate({{ group2 }} := "all")


    # Join All with Variable
      hrs_by_room <- full_join(hrs_all, hrs)
      return(hrs_by_room)
  }



  # Debug Code
    analysis_vars <- c("room_type", "tenure")
    analysis_index <- 2
    
  # Dynamic Variable
    mytempVar      <- analysis_vars[analysis_index]

    mytempVarTitle <- mytempVar
    mytempVarTitle <- str_replace(mytempVarTitle, "[_]", " ")
    mytempVarTitle <- str_to_title(mytempVarTitle)
    
    mytempVar_     <- sym(mytempVar)


  # run command
  hrs_by_room   <- hrsThreshold(risk_assigned, hhi, mytempVar_,   c({{mytempVar_}}))

Description of Issue

The code above only works if the line :

print ({{group2}})

is uncommented!!! This command, on the face of it, does NOTHING. But without it on my machine, the following error comes:

Error: Must group by variables found in `.data`.
* Column `mytempVar_` is not found.
Run `rlang::last_error()` to see where the error occurred.
> rlang::last_error()
<error/rlang_error>
Must group by variables found in `.data`.
* Column `mytempVar_` is not found.
Backtrace:
  1. global::hrsThreshold(...)
 11. dplyr:::group_by.data.frame(...)
 12. dplyr::group_by_prepare(.data, ..., .add = .add, caller_env = caller_env())
Run `rlang::last_trace()` to see the full context.
> rlang::last_trace()
<error/rlang_error>
Must group by variables found in `.data`.
* Column `mytempVar_` is not found.
Backtrace:
     x
  1. +-global::hrsThreshold(...)
  2. | \-`%>%`(...)
  3. +-dplyr::ungroup(.)
  4. +-dplyr::mutate(...)
  5. +-dplyr::mutate(...)
  6. +-dplyr::group_by(...)
  7. +-dplyr::mutate(., score = as_factor(score))
  8. +-dplyr::mutate(., percent = n_points/sum(n_points))
  9. +-dplyr::summarise(., n_points = n())
 10. +-dplyr::group_by(...)
 11. \-dplyr:::group_by.data.frame(...)
 12.   \-dplyr::group_by_prepare(.data, ..., .add = .add, caller_env = caller_env())

I've confirmed this is the issue that has been crashing my entire report application for days, in multiple functions. I only finally got it workoing by accidentally putting print statments in to debug the problem. Whereupon it mysteriously started to work. What on earth is going on?

Source File

library(tidyverse)
risk_assigned <- read.csv( text="hhi,device_id,rental_type,room,room_type,ts,temp,less_than_21,less_than_18,less_than_16,less_than_12
MyOrg,2C7970,Council Rental,Lounge,living,1598291349,18.3,1,NA,NA,NA
MyOrg,2C7970,Council Rental,Lounge,living,1598292249,18.3,1,NA,NA,NA
MyOrg,2C7970,Council Rental,Lounge,living,1598293149,18.3,1,NA,NA,NA
MyOrg,2C7970,Council Rental,Lounge,living,1598294049,18.3,1,NA,NA,NA
MyOrg,2C7970,Council Rental,Lounge,living,1598294949,18.2,1,NA,NA,NA
MyOrg,2C7970,Council Rental,Lounge,living,1598353449,18.5,1,NA,NA,NA
MyOrg,2C7970,Council Rental,Lounge,living,1598354354,18.5,1,NA,NA,NA
MyOrg,2C7970,Council Rental,Lounge,living,1598355254,18.5,1,NA,NA,NA
MyOrg,2C7970,Council Rental,Lounge,living,1598356154,18.5,1,NA,NA,NA
MyOrg,2C7970,Council Rental,Lounge,living,1598357054,18.5,1,NA,NA,NA
MyOrg,2C7970,Council Rental,Lounge,living,1598357954,18.5,1,NA,NA,NA
MyOrg,2C7971,Private Rental,Lounge,living,1598358854,18.5,1,NA,NA,NA
MyOrg,2C7971,Private Rental,Lounge,living,1598359754,18.6,1,NA,NA,NA
MyOrg,2C7971,Private Rental,Lounge,living,1598360654,18.5,1,NA,NA,NA
MyOrg,2C7971,Private Rental,Lounge,living,1598361554,18.5,1,NA,NA,NA
MyOrg,2C7971,Private Rental,Lounge,living,1598362454,18.4,1,NA,NA,NA
MyOrg,2C7971,Private Rental,Lounge,living,1598363354,18.4,1,NA,NA,NA
MyOrg,2C7971,Private Rental,Lounge,living,1598364254,18.4,1,NA,NA,NA
MyOrg,2C7971,Private Rental,Lounge,living,1598365154,18.4,1,NA,NA,NA
MyOrg,2C7971,Private Rental,Lounge,living,1598366054,18.3,1,NA,NA,NA
MyOrg,2C7971,Private Rental,Lounge,living,1598366954,18.2,1,NA,NA,NA
MyOrg,2C7971,Private Rental,Lounge,living,1598367850,18.2,1,NA,NA,NA
MyOrg,2C7971,Private Rental,Lounge,living,1598368750,18.1,1,NA,NA,NA
MyOrg,2C7971,Private Rental,Lounge,living,1598369650,18.1,1,NA,NA,NA
MyOrg,2C7971,Private Rental,Lounge,living,1598370550,18,1,NA,NA,NA
MyOrg,2C7972,Private Rental,Lounge,living,1598371450,17.9,1,1,NA,NA
MyOrg,2C7972,Private Rental,Lounge,living,1598372350,17.9,1,1,NA,NA
MyOrg,2C7972,Private Rental,Lounge,living,1598373250,17.8,1,1,NA,NA
MyOrg,2C7972,Private Rental,Lounge,living,1598374150,17.8,1,1,NA,NA
MyOrg,2C7972,Private Rental,Lounge,living,1598375050,17.8,1,1,NA,NA
MyOrg,2C7972,Private Rental,Lounge,living,1598375950,17.7,1,1,NA,NA
MyOrg,2C7972,Private Rental,Lounge,living,1598376850,17.6,1,1,NA,NA
MyOrg,2C7972,Private Rental,Lounge,living,1598377750,17.6,1,1,NA,NA
MyOrg,2C7972,Private Rental,Lounge,living,1598378650,17.6,1,1,NA,NA
MyOrg,2C7972,Private Rental,Lounge,living,1598379553,17.5,1,1,NA,NA
MyOrg,2C7972,Private Rental,Lounge,living,1598380453,17.5,1,1,NA,NA
MyOrg,2C7972,Private Rental,Lounge,living,1598381353,17.4,1,1,NA,NA
MyOrg,2C7972,Private Rental,Lounge,living,1598439851,17.7,1,1,NA,NA
MyOrg,2C7972,Private Rental,Lounge,living,1598440751,17.7,1,1,NA,NA
MyOrg,2C7972,Private Rental,Lounge,living,1598441651,17.6,1,1,NA,NA
MyOrg,2C7972,Private Rental,Lounge,living,1598442551,17.6,1,1,NA,NA
MyOrg,2C7972,Private Rental,Lounge,living,1598443451,17.5,1,1,NA,NA
MyOrg,2C7973,Council Rental,Lounge,living,1598444351,17.5,1,1,NA,NA
MyOrg,2C7973,Council Rental,Lounge,living,1598445251,17.4,1,1,NA,NA
MyOrg,2C7973,Council Rental,Lounge,living,1598446151,17.4,1,1,NA,NA
MyOrg,2C7973,Council Rental,Lounge,living,1598447051,17.3,1,1,NA,NA
MyOrg,2C7973,Council Rental,Lounge,living,1598447951,17.3,1,1,NA,NA
MyOrg,2C7973,Council Rental,Lounge,living,1598448851,17.1,1,1,NA,NA
MyOrg,2C7973,Council Rental,Lounge,living,1598449751,17.1,1,1,NA,NA
MyOrg,2C7973,Council Rental,Lounge,living,1598450651,17.1,1,1,NA,NA
MyOrg,2C7973,Council Rental,Lounge,living,1598451554,17.1,1,1,NA,NA
MyOrg,2C7973,Council Rental,Lounge,living,1598452454,17.1,1,1,NA,NA
MyOrg,2C7973,Council Rental,Lounge,living,1598453354,17.1,1,1,NA,NA
MyOrg,2C7973,Council Rental,Lounge,living,1598454254,17.1,1,1,NA,NA
MyOrg,2C7973,Council Rental,Lounge,living,1598455154,17.1,1,1,NA,NA
MyOrg,2C7973,Council Rental,Lounge,living,1598456054,17,1,1,NA,NA
MyOrg,2C7973,Council Rental,Lounge,living,1598456954,17,1,1,NA,NA
MyOrg,2C7973,Council Rental,Lounge,living,1598457854,16.9,1,1,NA,NA
MyOrg,2C7973,Council Rental,Lounge,living,1598458754,16.9,1,1,NA,NA
MyOrg,2C7973,Council Rental,Lounge,living,1598459654,16.9,1,1,NA,NA
MyOrg,2C7973,Council Rental,Lounge,living,1598460554,16.9,1,1,NA,NA
MyOrg,2C7973,Council Rental,Lounge,living,1598461454,16.8,1,1,NA,NA
MyOrg,2C7973,Council Rental,Lounge,living,1598462354,16.8,1,1,NA,NA
MyOrg,2C7974,Council Rental,Lounge,living,1598463254,16.7,1,1,NA,NA
MyOrg,2C7974,Council Rental,Lounge,living,1598464154,16.8,1,1,NA,NA
MyOrg,2C7974,Council Rental,Lounge,living,1598465051,16.7,1,1,NA,NA
MyOrg,2C7974,Council Rental,Lounge,living,1598465951,16.8,1,1,NA,NA
MyOrg,2C7974,Council Rental,Lounge,living,1598466851,16.7,1,1,NA,NA")

Solution

  • Why does this fail?

    Here is a minimal example:

    var <- rlang::sym("cyl")
    
    fn <- function(var, force) {
      if (force) {
        force(var)
      }
    
      dplyr::group_by(mtcars, {{ var }})
    }
    

    The expected behaviour is:

    fn(var, force = FALSE)
    #> Error: Must group by variables found in `.data`.
    #> * Column `var` is not found.
    

    This is unexpected behaviour:

    fn(var, force = TRUE)
    #> # A tibble: 32 × 11
    #> # Groups:   cyl [3]
    #>     mpg   cyl  disp    hp  drat    wt  qsec    vs    am  gear  carb
    #>   <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
    #> 1  21       6   160   110  3.9   2.62  16.5     0     1     4     4
    #> 2  21       6   160   110  3.9   2.88  17.0     0     1     4     4
    #> 3  22.8     4   108    93  3.85  2.32  18.6     1     1     4     1
    #> 4  21.4     6   258   110  3.08  3.22  19.4     1     0     3     1
    #> # … with 28 more rows
    

    There are two causes to this unexpected behaviour.

    1. Using {{ out of context is equivalent to forcing an argument. So print({{ var }}) is equivalent to force(var). See https://rlang.r-lib.org/reference/topic-inject-out-of-context.html.

    2. It is not possible to defuse a forced argument. And because the compiler unwraps constants from promises, we can't detect that case to throw an error instead. This is why forced arguments are injected rather than defused. See https://rlang.r-lib.org/reference/topic-embrace-non-args.html.

    We can't fix either (1) or (2) because tidy evaluation is not implemented in the core language, it's implemented on top of it.

    What should I do?

    In your example, it seems that you would like to pass a column name to your function through an argument.

    The first thing to note is that since that argument is passed to group_by() with {{, your function inherits all the behaviour of group_by(). It takes unquoted column names and supports all tidy eval features.

    When you have a column name in a string, the canonical way to pass it is with the .data pronoun:

    var <- "cyl"
    
    fn <- function(var) {
      dplyr::group_by(mtcars, {{ var }})
    }
    
    fn(var)
    #> Error: Must group by variables found in `.data`.
    #> * Column `var` is not found.
    
    fn(.data[[var]])
    #> # A tibble: 32 × 11
    #> # Groups:   cyl [3]
    #>     mpg   cyl  disp    hp  drat    wt  qsec    vs    am  gear  carb
    #>   <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
    #> 1  21       6   160   110  3.9   2.62  16.5     0     1     4     4
    #> 2  21       6   160   110  3.9   2.88  17.0     0     1     4     4
    #> 3  22.8     4   108    93  3.85  2.32  18.6     1     1     4     1
    #> 4  21.4     6   258   110  3.08  3.22  19.4     1     0     3     1
    #> # … with 28 more rows
    

    See https://rlang.r-lib.org/reference/dot-data.html.