Подтвердить что ты не робот

Сумма последних значений по группам

Для каждой строки моих данных я бы хотел вычислить сумму последнего value для каждого group:

dt = data.table(group = c('a','b','a','a','b','a'),
                value = c(10, 5, 20, 15, 15, 10),
                desired = c(10, 15, 25, 20, 30, 25))
#   group value desired
#1:     a    10      10
#2:     b     5      15
#3:     a    20      25  # latest value of a is 20, of b is 5
#4:     a    15      20  # latest value of a is 15, of b is 5
#5:     b    15      30
#6:     a    10      25
Столбец

desired - это то, чего я хочу достичь, и я могу сделать это с наивным циклом, но мои данные довольно велики с большим количеством строк и групп (1M + строк, 1000+ групп).

for (i in seq_len(nrow(dt))) {
  # can use `set` to make this faster, but still too slow
  # this is just to illustrate *a* solution
  dt[i, desired1 := dt[1:i, value[.N], by = group][, sum(V1)]]
}
4b9b3361

Ответ 1

Еще более простая логика от @eddi (по комментариям), уменьшающая круговую линию, показанную ниже:

dt[, incr := diff(c(0, value)), by = group][, ans := cumsum(incr)]

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

# I hope I got the desired output correctly
require(data.table)
dt = data.table(group = c('a','b','c','a','a','b','c','a'),
                value = c(10, 5, 20, 25, 15, 15, 30, 10),
                desired = c(10, 15, 35, 50, 40, 50, 60, 55))

Добавьте rleid:

dt[, id := rleid(group)]

Извлеките последнюю строку для каждого group, id:

last = dt[, .(value=value[.N]), by=.(group, id)]

last будет иметь уникальный id. Теперь идея состоит в том, чтобы получить приращение для каждого id, а затем присоединиться + обновить назад.

last = last[, incr := value - shift(value, type="lag", fill=0L), by=group
          ][, incr := cumsum(incr)-value][]

Присоединиться + обновить сейчас:

dt[last, ans := value + i.incr, on="id"][, id := NULL][]
#    group value desired ans
# 1:     a    10      10  10
# 2:     b     5      15  15
# 3:     c    20      35  35
# 4:     a    25      50  50
# 5:     a    15      40  40
# 6:     b    15      50  50
# 7:     c    30      60  60
# 8:     a    10      55  55

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


Сравнение по 500 группам с 10 000 строк с решением Дэвида:

require(data.table)
set.seed(45L)
groups = apply(matrix(sample(letters, 500L*10L, TRUE), ncol=10L), 1L, paste, collapse="")
uniqueN(groups) # 500L
N = 1e4L
dt = data.table(group=sample(groups, N, TRUE), value = sample(100L, N, TRUE))

arun <- function(dt) {

    dt[, id := rleid(group)]
    last = dt[, .(value=value[.N]), by=.(group, id)]
    last = last[, incr := value - shift(value, type="lag", fill=0L), by=group
              ][, incr := cumsum(incr)-value][]
    dt[last, ans := value + i.incr, on="id"][, id := NULL][]
    dt$ans
}

david <- function(dt) {
    dt[, indx := .I]
    res <- dcast(dt, indx ~ group)
    for (j in names(res)[-1L]) 
        set(res, j = j, value = res[!is.na(res[[j]])][res, on = "indx", roll = TRUE][[j]])
    rowSums(as.matrix(res)[, -1], na.rm = TRUE)

}

system.time(ans1 <- arun(dt))  ## 0.024s
system.time(ans2 <- david(dt)) ## 38.97s 
identical(ans1, as.integer(ans2))
# [1] TRUE

Ответ 2

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

library(zoo)
result <- rep(0, nrow(dt))
for(g in dt[, unique(group)]) {
  result <- result + dt[, na.fill(na.locf(ifelse(group==g, 1, NA)*value, na.rm=F), 0)]
}

all(dt[, desired] == result)

Ответ 3

используя dplyr, работает для многих групп, но данные не должны быть таблицей данных.

library(dplyr)
library(tidyr)
library(zoo)
dt %>% 
  mutate(row_number = row_number()) %>%
  spread(group, value) %>%
  arrange(row_number) %>%
  mutate_each(funs(na.locf(., na.rm = FALSE))) %>%
  mutate(answer = rowSums(.[,-1:-2], na.rm = T))

Использование вышеуказанной функции в примере данных (уведомление data.frame() not data.table():

dt = data.frame(group = c('a','b','a','a','b','a'),
                value = c(10, 5, 20, 15, 15, 10),
                desired = c(10, 15, 25, 20, 30, 25))
  desired row_number  a  b answer
1      10          1 10 NA     10
2      15          2 10  5     15
3      25          3 20  5     25
4      20          4 15  5     20
5      30          5 15 15     30
6      25          6 10 15     25

dt = data.frame(group = c('a','b','c','a','a','b','c','a'),
                value = c(10, 5, 20, 25, 15, 15, 30, 10),
                desired = c(10, 15, 35, 50, 40, 50, 60, 55))

  desired row_number  a  b  c answer
1      10          1 10 NA NA     10
2      15          2 10  5 NA     15
3      35          3 10  5 20     35
4      50          4 25  5 20     50
5      40          5 15  5 20     40
6      50          6 15 15 20     50
7      60          7 15 15 30     60
8      55          8 10 15 30     55