Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Distinct sequence pattern with counts

I have a sequence that looks like this

  id ep value
1  1  1     a
2  1  2     a
3  1  3     b
4  1  4     d
5  2  1     a
6  2  2     a
7  2  3     c
8  2  4     e

and what I want to do is to reduce it to

      id    ep  value     n  time total
1      1     0      a     2    20    40
2      1     1      b     1    10    40
3      1     2      d     1    10    40
4      2     0      a     2    20    40
5      2     1      c     1    10    40
6      2     2      e     1    10    40

The dplyrseems to be working ok

short = df %>% group_by(id) %>%
 mutate(grp = cumsum(value != lag(value, default = value[1]))) %>%
  count(id, grp, value) %>% mutate(time = n*10) %>% group_by(id) %>% 
   mutate(total = sum(time))

However, my database is really big and it takes forever.

Question 1

Could anyone help me to translate this line into data.table code?

Question 2

I am interested also then in going back to the long format and I am wondering what is the most efficient solution in terms of speed.

At the moment, I am using this line

short[rep(1:nrow(short), short$n), ] %>% 
  select(-n, -time, -total) %>% 
  group_by(id) %>% 
  mutate(ep = 1:n())

Any suggestions?

df = structure(list(id = structure(c(1L, 1L, 1L, 1L, 2L, 2L, 2L, 2L
), .Label = c("1", "2"), class = "factor"), ep = structure(c(1L, 
2L, 3L, 4L, 1L, 2L, 3L, 4L), .Label = c("1", "2", "3", "4"), class = "factor"), 
value = structure(c(1L, 1L, 2L, 4L, 1L, 1L, 3L, 5L), .Label = c("a", 
"b", "c", "d", "e"), class = "factor")), .Names = c("id", 
"ep", "value"), row.names = c(NA, -8L), class = "data.frame")
like image 506
giac Avatar asked Sep 27 '26 14:09

giac


1 Answers

An option would be to use rleid from data.table

library(data.table)
short1 <- setDT(df)[,  .N,.(id, grp = rleid(value), value)
           ][,  time := N*10
            ][, c('total', 'ep') :=  .(sum(time), seq_len(.N) - 1), id
             ][, grp := NULL][]
short1
#   id value N time total ep
#1:  1     a 2   20    40  0
#2:  1     b 1   10    40  1
#3:  1     d 1   10    40  2
#4:  2     a 2   20    40  0
#5:  2     c 1   10    40  1
#6:  2     e 1   10    40  2

Deriving the 'long' format would be

short1[rep(seq_len(.N), N), -c('N', 'time', 'total', 'ep'), 
             with = FALSE][, ep1 := seq_len(.N), id][]

The direct translation of the dplyr code into data.table would be

setDT(df)[, grp := cumsum(value != shift(value, fill = value[1])), id
   ][, .(N= .N), .(id, grp, value)
    ][, time := N*10
     ][, c('total', 'ep') :=  .(sum(time), seq_len(.N) - 1), id
       ][, grp := NULL][]
#   id value N time total ep
#1:  1     a 2   20    40  0
#2:  1     b 1   10    40  1
#3:  1     d 1   10    40  2
#4:  2     a 2   20    40  0
#5:  2     c 1   10    40  1
#6:  2     e 1   10    40  2
like image 93
akrun Avatar answered Sep 29 '26 02:09

akrun