Мне нужно решить проблему с использованием 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, которому я передаю два списка имен столбцов.
Затем я мог бы вычислить сумму каждой соответствующей записи в списке в виде:
map2(list1, list2, ~mutate(sum))
Однако я не могу понять, как лучше всего решить эту проблему с помощью purrr. Я новичок в использовании purrr, поэтому был бы очень признателен за любую помощь по этому вопросу.
Я вижу, что ответ был отредактирован, чтобы отразить вышеуказанный запрос. Из-за отсутствия аккуратного решения ... Я думаю, может быть что-то вроде транспонирования group_by, например. slice_by ???
Всем большое спасибо. Я использовал классический подход tidyverse: group_by, gather, spread и подведение итогов (очень похоже на то, что было предложено ниже "Lorenzo G" и "G. Grothendieck" в ответе № 1). Я никогда не работал с slice_by, но думаю, это тоже подойдет. Я хотел использовать картографический подход, чтобы сделать код еще короче и стандартизованным, и решение, предложенное «akrun», идеально соответствует этой потребности. Еще раз спасибо!





Если вам нравится использовать базовый подход 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, чтобы включить его в свой повседневный рабочий процесс.
names(df) %>% sub("\\d+$", "", .) %>% для многих столбцов согласно решению @docendodiscimus
@StephenHenderson Да, это хороший способ. Здесь я подумал, что если буквы стоят только в первой позиции, мы можем использовать substr
Для хакерского аккуратного решения проверьте это:
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
Создано 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")])
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
Станут ли имена столбцов ...
aa1, aa2, ab1, ab2и т. д. После того, как у вас будет 54 столбца?