使用带引号的参数作为闭包实例化的变量名?

Use quoted parameter as variable name for closure instantiation?

我已将我的大部分问题作为评论写在我提供的代表中。我希望改进我的代码的语义并回答有关将引用变量作为 closure-like 函数的参数的特定问题。

library(tidyverse)

# A df of file-paths split so all basenames
# are in the same column, but parent-dirs
# are spread across an abritary number of columns
# and filled with NA's.
dat <- tibble(
  ref01 = rep("analysis", 5),
  ref02 = c(NA, NA, "next", "next", "next"),
  ref03 = c(NA, NA, NA, NA, "last"),
  target = c("analysis.test1", "analysis.test2",
             "next.test3", "next.test4",
             "last.test5")
)

# For example this reprex df shows file-paths
# from a file-tree that looks like:
# analysis
# ├── next
# │   ├── last
# │   │   └── last.test5
# │   ├── next.test3
# │   └── next.test4
# ├── analysis.test1
# └── analysis.test2
dat
#> # A tibble: 5 x 4
#>   ref01    ref02 ref03 target        
#>   <chr>    <chr> <chr> <chr>         
#> 1 analysis <NA>  <NA>  analysis.test1
#> 2 analysis <NA>  <NA>  analysis.test2
#> 3 analysis next  <NA>  next.test3    
#> 4 analysis next  <NA>  next.test4    
#> 5 analysis next  last  last.test5

此函数清除 'target' 测试基名。 所有 test-names 都以其 parent-dir 名称和句点开头。 (例如 'last.test5')

这个函数接受一个 "target" 列和任意数量的 parent-dir 列。它反转 parent-dirs 的列表和 找到第一个 non-NA 值。然后将该值匹配到 目标值并将其删除。

我的问题在于这个函数:

  1. 有没有更语义化的方法来构建这个函数 以便它可以在 `mutate()' 函数中表达?
  2. 目前,replace_pattern()函数依赖于 .key 列标题为 "target" 和 被硬编码为输入参数。

    这是因为 `pmap' 的工作方式是 p-num 列表中的参数和名称的匹配参数。

    因为我希望这个函数适用于任意深度 file-paths,我需要找到一种方法来处理不同的 .key 名称。

    有没有办法引用 .key 变量,这样它就会 replace_pattern() 函数的第一个参数的名称?

trim_target <- function(.tbl, .key, ...){
  key <- tidyselect::eval_select(expr(c(!!enquo(.key))), .tbl)
  loc <- tidyselect::eval_select(expr(c(...)), .tbl)

  # First param has to be "target" since that's the name
  # of the .key column.
  replace_pattern <- function(target, ...){
    args <- c(...)
    pattern <- args %>% 
      rev() %>% 
      discard(is.na) %>% 
      first() %>% 
      paste0("\.")

    unlist(str_remove(target, pattern))
  }

  pmap(.tbl[,c(key, loc)], replace_pattern) %>% 
    unlist()
}

预期输出: 这按预期工作但不可扩展。 同样参考问题01,我必须通过dat 进入 mutate() function-call;我通常看不到这样做。

dat %>% 
  mutate(target = trim_target(dat, target, ref01:ref03))
#> # A tibble: 5 x 4
#>   ref01    ref02 ref03 target
#>   <chr>    <chr> <chr> <chr> 
#> 1 analysis <NA>  <NA>  test1 
#> 2 analysis <NA>  <NA>  test2 
#> 3 analysis next  <NA>  test3 
#> 4 analysis next  <NA>  test4 
#> 5 analysis next  last  test5

reprex package (v0.3.0)

于 2020-04-08 创建

回答问题 1

当您说您通常看不到 dat 传递给 mutate() 时,那是因为大多数函数通常不需要数据框上下文。例如,当您看到

mtcars %>% mutate( cyl = sqrt(cyl) )

函数 sqrt() 直接处理传递给它的值,而不关心这些值的来源。在您的情况下,您需要数据框上下文来帮助解析 ref01:ref03 表达式。出于这个原因,更合适的模式是将 mutate() 操作 放在 您的函数中,并让它 return 生成数据框。

回答问题 2

如果输入是命名列表,

pmap() 仅匹配参数名称。如果列表未命名,则匹配按位置完成。因此,您可以 1) 取消命名参数列表:

.tbl[,c(key, loc)] %>% as.list() %>% unname %>% pmap_chr(replace_pattern)

或 2) 由于您已经使用 [ 对列进行子集化,因此请将其转换为适当的 select 模式并相应地重命名所选列:

.tbl %>% select( target={{.key}}, ... ) %>% pmap_chr( replace_pattern )

综合起来

考虑到这两点,我会这样重写你的函数:

mutate_target <- function(.tbl, .key, ...){

  # No change from the OP
  replace_pattern <- function(target, ...){
    args <- c(...)
    pattern <- args %>%
      rev() %>%
      discard(is.na) %>%
      first() %>%
      paste0("\.")

    unlist(str_remove(target, pattern))
  }

  result <- .tbl %>% select( target={{.key}}, ... ) %>% pmap_chr( replace_pattern )
  .tbl %>% mutate( {{.key}} := result )
}

请注意,我删除了您明确的 eval_select() 调用。您可以将 ... 点直接传递给 dplyr 动词,同时将卷曲({{,即 !!enquo 的 shorthand)用于单数列。新功能的使用方法如下:

dat %>% mutate_target( target, ref01:ref03 )                           # Works
dat %>% rename( abc = target ) %>% mutate_target( abc, ref01:ref03 )   # Also works