dplyr:在 mutate_at 函数中访问列名

7

我想通过减去另一个具有几乎相同名称的列来更正数据框中的一列,但该其他列具有后缀。我想使用 mutate_at 函数来实现此目的。

在尝试解决问题时,我努力访问 mutate_at 中函数部分的列名称,以便使用它来访问其他列。

我在下面的小例子中展示了这一点,但基本上我想访问目前使用的列名 .,然后从管道数据中选择一个与 . 相同但具有后缀的列(在下面是 "_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"))


你无法访问名称的内部。 - akrun
6个回答

8

使用mutate_at不如使用mutateacross以及cur_column组合,例如:

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

4
太棒了!我之前不知道有“cur_column”,而且这是在dplyr 1.0版本之前编写的,所以across()函数还不存在(我想)。但是这个方法很好地解决了问题,谢谢! - Moritz Schwarz

3

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

  1. 使用!! rlang::sym()定义一个自定义的变异函数,以生成基于变量名称的字符向量的变量
  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>

本文创建于2020年5月21日,使用reprex package (v0.3.0)

在上述github问题中提到的,@Romain Francois提供了另一种解决此问题的方法。


2

我们无法访问mutate_at.names,因为它是一个向量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")])

1
您可以在基础R中使用Map或来自purrrmap2进行此操作:
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')], `-`)

1
这里有一个基于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)
} 

0

我看到这个答案后,想根据上面提到的技巧来总结一种简洁的解决方案。

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)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接