R 在 purrr::map 中使用 dplyr::mutate() 而不重复行

R using dplyr::mutate() within purrr::map without duplicating rows

这是数据:

library(tidyverse)
col_pre <- c('a', 'b', 'c')
df <- tibble(a1 = 1:3, a2 = 4:6, b1 = 7:9, b2 = 10:12, c1 = 13:15, c2 = 16:18)

我想使用 purrr::map()dplyr::mutate() 创建三个新列,它们是 df 中列的总和。 我可以使用 map() 迭代 a、b、c 列前缀的向量。我想出了 tidyeval 操作,以便 下面的代码运行没有错误。

out <- col_pre %>%
  map_df(~ df %>% 
            mutate(!!as.name(paste0(.x, '3')) := !!as.name(paste0(.x, '1')) + !!as.name(paste0(.x, '2')))
  )

但是,out 现在有六个虚假行:

     a1    a2    b1    b2    c1    c2    a3    b3    c3
1     1     4     7    10    13    16     5    NA    NA
2     2     5     8    11    14    17     7    NA    NA
3     3     6     9    12    15    18     9    NA    NA
4     1     4     7    10    13    16    NA    17    NA
5     2     5     8    11    14    17    NA    19    NA
6     3     6     9    12    15    18    NA    21    NA
7     1     4     7    10    13    16    NA    NA    29
8     2     5     8    11    14    17    NA    NA    31
9     3     6     9    12    15    18    NA    NA    33

它所做的是不必要地复制输入的三行 df

这是我想要的输出:

     a1    a2    b1    b2   c1    c2    a3     b3    c3
1     1     4     7    10    13    16     5    17    29
2     2     5     8    11    14    17     7    19    31
3     3     6     9    12    15    18     9    21    33

我觉得 purrr::reduce() 可能是解决方案,但我不确定如何应用它。

感谢任何帮助!

我们可以在进行评估之前将字符串转换为 symbol,而不是 mutate 使用 transmute 然后将列与原始数据集绑定

library(stringr)
library(purrr)
library(dplyr)
col_pre %>%
     map_dfc(~ df %>%
           transmute(!! str_c(.x, '3') :=  !! rlang::sym(str_c(.x, '1'))  + 
         !! rlang::sym(str_c(.x, 2)))) %>%
     bind_cols(df, .)
# A tibble: 3 x 9
#    a1    a2    b1    b2    c1    c2    a3    b3    c3
#   <int> <int> <int> <int> <int> <int> <int> <int> <int>
#1     1     4     7    10    13    16     5    17    29
#2     2     5     8    11    14    17     7    19    31
#3     3     6     9    12    15    18     9    21    33

或者另一种选择是 parse_exprs

df %>%
    mutate(!!! rlang::parse_exprs(str_c(sprintf("%s1 + %s2",
           col_pre, col_pre), collapse=";"))) %>% 
   rename_at(vars(contains("+")), ~ str_c(col_pre, 3))
# A tibble: 3 x 9
#     a1    a2    b1    b2    c1    c2    a3    b3    c3
#  <int> <int> <int> <int> <int> <int> <int> <int> <int>
#1     1     4     7    10    13    16     5    17    29
#2     2     5     8    11    14    17     7    19    31
#3     3     6     9    12    15    18     9    21    33

或者另一种选择是使用pivot_longer将其转换为'long'格式,然后进行计算

library(tidyr)
df %>%
   mutate(rn = row_number()) %>%
   pivot_longer(cols = -rn, names_to = c(".value", "group"),
          names_sep ="(?<=[a-z])(?=[0-9])") %>%
   group_by(rn) %>%
   summarise_at(vars(col_pre), list(`3` = sum)) %>% 
   select(-rn) %>%
   bind_cols(df, .)

或者如果我们使用dplyrdevel版本(‘0.8.99.9000’),那么across可以和summarise[=24=一起使用]

df %>%
     mutate(rn = row_number()) %>%
     pivot_longer(cols = -rn, names_to = c(".value", "group"),
           names_sep ="(?<=[a-z])(?=[0-9])") %>%
     group_by(rn) %>%
     summarise(across(col_pre, sum)) %>% 
     select(-rn) %>%
     rename_all(~ str_c(., 3)) %>% 
     bind_cols(df, .)
# A tibble: 3 x 9
#     a1    a2    b1    b2    c1    c2    a3    b3    c3
#  <int> <int> <int> <int> <int> <int> <int> <int> <int>
#1     1     4     7    10    13    16     5    17    29
#2     2     5     8    11    14    17     7    19    31
#3     3     6     9    12    15    18     9    21    33

我们可以使用 map_dfctransmute

library(dplyr)
library(purrr)

bind_cols(df, map_dfc(col_pre, ~df %>% 
       transmute(!!paste0(.x, 3) := !!sym(paste0(.x, 1)) + !!sym(paste0(.x, 2)))))

# A tibble: 3 x 9
#     a1    a2    b1    b2    c1    c2    a3    b3    c3
#  <int> <int> <int> <int> <int> <int> <int> <int> <int>
#1     1     4     7    10    13    16     5    17    29
#2     2     5     8    11    14    17     7    19    31
#3     3     6     9    12    15    18     9    21    33

在基础 R 中,我们可以使用 split.default

df[paste0(col_pre, 3)] <- lapply(split.default(df, 
                          sub('\d', '', names(df))), rowSums)

或者不拆分,我们可以按照@thelatemail 的建议,根据列的起始名称对数据进行子集化

df[paste0(col_pre,3)] <- lapply(col_pre, function(x) 
                        rowSums(df[startsWith(names(df), x)]))