在 mutate_if 调用中提取列名

Extract column name in mutate_if call

我想提取函数调用中的列名 mutate_if。有了这个,然后我想查找一个值 不同 table 并用查找值填充缺失值。我尝试使用 quosure 语法,但它不起作用。 是否可以直接提取列名?

示例数据

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)
  }))

有了一个额外的功能,我可以提取名称,但是替换就不再起作用了。

custom_mutate <- function(v) {
  v <- enquo(v)
  lookup_value <- df_lookup %>% pull(quo_name(v))

  # ifelse(is.na((!!v)), lookup_value, (!!v))
}

df %>% 
  mutate_if(is.numeric, funs(custom_mutate(v = .)))

有效

如果我将 df 作为附加参数添加到我的自定义函数中,它就可以工作,但是有没有办法不使用它?感觉不对,而不是 dplyr 的本意...如果我错了请纠正我 ;)
除此之外,我必须使用 UQE 而不是 !! 并且正如它在 Programming with dplyr:

中所说

UQE() is for expert use only

custom_mutate2 <- function(v, df) {
  v <- enquo(v)
  lookup_value <- df_lookup %>% pull(quo_name(v))

  df %>% 
    mutate(UQE(v) := ifelse(is.na((!!v)), lookup_value, (!!v))) %>% 
    pull(!!v)
}

df %>% 
  mutate_if(is.numeric, funs(custom_mutate2(v = ., df = df)))

预期输出

# 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   

您必须使用 quo 而不是 enquo

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

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

以你为例:

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    

是绝对正确的(你需要使用 quo)但是,因为我的第一个想法也是使用 enquo 我已经研究了为什么你必须使用quo 改为:

如果我们查看 mutate_if 的源代码,我们可以看到它是如何构造的:

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

通过略微修改覆盖 dplyr 中的 mutate_if 函数,我可以插入对 print() 的调用,允许我查看正在传递的 funs 对象至 mutate

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

然后,运行 您的代码将使用这个修改后的 mutate_if 函数::

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

现在,我们可以看到传递给 mutate 调用的函数列表已经用列名替换了 . 变量。这意味着,在语句中,有一个名为 xyz 的变量,其值来自 df.

想象一下简单的情况,我们有:

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

由此,希望您可以推断出为什么要使用 quo 而不是 enquo。您在列名之后,这是变量的名称 - 由 quo.

提供给您

因此,使用 quo 而不是 enquo 并且不首先将其分配给变量:

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