【问题标题】:Merge multiple data tables with duplicate column names合并具有重复列名的多个数据表
【发布时间】:2015-12-08 05:12:22
【问题描述】:

我正在尝试合并(连接)多个数据表(从 5 个 csv 文件中获取 fread)以形成单个数据表。当我尝试合并 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))

merge.data.table 中的错误(..., all = TRUE, by = "x") : x 有一些 重复的列名:y.x,y.y。请删除或重命名 复制并重试。

# 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))

为什么会发生这种情况,有没有办法在不更改任何列名的情况下合并任意数量的具有相同列名的数据表?

【问题讨论】:

  • “工作正常”?祝你好运有两个y.ys 等。
  • @Frank 是的,但我可以在获得合并数据表后使用 setnames()(我总是按特定顺序读取 5 个文件),所以这对我来说不是问题。
  • @srao - 这是个坏主意 - 在加入之前/期间执行 setnames,而不是之后
  • @Frank 是的,x 在所有 DT 中完全相同。没有重复的值。
  • @srao 如果x 对所有这些都完全相同,则不应合并

标签: r join merge duplicates data.table


【解决方案1】:

如果只是这 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 在 cmets 中所说:

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")

【讨论】:

  • 这与mergeall=TRUE 不同
  • 在玩具示例上工作并不奇怪。一旦你添加了 x 的值,这两个值在所有 5 之间并不完全相同,这两者就会出现分歧。mergeall = TRUE 执行外连接,而 [ 执行单侧连接。
  • 你的第一个是又名Reduce(function(X,Y) X[Y], DTlist)
  • @eddi 是的,但是 OP 在 cmets 中说 x 对于所有 DT 来说都是完全相同的。我已经更新了我的答案,以表明使用非具体的 x 值这是行不通的。
  • 好的,不过如果x 完全一样,那么合并就很傻了
【解决方案2】:

如果您想在合并期间重命名,请使用以下方法将计数器保留在 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

【讨论】:

  • 函数定义前后的括号是怎么回事,比如(function()...)()?
  • @Frank 这是一个闭包,外部函数创建一个环境,并返回内部函数,这就是那些括号提取的内容
【解决方案3】:

stack and reshape我不认为这完全映射到 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 所提到的。不过,一般问题很有趣,所以这就是我要在这里攻击的。)

【讨论】:

    【解决方案4】:

    使用重塑可以让您更灵活地命名列。

    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)
    

    【讨论】:

    • bind_rows 之后没有source 列,所以我看到了Error: cannot coerce type 'closure' to vector of type 'character'(因为source 是一个函数)。不确定此问题的解决方法...大概您误用了bind_rows...?
    • .id 功能是 dplyr 0.4.3 中的新功能。是你用的那个版本吗?
    • 不,0.4.2。那必须解释一下。谢谢。
    【解决方案5】:

    另一种方法:

    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”中的包名,以确保调用返回数据表而不是数据框,即使同时加载了“reshape2”包。在不明确提及包名称的情况下,可能会使用 reshape2 包中的 dcast 函数,该函数适用于 data.frame 并返回 data.frame 而不是 data.table。

    【讨论】:

      【解决方案6】:

      或者,您可以 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 不必命名;您已经可以通过1:5 参考它们。此外,您可能想要一个带有setnamesfor 循环,而不是setNames(eddi 在对 q 的评论中提到并由 op 使用)。
      • @Frank 是的,谢谢!我只是想包括这一步,如果 OP 可能想要放置 data.table 名称而不仅仅是数字,那么最终的 data.table 会提供更多信息
      • @Frank 我不明白为什么 for 循环?
      • data.table函数setnames通过引用操作,修改对象本身,所以不需要分配函数的值/结果。
      • @Frank 哦,那是我使用data.tablesetnames 的时候,对.. 好酷!
      猜你喜欢
      • 2017-06-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-12-08
      • 1970-01-01
      • 2021-01-06
      • 1970-01-01
      相关资源
      最近更新 更多