2016-06-09 36 views
30

per ogni riga dei miei dati mi piacerebbe per calcolare la somma di più recente value per ogni group:Somma dei valori più recenti tra gruppi

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 colonna è ciò che voglio raggiungere, e ho può farlo con un ciclo ingenuo, ma i miei dati sono abbastanza grandi con un sacco di righe e gruppi (1M + righe, 1000+ gruppi).

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)]] 
} 

risposta

18

logica Ancora più semplice da @eddi (sotto i commenti) riducendo la rotonda quello mostrato qui sotto:

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

Non sono sicuro di come si estende a più gruppi, ma ecco un esempio su dati con 3 gruppi:

# 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)) 

Aggiungi un rleid:

dt[, id := rleid(group)] 

Estrarre l'ultima riga per ogni group, id:

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

last avrà unico id. Ora l'idea è di ottenere l'incremento per ogni id, quindi unire + aggiornare di nuovo.

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

Registrati + Update:

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 

Non ne sono ancora sicuro dove/se questo si rompe .. guarderà con attenzione ora. L'ho scritto immediatamente in modo che ci siano più occhi su di esso.


Confrontando su 500 gruppi con 10.000 righe con la soluzione di David:

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 
+6

Questo è grande, grazie io sono! un po 'confuso riguardo il join e il rleid - non solo 'dt [, incr: = diff (c (0, valore)), per = gruppo] [, ans: = cumsum (incr)]' lavoro? (I' Non sono sicuro se mi manca qualche pezzo della logica) – eddi

+0

Oh sì, penso che avrebbe funzionato! Quella strada rotonda si riduce al tuo one-liner – Arun

+2

Wow che è un nice one-liner ... – andrew

2

Vorrei creare una colonna per ciascun gruppo che mostri l'ultimo valore per quel gruppo. Poi basta sommare quelle colonne:

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) 
+0

risposta Aggiornato per accaparrarsene 1e3 + gruppi – andrew

+4

modo troppo lento, ma grazie – eddi

-2

utilizzando dplyr, lavora per molti gruppi, ma i dati non devono essere tabella di dati.

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)) 

Utilizzando la funzione di cui sopra sui dati di esempio (notare data.frame() non 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 
+8

Ciò richiede troppa memoria/è troppo lento. – eddi