cbind определенные столбцы из нескольких data.tables эффективно

У меня есть список data.tables, которые мне нужно связать, однако мне нужны только последние X столбцов.

Мои данные структурированы следующим образом:

DT.1 <- data.table(x=c(1,1), y = c("a","a"), v1 = c(1,2), v2 = c(3,4))
DT.2 <- data.table(x=c(1,1), y = c("a","a"), v3 = c(5,6))
DT.3 <- data.table(x=c(1,1), y = c("a","a"), v4 = c(7,8), v5 = c(9,10), v6 = c(11,12))

DT.list <- list(DT.1, DT.2, DT.3)
>DT.list
    [[1]]
   x y v1 v2
1: 1 a  1  3
2: 1 a  2  4

[[2]]
   x y v3
1: 1 a  5
2: 1 a  6

[[3]]
   x y v4 v5 v6
1: 1 a  7  9 11
2: 1 a  8 10 12

Столбцы x и y одинаковы для каждой таблицы data.tables, но количество столбцов различается. Выходные данные не должны содержать повторяющихся столбцов x и y. Это должно выглядеть следующим образом:

   x y v1 v2 v3 v4 v5 v6
1: 1 a  1  3  5  7  9 11
2: 1 a  2  4  6  8 10 12

Я хочу избежать использования цикла. Я могу связать data.tables с помощью do.call("cbind", DT.list), а затем удалить дубликаты вручную, но есть ли способ, при котором дубликаты вообще не создаются? Кроме того, важна эффективность, поскольку списки могут быть длинными с большими таблицами данных.

спасибо


person greyBag    schedule 15.07.2015    source источник
comment
Вы имеете в виду, что количество столбцов отличается, верно?   -  person nicola    schedule 15.07.2015
comment
Если я правильно понял, вы можете попробовать: do.call(cbind,DT.list)[,.SD,.SDcols=unique(unlist(lapply(DT.list,names)))]   -  person nicola    schedule 15.07.2015
comment
@nicola Я думаю, что OP в первую очередь пытается избежать создания дубликатов.   -  person David Arenburg    schedule 15.07.2015
comment
Или другой вариант !duplicated т.е. dt1 <- do.call(cbind,DT.list);dt1[!duplicated(colnames(dt1))]   -  person akrun    schedule 15.07.2015


Ответы (3)


Вот как это можно сделать за один раз, используя lapply() для удаления столбцов x и y из второй и последующих таблиц данных перед вызовом cbind():

do.call(cbind,c(DT.list[1],lapply(DT.list[2:length(DT.list)],`[`,j=-c(1,2))));
##    x y v1 v2 v3 v4 v5 v6
## 1: 1 a  1  3  5  7  9 11
## 2: 1 a  2  4  6  8 10 12

Другой подход заключается в удалении столбцов x и y из второй и последующих таблиц данных перед выполнением прямого cbind(). Я думаю, что нет ничего плохого в том, чтобы использовать для этого цикл for:

for (i in seq_along(DT.list)[-1]) DT.list[[i]][,c('x','y')] <- NULL;
DT.list;
## [[1]]
##    x y v1 v2
## 1: 1 a  1  3
## 2: 1 a  2  4
##
## [[2]]
##    v3
## 1:  5
## 2:  6
##
## [[3]]
##    v4 v5 v6
## 1:  7  9 11
## 2:  8 10 12
##
do.call(cbind,DT.list);
##    x y v1 v2 v3 v4 v5 v6
## 1: 1 a  1  3  5  7  9 11
## 2: 1 a  2  4  6  8 10 12
person bgoldst    schedule 15.07.2015

Вот еще один способ:

Reduce(
  function(x,y){
    newcols = setdiff(names(y),names(x))
    x[,(newcols)] <- y[, ..newcols]
    x
  }, 
  DT.list,
  init = copy(DT.list[[1]][,c("x","y")])
)
#    x y v1 v2 v3 v4 v5 v6
# 1: 1 a  1  3  5  7  9 11
# 2: 1 a  2  4  6  8 10 12

Это позволяет избежать изменения списка (как это делает назначение <- NULL @bgoldst) или создания копий каждого элемента списка (как, я думаю, делает подход lapply). Тем не менее, я бы, вероятно, сделал <- NULL в большинстве практических приложений.

person Frank    schedule 15.07.2015
comment
Одна вещь, которая кажется мне ошибкой: я должен сделать x[,(newcols):=y[,newcols,with=FALSE]] вместо x[,(newcols)] <- y[,newcols,with=FALSE], но это дает ошибку в версии 1.9.4. - person Frank; 15.07.2015

Другой вариант — использовать параметр функции индексирования [,, внутри lapply в списке таблиц данных и исключить «ненужные» столбцы (в вашем случае x и y). Таким образом, дубликаты столбцов не создаются.

# your given test data
DT.1 <- data.table(x=c(1,1), y = c("a","a"), v1 = c(1,2), v2 = c(3,4))
DT.2 <- data.table(x=c(1,1), y = c("a","a"), v3 = c(5,6))
DT.3 <- data.table(x=c(1,1), y = c("a","a"), v4 = c(7,8), v5 = c(9,10), v6 = c(11,12))
DT.list <- list(DT.1, DT.2, DT.3)

A) используя вектор символов, чтобы указать, какие столбцы исключить

# cbind a list of subsetted data.tables
exclude.col <- c("x","y")
myDT <- do.call(cbind, lapply(DT.list, `[`,,!exclude.col, with = FALSE))
myDT
##    v1 v2 v3 v4 v5 v6
## 1:  1  3  5  7  9 11
## 2:  2  4  6  8 10 12
# join x & y columns for final results
cbind(DT.list[[1]][,.(x,y)], myDT)
##    x y v1 v2 v3 v4 v5 v6
## 1: 1 a  1  3  5  7  9 11
## 2: 1 a  2  4  6  8 10 12

B) то же, что и выше, но с использованием вектора символов непосредственно в lapply

myDT <- do.call(cbind, lapply(DT.list, `[`,,!c("x","y")))
myDT
##    v1 v2 v3 v4 v5 v6
## 1:  1  3  5  7  9 11
## 2:  2  4  6  8 10 12
# join x & y columns for final results
cbind(DT.list[[1]][,.(x,y)], myDT)
##    x y v1 v2 v3 v4 v5 v6
## 1: 1 a  1  3  5  7  9 11
## 2: 1 a  2  4  6  8 10 12

В) то же, что и выше, но все в одну строку

do.call( cbind, c(list(DT.list[[1]][,.(x,y)]), lapply(DT.list, `[`,,!c("x","y"))) )
# way too many brackets...but I think it works
##    x y v1 v2 v3 v4 v5 v6
## 1: 1 a  1  3  5  7  9 11
## 2: 1 a  2  4  6  8 10 12
person Valentin    schedule 26.09.2017