Как использовать карту из 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()

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

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

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

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

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

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

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

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

Станут ли имена столбцов ... aa1, aa2, ab1, ab2 и т. д. После того, как у вас будет 54 столбца?

Stephen Henderson 13.04.2018 14:17

Я вижу, что ответ был отредактирован, чтобы отразить вышеуказанный запрос. Из-за отсутствия аккуратного решения ... Я думаю, может быть что-то вроде транспонирования group_by, например. slice_by ???

Stephen Henderson 13.04.2018 14:34

Всем большое спасибо. Я использовал классический подход tidyverse: group_by, gather, spread и подведение итогов (очень похоже на то, что было предложено ниже "Lorenzo G" и "G. Grothendieck" в ответе № 1). Я никогда не работал с slice_by, но думаю, это тоже подойдет. Я хотел использовать картографический подход, чтобы сделать код еще короче и стандартизованным, и решение, предложенное «akrun», идеально соответствует этой потребности. Еще раз спасибо!

user30276 16.04.2018 07:52
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
15
3
15 555
8
Перейти к ответу Данный вопрос помечен как решенный

Ответы 8

Если вам нравится использовать базовый подход 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))

1) дплёр / тидыр Преобразование в полную форму, резюмирование и преобразование обратно в широкую форму:

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) база с краном

Используя 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)) в приведенном выше выражении, если имена расположены не в порядке возрастания.

Ответ принят как подходящий

Вот один вариант с 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

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

user30276 16.04.2018 07:57
names(df) %>% sub("\\d+$", "", .) %>% для многих столбцов согласно решению @docendodiscimus
Stephen Henderson 16.04.2018 12:42

@StephenHenderson Да, это хороший способ. Здесь я подумал, что если буквы стоят только в первой позиции, мы можем использовать substr

akrun 16.04.2018 12:44

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

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)

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

Подобно тому, что я бы тоже сделал. Хороший подход. Спасибо!

user30276 16.04.2018 07:54

Другое решение, которое разбивает 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

Создано 13.04.2018 пользователем пакет REPEX (v0.2.0).

в базе 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

Если вы хотите сделать его расширяемым до произвольной функции - Map(`+`, df[endsWith(names(df),"1")], df[endsWith(names(df),"2")])

thelatemail 23.08.2018 01:12
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

Обновлено:

В случае, если столбцов много, и вы хотите применить это программно:

row_sums <- function(x) {
  transmute(df, !! paste0("sum_", quo_name(x)) := pmap_dbl(select(df, starts_with(x)), sum))
}

newdf <- map_dfc(letters[1:3], row_sums)
newdf

  sum_a sum_b sum_c
1    10     7    25
2    12     9    27
3    14    11    29
4    16    13    31
5    18    15    33

И при необходимости вы можете добавить исходные переменные с помощью:

bind_cols(df, dfnew)

  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(unique(gsub("\\d+","", colnames(df))), function(li) {
   set_names(data.frame(V = apply(df[grep(li, colnames(df), val = T)], FUN = sum, MARGIN = 1)), paste0("sum_", li))
}))
#  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

Другие вопросы по теме