Search code examples
rrlang

Quasiquotation and ifelse : Unquoting not resolving as expected


I was expecting that I could use quasiquotation mechanisms from rlang package, such as !! and quo_name() to program name unquoting within mutate() and within ifelse() function. However, it did not work as I expected which I show below. Instead of resolving the value of the name in the scope of the dataframe in the ifelse() inside the mutate() call, it resolved only the character value of the parameter. I was able to use the base function get() to do what I want. However, I must be confusing something, and am looking how to do this in the rlang/quasiquotation world. Explanation and help appreciated.

suppressPackageStartupMessages(library(tidyverse))

df <- tibble(x=c(1, NA_integer_, 3), y=101:103)
print(df)
#> # A tibble: 3 x 2
#>       x     y
#>   <dbl> <int>
#> 1     1   101
#> 2    NA   102
#> 3     3   103

# Expected behavior
df %>%
    mutate(z = ifelse(is.na(x), y, x))
#> # A tibble: 3 x 3
#>       x     y     z
#>   <dbl> <int> <dbl>
#> 1     1   101     1
#> 2    NA   102   102
#> 3     3   103     3

# Similar question seemed to be posted here:
# From: https://community.rstudio.com/t/trouble-with-creating-column-names-from-a-passed-argument-in-function/7819/3
# I expect same output as above, but instead, the column `x` gets filled with the name of the column, not appropriate value.
v_colname <- "x"
df %>%
    mutate(z := ifelse(is.na(!!v_colname), y, !!v_colname)) %>%
    print()
#> # A tibble: 3 x 3
#>       x     y z    
#>   <dbl> <int> <chr>
#> 1     1   101 x    
#> 2    NA   102 x    
#> 3     3   103 x

# Tried variant with `quo_name()`, same unexpected result:
df %>%
    mutate(z := ifelse(is.na(!!quo_name(v_colname)), y, !!quo_name(v_colname)))
#> # A tibble: 3 x 3
#>       x     y z    
#>   <dbl> <int> <chr>
#> 1     1   101 x    
#> 2    NA   102 x    
#> 3     3   103 x

# This works, but I assume I am missing something with quasiquotation semantics:
df %>%
    mutate(z := ifelse(is.na(get(v_colname)), y, get(v_colname)))
#> # A tibble: 3 x 3
#>       x     y     z
#>   <dbl> <int> <dbl>
#> 1     1   101     1
#> 2    NA   102   102
#> 3     3   103     3

# Session info
sessionInfo()
#> R version 3.5.2 (2018-12-20)
#> Platform: x86_64-w64-mingw32/x64 (64-bit)
#> Running under: Windows 10 x64 (build 16299)
#> 
#> Matrix products: default
#> 
#> locale:
#> [1] LC_COLLATE=English_United States.1252 
#> [2] LC_CTYPE=English_United States.1252   
#> [3] LC_MONETARY=English_United States.1252
#> [4] LC_NUMERIC=C                          
#> [5] LC_TIME=English_United States.1252    
#> 
#> attached base packages:
#> [1] stats     graphics  grDevices utils     datasets  methods   base     
#> 
#> other attached packages:
#>  [1] bindrcpp_0.2.2  forcats_0.3.0   stringr_1.3.1   dplyr_0.7.8    
#>  [5] purrr_0.2.5     readr_1.3.1     tidyr_0.8.2     tibble_2.0.1   
#>  [9] ggplot2_3.1.0   tidyverse_1.2.1
#> 
#> loaded via a namespace (and not attached):
#>  [1] Rcpp_1.0.0       cellranger_1.1.0 plyr_1.8.4       pillar_1.3.1    
#>  [5] compiler_3.5.2   highr_0.7        bindr_0.1.1      tools_3.5.2     
#>  [9] digest_0.6.18    lubridate_1.7.4  jsonlite_1.6     evaluate_0.12   
#> [13] nlme_3.1-137     gtable_0.2.0     lattice_0.20-38  pkgconfig_2.0.2 
#> [17] rlang_0.3.1      cli_1.0.1        yaml_2.2.0       haven_2.0.0     
#> [21] xfun_0.4         withr_2.1.2      xml2_1.2.0       httr_1.4.0      
#> [25] knitr_1.21       hms_0.4.2        generics_0.0.2   grid_3.5.2      
#> [29] tidyselect_0.2.5 glue_1.3.0       R6_2.3.0         fansi_0.4.0     
#> [33] readxl_1.2.0     rmarkdown_1.11   modelr_0.1.2     magrittr_1.5    
#> [37] backports_1.1.3  scales_1.0.0     htmltools_0.3.6  rvest_0.3.2     
#> [41] assertthat_0.2.0 colorspace_1.4-0 utf8_1.1.4       stringi_1.2.4   
#> [45] lazyeval_0.2.1   munsell_0.5.0    broom_0.5.1      crayon_1.3.4

Created on 2019-02-27 by the reprex package (v0.2.1)


Solution

  • We can convert to symbol (sym) and then evaluate (!!)

    library(dplyr)
    df %>%
      mutate(z := ifelse(is.na(!!rlang::sym(v_colname)), y, !! rlang::sym(v_colname)))
    # A tibble: 3 x 3
    #      x     y     z
    #  <dbl> <int> <dbl>
    #1     1   101     1
    #2    NA   102   102
    #3     3   103     3