Как разгруппировать столбцы списка в data.table?

tidyr предоставляет unnest, которые помогают расширять столбцы списка.

Это похоже на гораздо (в 20 раз) более быструю функцию ungroup в kdb.

Я ищу аналогичную (но гораздо более быструю) функцию, которая, предполагая, что data.table содержит несколько столбцов списка, каждый с одинаковым количеством элементов в каждой строке, расширяла бы data.table.

Это расширение этого сообщения.

library(data.table)
library(tidyr)
t = Sys.time()
DT = data.table(a=c(1,2,3),
                b=c('q','w','e'),
                c=list(rep(t,2),rep(t+1,3),rep(t,0)),
                d=list(rep(1,2),rep(20,3),rep(1,0)))

print(DT)
   a b                                                           c        d
1: 1 q                     2016-01-09 09:55:14,2016-01-09 09:55:14      1,1
2: 2 w 2016-01-09 09:55:15,2016-01-09 09:55:15,2016-01-09 09:55:15 20,20,20
3: 3 e                                                                     

print(unnest(DT))
Source: local data frame [5 x 4]

      a     b                   c     d
  (dbl) (chr)              (time) (dbl)
1     1     q 2016-01-09 09:55:14     1
2     1     q 2016-01-09 09:55:14     1
3     2     w 2016-01-09 09:55:15    20
4     2     w 2016-01-09 09:55:15    20
5     2     w 2016-01-09 09:55:15    20

Вот моя собственная попытка... она кажется в 2 раза быстрее, но должна быть значительно улучшена...

dtUngroup <- function(DT){
  colClasses <- lapply(DT,FUN=class)
  listCols <- colnames(DT)[colClasses=='list']
  if(length(listCols)>0){
    nonListCols <- setdiff(colnames(DT),listCols)
    nbListElem <- unlist(DT[,lapply(.SD,FUN=lengths),.SDcols=(listCols[1L])])
    DT1 <- DT[,lapply(.SD,FUN=rep,times=(nbListElem)),.SDcols=(nonListCols)]
    DT1[,(listCols):=DT[,lapply(.SD,FUN=function(x) do.call('c',x)),.SDcols=(listCols)]]
    return(DT1)
  }
  return(DT)
} 
dtUngroup(DT)[]
   a b                   c  d
1: 1 q 2016-01-09 09:55:14  1
2: 1 q 2016-01-09 09:55:14  1
3: 2 w 2016-01-09 09:55:15 20
4: 2 w 2016-01-09 09:55:15 20
5: 2 w 2016-01-09 09:55:15 20

person statquant    schedule 09.01.2016    source источник
comment
хорошо, удалите из списка POSIXct... не стесняйтесь набрасывать ответ...   -  person statquant    schedule 09.01.2016
comment
Вы можете сделать его еще короче: DT[, lapply(.SD, unlist), by = 1:nrow(DT)]   -  person Jaap    schedule 09.01.2016
comment
Используя numberAsPOSIXct() в пакете oce и идею Яапа, возможно, вам нужно следующее: DT[, lapply(.SD, unlist), by = 1:nrow(DT)][, c := numberAsPOSIXct(c)][]   -  person jazzurro    schedule 09.01.2016
comment
Почему это, а не FUN=function(x) do.call('c',x) ? Я согласен, что могу сэкономить 2 строки... но намного ли это быстрее?   -  person statquant    schedule 09.01.2016
comment
@jazzurro, просто используя asPOSIXct, тоже работает   -  person Jaap    schedule 09.01.2016
comment
@Jaap Я вижу. Спасибо за это. :)   -  person jazzurro    schedule 09.01.2016


Ответы (1)


С использованием:

na.omit(DT[, lapply(.SD, unlist), a][, c := as.POSIXct(c, origin="1970-01-01")])

дает:

   a b                   c  d
1: 1 q 2016-01-09 12:17:24  1
2: 1 q 2016-01-09 12:17:24  1
3: 2 w 2016-01-09 12:17:25 20
4: 2 w 2016-01-09 12:17:25 20
5: 2 w 2016-01-09 12:17:25 20

Когда значения в столбце a не уникальны для каждой строки, вы можете использовать:

na.omit(DT[, lapply(.SD, unlist), by=1:nrow(DT)][, c := as.POSIXct(c, origin="1970-01-01")])

Ориентир:

> microbenchmark(dtUngroup(DT)[], jaap())
Unit: milliseconds
            expr      min       lq     mean   median       uq      max neval cld
 dtUngroup(DT)[] 3.935677 4.005596 4.189208 4.066196 4.227372 6.750338   100   b
          jaap() 1.977175 2.039830 2.094536 2.074314 2.132525 2.309848   100  a 
person Jaap    schedule 09.01.2016
comment
Мне нужно было бы обернуть это в функцию для обработки нескольких столбцов POSIXct, и я совершенно уверен, что будет та же история со многими другими типами (например, data.table:::ITime) - person statquant; 09.01.2016
comment
@statquant см. здесь для примера - person Jaap; 09.01.2016
comment
на самом деле никакие классы из столбцов списка не должны были бы динамически перерабатываться, это не просто выполнение DT[,c:=as.whatever(c)]. Итак, я поддерживаю вас, но я не думаю, что на вопрос дан ответ: 1. мы не получаем требуемый результат (даже если это всего лишь шаг вперед) 2. нет эталона 3. это решение теряет тип столбцов (или не предоставляйте решение для нескольких столбцов) - person statquant; 09.01.2016