合并多个列名重复的数据表
Merge multiple data tables with duplicate column names
我正在尝试合并(加入)多个数据 tables(通过 fread 从 5 个 csv 文件中获得)以形成单个数据 table。当我尝试合并 5 个数据 tables 时出现错误,但当我仅合并 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
我有一个解决方法,如果我更改 DT1 的第二列名称:
setnames(DT1, "y", "new_y")
# this works now
Reduce(function(...) merge(..., all = TRUE, by = "x"), list(DT1, DT2, DT3, DT4, DT5))
为什么会发生这种情况,有什么方法可以在不更改任何列名的情况下将任意数量的数据 table 与相同的列名合并?
如果只是这 5 个数据表(其中 x
对所有数据表都相同),您还可以使用嵌套连接:
# 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")
使用整形可以让您更灵活地命名列。
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)
堆叠和重塑 我不认为这与 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
(我不确定这是否完全扩展到其他情况。很难说,因为 OP 的示例确实不需要 merge
的全部功能。在 OP 的情况下,mycols="x"
和 x
在所有 DT*
中都是相同的,显然合并是不合适的,正如@eddi 所提到的。不过,一般问题很有趣,所以这就是我在这里试图攻击的问题。 )
如果您想在合并期间重命名,这里有一种将计数器保留在 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
或者,您可以 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
另一种方法:
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" 中的包名称以确保调用 returns 数据 table 而不是数据帧,即使 "reshape2" 包加载为出色地。在不明确提及包名称的情况下,可能会使用来自 reshape2 包的 dcast 函数,它适用于 data.frame 和 returns a data.frame 而不是 data.table.
我正在尝试合并(加入)多个数据 tables(通过 fread 从 5 个 csv 文件中获得)以形成单个数据 table。当我尝试合并 5 个数据 tables 时出现错误,但当我仅合并 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
我有一个解决方法,如果我更改 DT1 的第二列名称:
setnames(DT1, "y", "new_y")
# this works now
Reduce(function(...) merge(..., all = TRUE, by = "x"), list(DT1, DT2, DT3, DT4, DT5))
为什么会发生这种情况,有什么方法可以在不更改任何列名的情况下将任意数量的数据 table 与相同的列名合并?
如果只是这 5 个数据表(其中 x
对所有数据表都相同),您还可以使用嵌套连接:
# 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")
使用整形可以让您更灵活地命名列。
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)
堆叠和重塑 我不认为这与 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
(我不确定这是否完全扩展到其他情况。很难说,因为 OP 的示例确实不需要 merge
的全部功能。在 OP 的情况下,mycols="x"
和 x
在所有 DT*
中都是相同的,显然合并是不合适的,正如@eddi 所提到的。不过,一般问题很有趣,所以这就是我在这里试图攻击的问题。 )
如果您想在合并期间重命名,这里有一种将计数器保留在 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
或者,您可以 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
另一种方法:
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" 中的包名称以确保调用 returns 数据 table 而不是数据帧,即使 "reshape2" 包加载为出色地。在不明确提及包名称的情况下,可能会使用来自 reshape2 包的 dcast 函数,它适用于 data.frame 和 returns a data.frame 而不是 data.table.