2015-09-11 2 views
10

Я пытаюсь объединить (объединить) несколько таблиц данных (полученных с помощью fread из 5 файлов csv), чтобы сформировать единую таблицу данных. Я получаю сообщение об ошибке при попытке объединить 5 таблиц данных, но работает хорошо, когда я сливаю только 4. MWE ниже:Объединить несколько таблиц данных с одинаковыми именами столбцов

# example data 
DT1 <- data.table(x = letters[1:6], y = 10:15) 
DT2 <- data.table(x = letters[1:6], y = 11:16) 
DT3 <- data.table(x = letters[1:6], y = 12:17) 
DT4 <- data.table(x = letters[1:6], y = 13:18) 
DT5 <- data.table(x = letters[1:6], y = 14:19) 

# this gives an error 
Reduce(function(...) merge(..., all = TRUE, by = "x"), list(DT1, DT2, DT3, DT4, DT5)) 

Error in merge.data.table(..., all = TRUE, by = "x") : x has some duplicated column name(s): y.x,y.y. Please remove or rename the duplicate(s) and try again.

# whereas this works fine 
Reduce(function(...) merge(..., all = TRUE, by = "x"), list(DT1, DT2, DT3, DT4)) 

    x y.x y.y y.x y.y 
1: a 10 11 12 13 
2: b 11 12 13 14 
3: c 12 13 14 15 
4: d 13 14 15 16 
5: e 14 15 16 17 
6: f 15 16 17 18 

У меня есть обходной путь, где, если я меняю 2-й имя столбца для DT1:

setnames(DT1, "y", "new_y") 

# this works now 
Reduce(function(...) merge(..., all = TRUE, by = "x"), list(DT1, DT2, DT3, DT4, DT5)) 

Почему это происходит, и есть ли способ объединить произвольное количество таблиц данных с одинаковыми именами столбцов без изменения какого-либо имен столбцов?

+0

Возможный дубликат http://stackoverflow.com/questions/28378637/reduce-in-r-over-similar-variable-names-causing-error – pcantalupo

+1

«Прекрасно работает»? Удачи вам в двух 'y.y's и т. Д. – Frank

+0

Похоже, что это происходит потому, что функция' Reduce' не может дать новое имя столбца. Другими словами, у вас заканчиваются комбинации 'x' и' y' (существует только 4 возможных сочетания, поэтому ошибка возникает при работе с 5-м столбцом). – bourbaki4481472

ответ

5

Вот aw ау держать счетчик в Reduce, если вы хотите переименовать в процессе слияния:

Reduce((function() {counter = 0 
        function(x, y) { 
         counter <<- counter + 1 
         d = merge(x, y, all = T, by = 'x') 
         setnames(d, c(head(names(d), -1), paste0('y.', counter))) 
        }})(), list(DT1, DT2, DT3, DT4, DT5)) 
# x y.x y.1 y.2 y.3 y.4 
#1: a 10 11 12 13 14 
#2: b 11 12 13 14 15 
#3: c 12 13 14 15 16 
#4: d 13 14 15 16 17 
#5: e 14 15 16 17 18 
#6: f 15 16 17 18 19 
+0

Что происходит с круглыми скобками и после определения функции, например '(function() ...)()'? – Frank

+2

@Frank это закрытие, внешняя функция создает среду и возвращает внутреннюю функцию, которая является тем, что эти скобки извлекают – eddi

5

Если это как раз те 5 DataTables (где x является одинаковым для всех DataTables), вы также можете использовать вложенные присоединяется:

# set the key for each datatable to 'x' 
setkey(DT1,x) 
setkey(DT2,x) 
setkey(DT3,x) 
setkey(DT4,x) 
setkey(DT5,x) 

# the nested join 
mergedDT1 <- DT1[DT2[DT3[DT4[DT5]]]] 

Или, как сказал @Frank в комментариях:

DTlist <- list(DT1,DT2,DT3,DT4,DT5) 
Reduce(function(X,Y) X[Y], DTlist) 

, которая дает:

x y1 y2 y3 y4 y5 
1: a 10 11 12 13 14 
2: b 11 12 13 14 15 
3: c 12 13 14 15 16 
4: d 13 14 15 16 17 
5: e 14 15 16 17 18 
6: f 15 16 17 18 19 

Это дает тот же результат, как:

mergedDT2 <- Reduce(function(...) merge(..., all = TRUE, by = "x"), list(DT1, DT2, DT3, DT4, DT5)) 

> identical(mergedDT1,mergedDT2) 
[1] TRUE 

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

DT1[DT2[DT3[DT4[DT5[DT6]]]]] 

это дает:

x y1 y2 y3 y4 y5 y6 
1: b 11 12 13 14 15 15 
2: c 12 13 14 15 16 16 
3: d 13 14 15 16 17 17 
4: e 14 15 16 17 18 18 
5: f 15 16 17 18 19 19 
6: g NA NA NA NA NA 20 

Хотя:

Reduce(function(...) merge(..., all = TRUE, by = "x"), list(DT1, DT2, DT3, DT4, DT5, DT6)) 

дает:

x y1 y2 y3 y4 y5 y6 
1: a 10 11 12 13 14 NA 
2: b 11 12 13 14 15 15 
3: c 12 13 14 15 16 16 
4: d 13 14 15 16 17 17 
5: e 14 15 16 17 18 18 
6: f 15 16 17 18 19 19 
7: g NA NA NA NA NA 20 

Б данные:

Для того, чтобы сделать код с Reduce работы, я изменил имена y столбцов.

DT1 <- data.table(x = letters[1:6], y1 = 10:15) 
DT2 <- data.table(x = letters[1:6], y2 = 11:16) 
DT3 <- data.table(x = letters[1:6], y3 = 12:17) 
DT4 <- data.table(x = letters[1:6], y4 = 13:18) 
DT5 <- data.table(x = letters[1:6], y5 = 14:19) 

DT6 <- data.table(x = letters[2:7], y6 = 15:20, key="x") 
+0

это не то же самое, что и 'merge' с' all = TRUE' – eddi

+0

@eddi см. Обновление – Jaap

+1

Ничего удивительного в этом не работает на примере игрушек. Эти два будут расходиться, если вы добавите значения 'x', которые не все одинаковы между всеми 5.' merge' с 'all = TRUE' выполняет внешнее объединение, тогда как' ['делает односторонний. – eddi

2

Использование переделки дает вам большую гибкость в том, как вы хотите назвать свои столбцы. не

library(dplyr) 
library(tidyr) 

list(DT1, DT2, DT3, DT4, DT5) %>% 
    bind_rows(.id = "source") %>% 
    mutate(source = paste("y", source, sep = ".")) %>% 
    spread(source, y) 

Или это будет работать

library(dplyr) 
library(tidyr) 

list(DT1 = DT1, DT2 = DT2, DT3 = DT3, DT4 = DT4, DT5 = DT5) %>% 
    bind_rows(.id = "source") %>% 
    mutate(source = paste(source, "y", sep = ".")) %>% 
    spread(source, y) 
+0

После 'bind_rows' нет столбца' source', поэтому я вижу 'Error: не могу принудить тип 'закрывать' к вектору типа 'character'' (поскольку' source' является функцией). Не уверен, исправить это ... предположительно вы злоупотребляете 'bind_rows' ...? – Frank

+0

Функция .id является новой в dplyr 0.4.3. Это версия, которую вы используете? – bramtayl

+0

Нет, 0.4.2. Это должно объяснить это. Благодарю. – Frank

4

стек и изменить Я не думаю, что карты именно функции merge но ...

mycols <- "x" 
DTlist <- list(DT1,DT2,DT3,DT4,DT5) 

dcast(rbindlist(DTlist,idcol=TRUE), paste0(paste0(mycols,collapse="+"),"~.id")) 

# x 1 2 3 4 5 
# 1: a 10 11 12 13 14 
# 2: b 11 12 13 14 15 
# 3: c 12 13 14 15 16 
# 4: d 13 14 15 16 17 
# 5: e 14 15 16 17 18 
# 6: f 15 16 17 18 19 

у меня нет смысл, если бы это распространилось на большее количество столбцов, чем y.

слияние правопреемника

DT <- Reduce(function(...) merge(..., all = TRUE, by = mycols), 
    lapply(DTlist,`[.noquote`,mycols)) 

for (k in seq_along(DTlist)){ 
    js = setdiff(names(DTlist[[k]]), mycols) 
    DT[DTlist[[k]], paste0(js,".",k) := mget(paste0("i.",js)), on=mycols, by=.EACHI] 
} 

# x y.1 y.2 y.3 y.4 y.5 
# 1: a 10 11 12 13 14 
# 2: b 11 12 13 14 15 
# 3: c 12 13 14 15 16 
# 4: d 13 14 15 16 17 
# 5: e 14 15 16 17 18 
# 6: f 15 16 17 18 19 

(я не уверен, что это в полной мере распространяется и на другие случаи. Трудно сказать, потому что пример Ора действительно не требует полной функциональности merge. В дело OP, в с mycols="x" и x быть одинаковыми во всех DT*, очевидно, слияние неуместно, как было упомянуто @eddi. общая задача интересна, хотя, так это то, что я пытаюсь атаковать здесь.)

1

В качестве альтернативы вы можете setNames для колонок до и делать merge как этот

dts = list(DT1, DT2, DT3, DT4, DT5) 
names(dts) = paste('DT', c(1:5), sep = '')  

dtlist = lapply(names(dts),function(i) 
     setNames(dts[[i]], c('x', paste('y',i,sep = '.')))) 

Reduce(function(...) merge(..., all = T), dtlist) 

# x y.DT1 y.DT2 y.DT3 y.DT4 y.DT5 
#1: a 10 11 12 13 14 
#2: b 11 12 13 14 15 
#3: c 12 13 14 15 16 
#4: d 13 14 15 16 17 
#5: e 14 15 16 17 18 
#6: f 15 16 17 18 19 
+0

Fyi, нет необходимости указывать имена 'dts'; вы уже можете обратиться к ним с помощью «1: 5». Кроме того, вам, вероятно, нужен цикл 'for' с' setnames', а не 'setNames' (упомянутый eddi в комментарии к q и используется op). – Frank

+1

@Frank да спасибо! Я просто подумал о том, чтобы включить этот шаг, если OP может захотеть поместить имена данных.table вместо просто чисел, таким образом, конечная таблица данных будет более информативной. –

+0

@Frank Я не понял, почему для цикла? –

2

Другой способ сделать это:

dts <- list(DT1, DT2, DT3, DT4, DT5) 

names(dts) <- paste("y", seq_along(dts), sep="") 
data.table::dcast(rbindlist(dts, idcol="id"), x ~ id, value.var = "y") 

# x y1 y2 y3 y4 y5 
#1: a 10 11 12 13 14 
#2: b 11 12 13 14 15 
#3: c 12 13 14 15 16 
#4: d 13 14 15 16 17 
#5: e 14 15 16 17 18 
#6: f 15 16 17 18 19 

Добавлен имя пакета в «data.table :: dcast», чтобы гарантировать, что вызов возвращает таблицу данных, а не данные f rame, даже если пакет reshape2 также загружен. Без упоминания имени пакета явно можно использовать функцию dcast из пакета reshape2, которая работает на data.frame и возвращает data.frame вместо data.table.

Смежные вопросы