【问题标题】:dplyr: access column name in mutate_at functiondplyr:在 mutate_at 函数中访问列名
【发布时间】:2025-12-07 09:10:02
【问题描述】:

我想更正 data.frame 中的一列,方法是从其中减去名称几乎相同的另一列,但另一列有后缀。我想为此使用mutate_at 函数。

为了弄清楚这一点,我努力访问 mutate_at 函数部分中的列名,以使用它来访问另一列。

我在下面的一个小例子中展示了这一点,但基本上我想访问当前使用的列的名称.,然后从管道中的数据中选择与@987654323 同名的列@ 但带有后缀(下面是"_new")。

感谢您的帮助!

这是我希望如何做的一个例子 - 但这不起作用。

library(tidyverse)
data("mtcars")
new <- mtcars/4
names(new) <-paste0(names(new),"_new")

df <- bind_cols(mtcars,new)

df %>% 
  mutate_at(.vars = vars(carb,disp),
            .funs = list(corrected = ~ . - df %>% pull(paste0(names(.),"_new"))))

df %>% pull(paste0("carb","_new"))

【问题讨论】:

  • 你不能访问里面的名字

标签: r dplyr


【解决方案1】:

为什么不使用mutate 结合acrosscur_column 而不是使用cur_column 即:

df %>% 
  mutate( across( c(carb,disp), ~ . - pull(df, paste0(cur_column(), "_new") ),  .names = "{.col}_corrected") )

【讨论】:

  • 这很棒——我不知道'cur_column',这是在dplyr 1.0 版之前写的,所以across() 还不存在(我认为)。但这很好地解决了它,谢谢!
【解决方案2】:

正如其他人已经指出的那样,变量名不能在mutate_at 中访问,这对于即将到来的mutate(across()) 也是如此。我将这个问题作为dplyrhere 的功能请求来解决,但显然,这种数据整理任务对于dplyr 来说太专业了。下面我针对这种数据整理问题提供了我最喜欢的解决方法,它包括两个步骤:

  1. 使用 !! rlang::sym() 定义自定义 mutate 函数,以根据变量名称的字符向量生成变量
  2. 使用purrr::reduce 应用此自定义函数。
library(tidyverse)

# your toy data
df <- mtcars %>% 
         as_tibble %>% 
         mutate_all(list(new =~ ./4))

# step 1: generate helper function, in this case a simple `mutate` call

gen_corrected <- function(df, x) {

  mutate(df,
         "{x}_corrected" := !! rlang::sym(x) - !! rlang::sym(str_c(x, "_new"))
  )
}

# step 2:
# use purrr's `reduce` on the vector of vars you want to change
# the vector of variables can be defined in a separate step
# important: you need to set `.init = .`

df %>% 
  purrr::reduce(c('carb', 'disp'), gen_corrected, .init = .)
#> # A tibble: 32 x 24
#>      mpg   cyl  disp    hp  drat    wt  qsec    vs    am  gear  carb mpg_new
#>    <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>   <dbl>
#>  1  21       6  160    110  3.9   2.62  16.5     0     1     4     4    5.25
#>  2  21       6  160    110  3.9   2.88  17.0     0     1     4     4    5.25
#>  3  22.8     4  108     93  3.85  2.32  18.6     1     1     4     1    5.7 
#>  4  21.4     6  258    110  3.08  3.22  19.4     1     0     3     1    5.35
#>  5  18.7     8  360    175  3.15  3.44  17.0     0     0     3     2    4.68
#>  6  18.1     6  225    105  2.76  3.46  20.2     1     0     3     1    4.53
#>  7  14.3     8  360    245  3.21  3.57  15.8     0     0     3     4    3.58
#>  8  24.4     4  147.    62  3.69  3.19  20       1     0     4     2    6.1 
#>  9  22.8     4  141.    95  3.92  3.15  22.9     1     0     4     2    5.7 
#> 10  19.2     6  168.   123  3.92  3.44  18.3     1     0     4     4    4.8 
#> # … with 22 more rows, and 12 more variables: cyl_new <dbl>, disp_new <dbl>,
#> #   hp_new <dbl>, drat_new <dbl>, wt_new <dbl>, qsec_new <dbl>, vs_new <dbl>,
#> #   am_new <dbl>, gear_new <dbl>, carb_new <dbl>, carb_corrected <dbl>,
#> #   disp_corrected <dbl>

reprex package (v0.3.0) 于 2020 年 5 月 21 日创建

In the github issue mention above@Romain Francois 为这个问题提供了另一种解决方法。

【讨论】:

    【解决方案3】:

    我们无法访问. 内部的names mutate_at,因为它是vector 并且没有列名信息。一种选择是map2

    library(purrr)
    library(dplyr)
    library(stringr)
    nm1 <- c('carb', 'disp')
    map_dfc(nm1, ~ df %>%
                    transmute(!!str_c(.x, '_corrected') := 
                        !! rlang::sym(.x) - !! rlang::sym(str_c(.x, "_new"))))%>%
       bind_cols(df, .) %>%
       head
    # mpg cyl disp  hp drat    wt  qsec vs am gear carb mpg_new cyl_new disp_new hp_new drat_new  wt_new qsec_new
    #1 21.0   6  160 110 3.90 2.620 16.46  0  1    4    4   5.250     1.5    40.00  27.50   0.9750 0.65500   4.1150
    #2 21.0   6  160 110 3.90 2.875 17.02  0  1    4    4   5.250     1.5    40.00  27.50   0.9750 0.71875   4.2550
    #3 22.8   4  108  93 3.85 2.320 18.61  1  1    4    1   5.700     1.0    27.00  23.25   0.9625 0.58000   4.6525
    #4 21.4   6  258 110 3.08 3.215 19.44  1  0    3    1   5.350     1.5    64.50  27.50   0.7700 0.80375   4.8600
    #5 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2   4.675     2.0    90.00  43.75   0.7875 0.86000   4.2550
    #6 18.1   6  225 105 2.76 3.460 20.22  1  0    3    1   4.525     1.5    56.25  26.25   0.6900 0.86500   5.0550
    #  vs_new am_new gear_new carb_new carb_corrected disp_corrected
    #1   0.00   0.25     1.00     1.00           3.00         120.00
    #2   0.00   0.25     1.00     1.00           3.00         120.00
    #3   0.25   0.25     1.00     0.25           0.75          81.00
    #4   0.25   0.00     0.75     0.25           0.75         193.50
    #5   0.00   0.00     0.75     0.50           1.50         270.00
    #6   0.25   0.00     0.75     0.25           0.75         168.75
    

    或者我们可以使用for 循环进行更新

    for(nm in nm1) {
                df <- df %>%
                        mutate(!! str_c(nm, '_corrected') := 
                              !! rlang::sym(nm) - !! rlang::sym(str_c(nm, '_new')))
      }
    

    或者使用base Rlapply

     df[paste0(nm1, "_corrected")] <- lapply(nm1, function(nm)
                       df[nm] - df[paste0(nm, "_new")])
    

    【讨论】:

      【解决方案4】:

      您可以在基础 R 中使用 Mapmap2 来自 purrr

      cols <- c('carb', 'disp')
      df[paste0(cols, '_corrected')] <- Map(`-`, df[cols], df[paste0(cols, '_new')])
      

      使用map2

      library(purrr)
      df[paste0(cols, '_corrected')] <- map2(df[cols], df[paste0(cols, '_new')], `-`)
      

      【讨论】:

        【解决方案5】:

        这是一个基本的 R 解决方案。我们可以定义一个函数diff_col,它创建操作,然后使用for循环重复应用该函数来修改数据框。

        diff_col <- function(col, dat){
          dat[[paste0(col, "_corrected")]] <- dat[[col]] - dat[[paste0(col, "_new")]]
          return(dat)
        }
        
        for (name in c("carb", "disp")){
          df <- diff_col(col = name, dat = df)
        } 
        

        【讨论】:

          【解决方案6】:

          我得到了这个答案,并想根据上面提到的技巧来捕捉一个浓缩的解决方案。

          the_clauses <- c("carb", "disp") %>% 
              set_names(., str_c(., "_new")) %>%
              map(.f = ~quo(!!sym(.x)/4))
               
          # Equivalent to: 
          # quos(carb_new = carb/4, disp_new = disp/4)
          
          mtcars %>% mutate(!!!the_clauses)
          

          【讨论】: