Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Binding list of data.tables by columns, and by reference

Tags:

r

data.table

Say I have the following list:

X = list(data.table(1:2,3:4,5:6), 
         data.table(letters[1:2], letters[3:4]))

and I would like to bind elements of the list by columns in to a single data.table. The output would be the same as produced by

do.call(cbind, X)

However, since my original list and containing data tables are quite large, it would be better if I could do this by reference, rather than copying the whole object with cbind(). Is there a way to do it? Something like rbindlist() equivalent for binding by columns, I've seen this marked as to-do...

Apologies if this simple question is already answered somewhere else, and I've missed the answer.

like image 650
anamaria Avatar asked Jan 19 '18 03:01

anamaria


2 Answers

How about the following?

# check.names = TRUE forces unique names on the output
setDT(
  unlist(X, recursive = FALSE),
  check.names = TRUE
)[]
#    V1 V2 V3 V4 V5
# 1:  1  3  5  a  c
# 2:  2  4  6  b  d
like image 189
MichaelChirico Avatar answered Oct 31 '22 16:10

MichaelChirico


The bind_cols from dplyr seems to be efficient compared to the do.call(cbind and it returns a data.table

library(dplyr)
bind_cols(X)
#   V1 V2 V3 V11 V21
#1:  1  3  5   a   c
#2:  2  4  6   b   d

Benchmarks

set.seed(24)
X1 <- lapply(1:10, function(i)
      as.data.table(matrix(sample(1:9, 1e5*1e3, replace = TRUE), nrow = 1e5, ncol = 1e3)))

system.time({
   bind_cols(X1)
  })
#user  system elapsed 
#   0.01    0.00    0.02 

system.time({
    do.call(cbind, X1)
   })
#user  system elapsed 
#   2.22   37.84   40.93 

system.time({
  setDT(unlist(X1, recursive = FALSE), check.names = TRUE)
  })
#  user  system elapsed 
#   0.05    0.00    0.05 

Or with check.names = FALSE

system.time({
   setDT(unlist(X1, recursive = FALSE), check.names = FALSE)
  })
#  user  system elapsed 
#  0.01    0.00    0.02 

Also based on @MichaelChirico's example data for testing

set.seed(24)
NN <- 1e6
L <- lapply(integer(20L), function(ii) {
    setDT(lapply(integer(sample(15L, 1L)), function(x) rnorm(NN))) }) 

system.time({
   bind_cols(L)
  })
# user  system elapsed 
#      0       0       0 

system.time({
    do.call(cbind, L)
   })
# user  system elapsed 
#   0.44    0.53    0.97 


system.time({
base = L[[1L]]
jj = ncol(base) + 1L
for (ii in 2L:length(L)) {
  for (col_j in seq_len(ncol(L[[ii]]))) {
    set(base, , sprintf('V%d', jj), L[[ii]][[col_j]])
    jj = jj + 1L
  }
}
 })
#user  system elapsed 
#  0.12    0.33    0.46 

and with @MichaelChirico's updated method

system.time({
   setDT(unlist(L, recursive = FALSE), check.names = TRUE)
   })
#  user  system elapsed 
#     0       0       0 
like image 30
akrun Avatar answered Oct 31 '22 15:10

akrun