在 `dplyr` 的 `across()` 中评估 constants/scalars

Evaluating constants/scalars inside `dplyr`'s `across()`

在我看来,这似乎是一件相当容易的事情,但我在尝试让它发挥作用时遇到了很多麻烦。

我想通过 mutate()across() 做的只是让一些变量在自定义函数中计算为常量或标量。

我将使用非across() 实现来演示:

library(dplyr, warn.conflicts = FALSE)

vars <- c("mpg", "cyl")

Test_Function <- function(.data, .vars) {
    
    Data_Frame <- .data %>% 
        
        mutate(
            !!.vars[1] := "Something", 
            !!.vars[2] := 2L
        ) %>% 
        
        select({{.vars}}) %>% 
        
        as_tibble()
    
    return(Data_Frame)
}

mtcars %>% Test_Function(vars)
#> # A tibble: 32 x 2
#>    mpg         cyl
#>    <chr>     <int>
#>  1 Something     2
#>  2 Something     2
#>  3 Something     2
#>  4 Something     2
#>  5 Something     2
#>  6 Something     2
#>  7 Something     2
#>  8 Something     2
#>  9 Something     2
#> 10 Something     2
#> # ... with 22 more rows

reprex package (v2.0.1)

于 2021-08-11 创建

当我试图通过 across() 完成同样的事情时,我无法让它工作:

library(dplyr, warn.conflicts = FALSE)

vars <- c("mpg", "cyl")

Test_Function <- function(.data, .vars) {
    
    Data_Frame <- 
        
        .data %>%
        
        mutate(
            across(!!.vars, "Something"), # Doesn't work
            across(!!.vars, ~ .x := "Something"), # purrr-style doesn't work
            across(!!.vars, ~ assign(.x, "Something")), # purrr-style with assign() doesn't work
            across(!!.vars, assign, "Something") # Regular assign() doesn't work
        ) %>%
        
        select({{.vars}}) %>%
        
        as_tibble()
    
    return(Data_Frame)
}

mtcars %>% Test_Function(vars)

顺便说一句,我希望我可以提供 across() 一些 .data 中不存在的变量,这样我就可以轻松地创建 new 列而无需必须在 mutate() 内手动完成,但截至撰写本文时还行不通(还?)。

编辑:

@MrFlick/@JonSpring 和@TimTeaFan 给出了对我有用的建议;前者仅用于一个值,后者用于值列表。我将为我所使用的两者提供最少的代表。谢谢大家的帮助!

@MrFlick/@JonSpring的建议,返回单个值:

library(dplyr, warn.conflicts = FALSE)

vars <- c("mpg", "cyl")

Test_Function <- function(.data, .vars) {
    
    Data_Frame <- 
        
        .data %>%
        
        mutate(
            across(!!.vars, ~ "Something"), # I was so close to this initially, I just missed the tilde.
        ) %>%
        
        select({{.vars}}) %>%
        
        as_tibble()
    
    return(Data_Frame)
}

mtcars %>% Test_Function(vars)
#> # A tibble: 32 x 2
#>    mpg       cyl      
#>    <chr>     <chr>    
#>  1 Something Something
#>  2 Something Something
#>  3 Something Something
#>  4 Something Something
#>  5 Something Something
#>  6 Something Something
#>  7 Something Something
#>  8 Something Something
#>  9 Something Something
#> 10 Something Something
#> # ... with 22 more rows

reprex package (v2.0.1)

于 2021-08-12 创建

现在@TimTeaFan 的建议:

library(dplyr, warn.conflicts = FALSE)

varlist <- 
    
    list(
        "mpg" = "Something",
        "cyl" = 2L
    )

vars <- c("mpg", "cyl")

Test_Function <- function(.data, .varlist, .vars) {
    
    Data_Frame <- 
        
        .data %>%
        
        mutate(
            !!! .varlist # I haven't really undstood the big-bang operator (!!!) before now, so this was a great demonstration!
        ) %>%
        
        select(!!.vars) %>%
        
        as_tibble()
    
    return(Data_Frame)
}

mtcars %>% Test_Function(varlist, vars)
#> # A tibble: 32 x 2
#>    mpg         cyl
#>    <chr>     <int>
#>  1 Something     2
#>  2 Something     2
#>  3 Something     2
#>  4 Something     2
#>  5 Something     2
#>  6 Something     2
#>  7 Something     2
#>  8 Something     2
#>  9 Something     2
#> 10 Something     2
#> # ... with 22 more rows

reprex package (v2.0.1)

于 2021-08-12 创建

如果您只想将标量分配给一个变量,那么您既不需要 across 也不需要函数来这样做。您可以只定义一个列表并在 dplyr::mutate 内使用三重爆炸运算符,或者更接近您的情况, dplyr::transmute.

library(dplyr, warn.conflicts = FALSE)

vars <- list("mpg" = "something",
             "cyl" = 2L)

mtcars <- head(mtcars)

mtcars %>% 
  transmute(!!! vars)

#>                         mpg cyl
#> Mazda RX4         something   2
#> Mazda RX4 Wag     something   2
#> Datsun 710        something   2
#> Hornet 4 Drive    something   2
#> Hornet Sportabout something   2
#> Valiant           something   2

如果您想以类似于 across 的方式以编程方式创建新列,那么一个简单的 {tidyverse} 技巧是在 dplyr::mutate 中使用 purrr::map_dfc。请注意,在下面的示例中,您可以根据其他列名使用任何其他函数甚至表达式:

library(purrr)

mtcars %>%
  transmute(map_dfc(vars, ~ .x))

#>                         mpg cyl
#> Mazda RX4         something   2
#> Mazda RX4 Wag     something   2
#> Datsun 710        something   2
#> Hornet 4 Drive    something   2
#> Hornet Sportabout something   2
#> Valiant           something   2

我在 github、{dplyover} 上也有一个包,它有一个函数 over,它使用 dplyr::across 的语法循环创建类似于 purrr::map_dfc。您可以使用 .names 参数即时创建列名。

library(dplyover) # https://github.com/TimTeaFan/dplyover

mtcars %>% 
  transmute(over(vars, ~ .x,
                 .names = "{x}_new"))

#>                     mpg_new cyl_new
#> Mazda RX4         something       2
#> Mazda RX4 Wag     something       2
#> Datsun 710        something       2
#> Hornet 4 Drive    something       2
#> Hornet Sportabout something       2
#> Valiant           something       2

reprex package (v2.0.1)

于 2021-08-11 创建