cumsum для уникального значения с использованием dplyr mutate

Фиктивный набор данных:

data <- data.frame(
  id = c(1,1,2,2,3,4,5,6),
  value = c(10,10,20,20,10,30,40,50),
  other = c(1,2,3,4,5,6,7,8)
)

Данные были выведены из group_by(id) операции в dplyr трубе. Каждый id связан не более чем с одним значением, и два разных id могут иметь одно и то же значение. Мне нужно найти совокупную сумму по идентификаторам, добавив новый столбец: cum_col = c(10,10,30,30,40,70,110,160) cumsum в mutate найдет совокупную сумму по всему столбцу значений и не выберет только одно значение для каждой группы. summarise бесполезен, поскольку есть другие столбцы, которые мне нужно сохранить.

Есть ли выход, не используя summarise, а потом join назад? Или, пожалуйста, укажите мне ссылку, если на нее уже ответили.

Изменить: просто для информации фактические данные имеют ~ 2 миллиона строк и 100 столбцов.


person Kaur    schedule 13.11.2017    source источник
comment
Вам нужен только dplyr ответ или вы открыты для других вариантов? Кроме того, в группе всегда будет только один уникальный value?   -  person Ronak Shah    schedule 13.11.2017
comment
Только dplyr, поскольку я использую канал для выполнения пары других мутаций и операций в одном блоке кода   -  person Kaur    schedule 13.11.2017
comment
Да, у группы всегда будет одно уникальное значение   -  person Kaur    schedule 13.11.2017


Ответы (3)


Альтернативой может быть вложить фрейм данных в столбец id, вычислить кумулятивную сумму и затем развернуть:

data %>% 
    group_by(id) %>% nest() %>% 
    mutate(cum_col = cumsum(sapply(data, function(dat) dat$value[1]))) %>% 
    unnest() 

# A tibble: 8 x 4
#     id cum_col value other
#  <dbl>   <dbl> <dbl> <dbl>
#1     1      10    10     1
#2     1      10    10     2
#3     2      30    20     3
#4     2      30    20     4
#5     3      40    10     5
#6     4      70    30     6
#7     5     110    40     7
#8     6     160    50     8

Сравните с summarize и join:

summarise_f <- function(data) data %>% 
    group_by(id) %>% 
    summarise(val = first(value)) %>%
    mutate(cum_col = cumsum(val)) %>%
    select(-val) %>%
    inner_join(data, by="id")

nest_f <- function(data) data %>% 
    group_by(id) %>% nest() %>% 
    mutate(cum_col = cumsum(sapply(data, function(dat) dat$value[1]))) %>% 
    unnest() 

df <- bind_rows(rep(list(data), 100000))

microbenchmark::microbenchmark(summarise_f(df), nest_f(df))
#Unit: milliseconds
#            expr       min        lq     mean    median        uq      max neval
# summarise_f(df)  79.78891  89.65753 117.8480  93.56766  99.97694 277.3773   100
#      nest_f(df) 191.10597 208.07364 280.2466 225.65567 369.20202 524.5106   100

Summarize, а затем join на самом деле быстрее.

С большим набором данных:

df <- bind_rows(rep(list(data), 1000000))
microbenchmark::microbenchmark(summarise_f(df), nest_f(df))
#Unit: milliseconds
#            expr       min        lq      mean    median       uq      max neval
# summarise_f(df)  819.5588  905.2136  993.4916  961.1797 1040.947 1480.391   100
#      nest_f(df) 1768.3060 1992.6753 2069.1454 2057.3091 2162.440 2501.715   100
person Psidom    schedule 13.11.2017
comment
Я думаю, вы также можете избежать дополнительных накладных расходов при использовании параметра summarise - например, data %>% distinct(id,value) %>% mutate(cum_col=cumsum(value)) %>% select(-value) %>% inner_join(data, by="id"). - person thelatemail; 13.11.2017
comment
@Psidom @thelatemail Спасибо за ответ. Внутреннее соединение означает, что мне нужно сохранить копию данных для последующего присоединения. но эти данные создаются как часть конвейера в dplyr. Меняли несколько раз между ними. original_data %>% group_by (some columns) %>% mutate(add columns) %>% filter(conditions) %>% group_by(other columns) %>% mutate( calculate cumsum on one of other columns) %>% carry on with rest of operations nest хотя медленный может быть лучшим вариантом, чтобы избежать необходимости сохранять данные для присоединения позже - person Kaur; 13.11.2017
comment
@thelatemail Хороший вариант. Примерно так же быстро, как summarize. - person Psidom; 13.11.2017
comment
@Psidom - в ваших больших df у вас по-прежнему всего 6 групп. Я думаю, если вы сделаете это намного больше, group_by может вызвать относительное замедление. - person thelatemail; 13.11.2017
comment
@thelatemail Согласен. Просто найти воспроизводимый пример немного сложнее. - person Psidom; 13.11.2017
comment
@Kaur Напишите суммирующую функцию, как указано выше, а затем соедините ее после ваших каналов. Это может быть одним из способов избежать сохранения промежуточных данных. - person Psidom; 13.11.2017
comment
Замечательно! есть несколько вариантов сейчас. Спасибо - person Kaur; 13.11.2017

Другой альтернативой является создание фиктивного столбца (cols), который имеет только первые value на группу, а остальные заменяются на 0, а затем мы берем cumsum по всему столбцу.

library(dplyr)
data %>%
  group_by(id) %>%
  mutate(cols = c(value[1], rep(0, n() -1))) %>%
  ungroup() %>%
  mutate(cum_col = cumsum(cols)) %>%
  select(-cols)


# A tibble: 8 x 4
#     id value other cum_col
#  <dbl> <dbl> <dbl>   <dbl>
#1     1    10     1      10
#2     1    10     2      10
#3     2    20     3      30
#4     2    20     4      30
#5     3    10     5      40
#6     4    30     6      70
#7     5    40     7     110
#8     6    50     8     160
person Ronak Shah    schedule 13.11.2017
comment
Спасибо, способ кажется простым и умным. Но не уверен в производительности. Увидим - person Kaur; 13.11.2017

Мы могли бы также использовать duplicated

library(dplyr)
data %>%
     mutate(cum_col = cumsum(value*!duplicated(id)))
#  id value other cum_col
#1  1    10     1      10
#2  1    10     2      10
#3  2    20     3      30
#4  2    20     4      30
#5  3    10     5      40
#6  4    30     6      70
#7  5    40     7     110
#8  6    50     8     160
person akrun    schedule 13.11.2017