Как использовать map from purrr с dplyr:: mutate для создания нескольких новых столбцов на основе пар столбцов

у меня есть следующая проблема с использованием R. короче говоря, я хочу создать несколько новых столбцов в фрейме данных на основе вычислений различных пар столбцов в фрейме данных.

выглядит следующим образом:

df <- data.frame(a1 = c(1:5), 
                 b1 = c(4:8), 
                 c1 = c(10:14), 
                 a2 = c(9:13), 
                 b2 = c(3:7), 
                 c2 = c(15:19))
df
a1 b1 c1 a2 b2 c2
1  4 10  9  3 15
2  5 11 10  4 16
3  6 12 11  5 17
4  7 13 12  6 18
5  8 14 13  7 19

вывод должен выглядеть следующим образом:

a1 b1 c1 a2 b2 c2 sum_a sum_b sum_c
1  4 10  9  3 15    10     7    25
2  5 11 10  4 16    12     9    27
4  7 13 12  6 18    16    13    31
5  8 14 13  7 19    18    15    33

Я могу добиться этого, используя dplyr, выполняя некоторую ручную работу следующим образом:

df %>% rowwise %>% mutate(sum_a = sum(a1, a2),
                          sum_b = sum(b1, b2),
                          sum_c = sum(c1, c2)) %>% 
  as.data.frame()

Итак, что делается: возьмите столбцы с буквой "a" в нем, вычислить rowwise сумму, и создать новый столбец с суммой по имени sum_[письмо]. Повторите для столбцов с разными буквами.

это работает, однако, если у меня есть большой набор данных С 300 различными парами столбцов, ручной ввод будет значительным, так как мне придется писать 300 мутирующих вызовов.

недавно я наткнулся на пакет R "муррр" , и я предполагаю, что это решит мою проблему делать то, что я хочу, более автоматизированным способом.

In в частности, я бы подумал, что могу использовать purrr: map2, к которому я передаю два списка имен столбцов.

  • list1 = все столбцы с номером 1 в нем
  • list2 = все столбцы с номером 2 в нем

тогда я мог бы вычислить сумму каждой соответствующей записи списка в виде:

map2(list1, list2, ~mutate(sum))

однако я не могу понять, как лучше всего подойти к этой проблеме, используя purrr. Я довольно новичок в использовании purrr, поэтому я бы действительно ценю любую помощь по этому вопросу.

7 ответов


вот один из вариантов с purrr. Мы получаем unique код names набора данных ('nm1'), используйте map (от purrr) для перебора уникальных имен,select столбец matches значение префикса "nm1", добавьте строки с помощью reduce и привязка столбцов (bind_cols) с исходным набором данных

library(tidyverse)
nm1 <- names(df) %>% 
          substr(1, 1) %>%
          unique 
nm1 %>% 
     map(~ df %>% 
            select(matches(.x)) %>%
            reduce(`+`)) %>%
            set_names(paste0("sum_", nm1)) %>%
     bind_cols(df, .)
#    a1 b1 c1 a2 b2 c2 sum_a sum_b sum_c
#1  1  4 10  9  3 15    10     7    25
#2  2  5 11 10  4 16    12     9    27
#3  3  6 12 11  5 17    14    11    29
#4  4  7 13 12  6 18    16    13    31
#5  5  8 14 13  7 19    18    15    33

Если вам нравится рассматривать базовый подход R, вот как вы могли бы это сделать:

cbind(df, lapply(split.default(df, substr(names(df), 0,1)), rowSums))
#  a1 b1 c1 a2 b2 c2  a  b  c
#1  1  4 10  9  3 15 10  7 25
#2  2  5 11 10  4 16 12  9 27
#3  3  6 12 11  5 17 14 11 29
#4  4  7 13 12  6 18 16 13 31
#5  5  8 14 13  7 19 18 15 33

он разбивает столбец данных на список, основанный на первой букве каждого имени столбца (a, b или c).

Если у вас есть большое количество столбцов и вам нужно различать все символы, кроме чисел в конце каждого имени столбца, вы можете изменить подход к:

cbind(df, lapply(split.default(df, sub("\d+$", "", names(df))), rowSums))

в базе R все векторизовано:

nms <- names(df)
df[paste0("sum_",unique(gsub("[1-9]","",nms)))] <-
  df[endsWith(nms,"1")] + df[endsWith(nms,"2")]

#   a1 b1 c1 a2 b2 c2 sum_a sum_b sum_c
# 1  1  4 10  9  3 15    10     7    25
# 2  2  5 11 10  4 16    12     9    27
# 3  3  6 12 11  5 17    14    11    29
# 4  4  7 13 12  6 18    16    13    31
# 5  5  8 14 13  7 19    18    15    33

df %>% 
  mutate(sum_a = pmap_dbl(select(., starts_with("a")), sum), 
         sum_b = pmap_dbl(select(., starts_with("b")), sum),
         sum_c = pmap_dbl(select(., starts_with("c")), sum))

  a1 b1 c1 a2 b2 c2 sum_a sum_b sum_c
1  1  4 10  9  3 15    10     7    25
2  2  5 11 10  4 16    12     9    27
3  3  6 12 11  5 17    14    11    29
4  4  7 13 12  6 18    16    13    31
5  5  8 14 13  7 19    18    15    33

для хакерского аккуратного решения проверьте это:

library(tidyr)
library(dplyr)

df %>% 
   rownames_to_column(var = 'row') %>% 
   gather(a1:c2, key = 'key', value = 'value') %>% 
   extract(key, into = c('col.base', 'col.index'), regex = '([a-zA-Z]+)([0-9]+)') %>% 
   group_by(row, col.base) %>% 
   summarize(.sum = sum(value)) %>%
   spread(col.base, .sum) %>% 
   bind_cols(df, .) %>% 
   select(-row)

в принципе, я собираю все пары столбцов с их значениями во всех строках, разделяю имя столбца на две части, вычисляю суммы строк для столбцов с одной и той же буквой и возвращаю ее в широкую форму.


еще одно решение, которое раскалывает df по номерам, чем использовать Reduce вычислить sum

library(tidyverse)

df %>% 
  split.default(., substr(names(.), 2, 3)) %>% 
  Reduce('+', .) %>% 
  set_names(paste0("sum_", substr(names(.), 1, 1))) %>% 
  cbind(df, .)

#>   a1 b1 c1 a2 b2 c2 sum_a sum_b sum_c
#> 1  1  4 10  9  3 15    10     7    25
#> 2  2  5 11 10  4 16    12     9    27
#> 3  3  6 12 11  5 17    14    11    29
#> 4  4  7 13 12  6 18    16    13    31
#> 5  5  8 14 13  7 19    18    15    33

создано 2018-04-13 reprex пакет (версии v0.2.0).


1) dplyr/tidyr преобразовать в длинную форму, суммировать и преобразовать обратно в широкую форму:

library(dplyr)
library(tidyr)

DF %>%
  mutate(Row = 1:n()) %>%
  gather(colname, value, -Row) %>%
  group_by(g = gsub("\d", "", colname), Row) %>%
  summarize(sum = sum(value)) %>%
  ungroup %>%
  mutate(g = paste("sum", g, sep = "_")) %>%
  spread(g, sum) %>%
  arrange(Row) %>%
  cbind(DF, .) %>%
  select(-Row)

даем:

  a1 b1 c1 a2 b2 c2 sum_a sum_b sum_c
1  1  4 10  9  3 15    10     7    25
2  2  5 11 10  4 16    12     9    27
3  4  7 13 12  6 18    16    13    31
4  5  8 14 13  7 19    18    15    33

2) База с использованием матричного умножения

nms представляет собой вектор имен столбцов без цифр и предваряется sum_. u является вектором уникальных его элементов. Сформируйте логическую матрицу, используя outer от того, что при умножении на DF дает суммы -- журналы получают преобразованы в 0-1 когда это будет сделано. Наконец, привяжите его к входу.

nms <- gsub("(\D+)\d", "sum_\1", names(DF))
u <- unique(nms)
sums <- as.matrix(DF) %*% outer(nms, setNames(u, u), "==")
cbind(DF, sums)

даем:

  a1 b1 c1 a2 b2 c2 sum_a sum_b sum_c
1  1  4 10  9  3 15    10     7    25
2  2  5 11 10  4 16    12     9    27
3  4  7 13 12  6 18    16    13    31
4  5  8 14 13  7 19    18    15    33

3) база с tapply

используя nms из (2) применить tapply к каждой строке:

cbind(DF, t(apply(DF, 1, tapply, nms, sum)))

даем:

  a1 b1 c1 a2 b2 c2 sum_a sum_b sum_c
1  1  4 10  9  3 15    10     7    25
2  2  5 11 10  4 16    12     9    27
3  4  7 13 12  6 18    16    13    31
4  5  8 14 13  7 19    18    15    33

вы можете заменить nms на factor(nms, levels = unique(nms)) в приведенном выше выражении, если имена не в порядке возрастания.