Extract column name in mutate_if call

You have to use quo instead of enquo

#enquo(.) :
<quosure: empty>
~function (expr) 
{
    enexpr(expr)
}
...

#quo(.) :
<quosure: frame>
~x
<quosure: frame>
~y
<quosure: frame>
~z

With your example :

mutate_if(df, is.numeric, funs({
  lookup_value <- df_lookup %>% pull(quo_name(quo(.)))
  ifelse(is.na(.), lookup_value, .)
}))

# A tibble: 10 x 4
       x     y     z a    
   <int> <int> <int> <chr>
 1     1     1     8 a    
 2     2     2     2 b    
 3     3     3     3 c    
 4     4     5     8 d    
 5     5     1     8 e    
 6     6     2     2 a    
 7     7     3     3 b    
 8     8     5     8 c    
 9     9     1     8 d    
10    10     2     2 e    

Julien Nvarre's answer is absolutely correct (you need to use quo) but, since my first thought would also have been to use enquo I have looked at why you have to use quo instead:

If we look at the source for mutate_if we can see how it is constructed:

dplyr:::mutate_if
#> function (.tbl, .predicate, .funs, ...) 
#> {
#>     funs <- manip_if(.tbl, .predicate, .funs, enquo(.funs), caller_env(), 
#>         ...)
#>     mutate(.tbl, !(!(!funs)))
#> }
#> <environment: namespace:dplyr>

By overriding the mutate_if function in dplyr with a slight modification, I can insert a call to print() allowing me to look at the funs object being passed to mutate:

mutate_if <- function (.tbl, .predicate, .funs, ...) 
{
  funs <- dplyr:::manip_if(.tbl, .predicate, .funs, enquo(.funs), caller_env(), 
                   ...)
  print(funs)
}

Then, running your code will use this modified mutate_if function::

df <- structure(list(x = 1:10, 
                     y = c(1L, 2L, 3L, NA, 1L, 2L, 3L, NA, 1L, 2L), 
                     z = c(NA, 2L, 3L, NA, NA, 2L, 3L, NA, NA, 2L), 
                     a = c("a", "b", "c", "d", "e", "a", "b", "c", "d", "e")), 
                .Names = c("x", "y", "z", "a"), 
                row.names = c(NA, -10L), 
                class = c("tbl_df", "tbl", "data.frame"))
df_lookup <- tibble(x = 0L, y = 5L, z = 8L)

df %>% 
  mutate_if(is.numeric, funs({
    x <- .
    x <- enquo(x)
    lookup_value <- df_lookup %>% pull(quo_name(x))
    x <- ifelse(is.na(x), lookup_value, x)
    return(x)
  }))
#> $x
#> <quosure>
#>   expr: ^{
#>           x <- x
#>           x <- enquo(x)
#>           lookup_value <- df_lookup %>% pull(quo_name(x))
#>           x <- ifelse(is.na(x), lookup_value, x)
#>           return(x)
#>         }
#>   env:  0000000007FBBFA0
#> 
#> $y
#> <quosure>
#>   expr: ^{
#>           x <- y
#>           x <- enquo(x)
#>           lookup_value <- df_lookup %>% pull(quo_name(x))
#>           x <- ifelse(is.na(x), lookup_value, x)
#>           return(x)
#>         }
#>   env:  0000000007FBBFA0
#> 
#> $z
#> <quosure>
#>   expr: ^{
#>           x <- z
#>           x <- enquo(x)
#>           lookup_value <- df_lookup %>% pull(quo_name(x))
#>           x <- ifelse(is.na(x), lookup_value, x)
#>           return(x)
#>         }
#>   env:  0000000007FBBFA0

Now, we can see that the function list being passed to the mutate call has already substituted the name of the column for the . variable. This means that, within the statement, there is a variable called x, y, or z the value of which comes from df.

Imagine the simple case, we have:

library(rlang)
x <- 1:10
quo(x)
#> <quosure>
#>   expr: ^x
#>   env:  0000000007615318
enquo(x)
#> <quosure>
#>   expr: ^<int: 1L, 2L, 3L, 4L, 5L, ...>
#>   env:  empty

From this, hopefully you can extrapolate why you want to use quo rather than enquo. You are after the column name, which is the name of the variable - given to you by quo.

Thus, using quo instead of enquo and not assigning it to a variable first:

mutate_if(df, is.numeric, funs({
  lookup_value <- df_lookup %>% pull(quo_name(quo(.)))
  ifelse(is.na(.), lookup_value, .)
}))

Tags:

R

Nse

Dplyr