I have a simple data.table as follows-
ID = c(rep("A", 1000), rep("B", 1000), rep("C", 1000), rep("D", 1000))
val = c("a", "a", "a", "b", "b", "c", "c","d","d","d","d","e","e","f","f","g","g","g","g","g")
dt = data.table(ID, val)
I want to add a new column to this data.table which will have the lag of val by group ID.
Here is the expected output
> head(dt, 20)
ID val val_lag
1: A a <NA>
2: A a <NA>
3: A a <NA>
4: A b a
5: A b a
6: A c b
7: A c b
8: A d c
9: A d c
10: A d c
11: A d c
12: A e d
13: A e d
14: A f e
15: A f e
16: A g f
17: A g f
18: A g f
19: A g f
20: A g f
The current solution I am using is -
dt[, val_lag := with(rle(val), rep(c(NA, head(values, -1)), lengths)), by = ID]
However, this solution is super slow on the actual dataset, which is very large and has millions of rows. Is there any faster way to solve this problem?
Following is the performance result of all methods discussed in this post -
microbenchmark::microbenchmark(rles = dt[, val_lag1 := with(rle(val), rep(c(NA, head(values, -1)), lengths)), by = ID],
chinsoon = dt[, val_lag := shift(val)[nafill(replace(seq.int(.N), rowid(rleid(val)) > 1L, NA_integer_), "locf")], by = ID],
TiC = dt[, val_lag3 := c(NA,rle(val)$values)[cumsum(c(0,head(val,-1)!=tail(val,-1)))+1], by = ID],
times = 1000
)
Unit: milliseconds
expr min lq mean median uq max neval cld
rles 1.549548 1.781014 2.750187 2.096805 2.743668 46.65326 1000 a
chinsoon 1.766827 2.060233 3.059109 2.379477 3.077080 67.16040 1000 a
TiC 1.986808 2.226933 3.472451 2.624236 3.397165 60.67802 1000 b
Thanks!