在 dplyr 的 mutate 中使用 across 的问题,其中要应用的函数取决于另一个 tibble

Problem using across in mutate in dplyr, where function to apply depends on another tibble

我正在尝试改变 tibble 的某些列,其中要使用的特定函数在另一个 tibble 中命名。设置代码如下,之后我解释问题。

library(tidyverse)
library(lubridate)

transforms <- list(
  "floor_date" = function(x) {
    floor_date(dmy(x), "month")
  },
  "integer" = function(x) {
    as.integer(gsub("[^[:digit:]]", "", x))
  }
)

data_meta <- tibble(
  datafield = letters[1:3], 
  transform_to = c("floor_date", "", "integer")
)
# A tibble: 3 x 2
# datafield transform_to
# <chr>     <chr>       
# 1 a       "floor_date"
# 2 b       ""          
# 3 c       "integer"  

data <- tibble(
  a = c("09/09/2021", "19/09/2021", "06/10/2021"),
  b = c("lorem", "ipsum", "dolor"),
  c = c("99 bottles", "98 bottles", "97 bottles")
)
# A tibble: 3 x 3
#   a          b     c         
#   <chr>      <chr> <chr>
# 1 09/09/2021 lorem 99 bottles
# 2 19/09/2021 ipsum 98 bottles
# 3 06/10/2021 dolor 97 bottles

data_meta tibble 包含 data tibble 的每一列所需的转换函数(如果有)。这些转换函数位于命名列表 transforms 中。为了只关注那些需要转换的列,我定义了 needs_transform:

needs_transform <- data_meta %>%
      filter(nchar(transform_to) > 0)
    # A tibble: 2 x 2
    #   datafield transform_to
    #   <chr>     <chr>       
    # 1 a         floor_date  
    # 2 c         integer

我现在想使用 mutate(across(...)) 来应用转换。我发现以下根据列名给出了正确的函数:

transforms[[(needs_transform %>% filter(datafield == "a") %>% select(transform_to))[[1,1]]]]
# function(x) {
#   floor_date(dmy(x), "month")
# }

所以我尝试使用 cur_column() 函数进行以下正确过滤:

clean_data <- data %>%
  mutate(across(
    needs_transform$datafield,
    ~ transforms[[(needs_transform %>%
                     filter(datafield == cur_column()) %>% select(transform_to))[[1,1]]]]
  ))
# Error in `mutate()`:
#   ! Problem while computing `..1 = across(...)`.
# Caused by error in `across()`:
#   ! Problem while computing column `a`.

不幸的是,这不起作用,我不确定为什么,即使在检查回溯之后(可以提供它,但它没有帮助)。

我的第二次尝试是尝试将逻辑包装在一个函数中(注意 x arg 什么都不做,但需要在 across 中使用):

get_transform <- function(x) {
  t <- (needs_transform %>%
          filter(datafield == cur_column()) %>%
          select(transform_to))[[1,1]]
  
  transforms[[t]]
}

clean_data <- data %>%
  mutate(across(
    needs_transform$datafield,
    get_transform
  ))
# Error in `mutate()`:
#   ! Problem while computing `..1 = across(needs_transform$datafield, get_transform)`.
# Caused by error in `across()`:
#   ! Problem while computing column `a`.

几乎完全相同的错误消息。我在这里查看了几个线程,但没有什么与我想要做的完全匹配。任何人都可以帮助让它工作吗?或者这不是一个很好的方法,还有更好的方法吗?

实现您想要的结果的一个选项可能如下所示:

library(tidyverse)
library(lubridate)

trans <- function(x, y) {
  fn_name <- data_meta %>%
    filter(datafield == y) %>%
    pull(transform_to)
  transforms[[fn_name]](x)
}

data %>%
  mutate(across(needs_transform$datafield, ~ trans(.x, cur_column())))
#> # A tibble: 3 × 3
#>   a          b         c
#>   <date>     <chr> <int>
#> 1 2021-09-01 lorem    99
#> 2 2021-09-01 ipsum    98
#> 3 2021-10-01 dolor    97

您也可以试试:

data %>%
  mutate( across(needs_transform$datafield, 
        ~ transforms[[with(data_meta,
            transform_to[datafield == cur_column()])]](.x)))

 a          b         c
  <date>     <chr> <int>
1 2021-09-01 lorem    99
2 2021-09-01 ipsum    98
3 2021-10-01 dolor    97

或偶数:

data %>%
   mutate( across(needs_transform$datafield, 
           ~.x %>% {transforms %>%
             getElement(data_meta %>%
             filter(datafield == cur_column())%>%
             pull(transform_to))}()))