【问题标题】:Data.table - left outer join on multiple tablesData.table - 多个表的左外连接
【发布时间】:2015-07-17 16:35:37
【问题描述】:

假设你有类似的数据

fruits <- data.table(FruitID=c(1,2,3), Fruit=c("Apple", "Banana", "Strawberry"))
colors <- data.table(ColorID=c(1,2,3,4,5), FruitID=c(1,1,1,2,3), Color=c("Red","Yellow","Green","Yellow","Red"))
tastes <- data.table(TasteID=c(1,2,3), FruitID=c(1,1,3), Taste=c("Sweeet", "Sour", "Sweet"))

setkey(fruits, "FruitID")
setkey(colors, "ColorID")
setkey(tastes, "TasteID")

fruits
   FruitID      Fruit
1:       1      Apple
2:       2     Banana
3:       3 Strawberry

colors
   ColorID FruitID  Color
1:       1       1    Red
2:       2       1 Yellow
3:       3       1  Green
4:       4       2 Yellow
5:       5       3    Red

tastes
   TasteID FruitID  Taste
1:       1       1 Sweeet
2:       2       1   Sour
3:       3       3  Sweet

我通常需要对这样的数据执行左外连接。例如,“给我所有的水果和它们的颜色”需要我写(也许有更好的方法?)

setkey(colors, "FruitID")
result <- colors[fruits, allow.cartesian=TRUE]
setkey(colors, "ColorID")

这样一个简单而频繁的任务,三行代码显得多余,所以我写了一个方法myLeftJoin

myLeftJoin <- function(tbl1, tbl2){
  # Performs a left join using the key in tbl1 (i.e. keeps all rows from tbl1 and only matching rows from tbl2)

  oldkey <- key(tbl2)
  setkeyv(tbl2, key(tbl1))
  result <- tbl2[tbl1, allow.cartesian=TRUE]
  setkeyv(tbl2, oldkey)
  return(result)
}

我可以像这样使用

myLeftJoin(fruits, colors)
   ColorID FruitID  Color      Fruit
1:       1       1    Red      Apple
2:       2       1 Yellow      Apple
3:       3       1  Green      Apple
4:       4       2 Yellow     Banana
5:       5       3    Red Strawberry

如何扩展此方法,以便可以将任意数量的表传递给它并获得所有表的链式左外连接?类似myLeftJoin(tbl1, ...)

例如,我希望myleftJoin(fruits, colors, tastes) 的结果等于

setkey(colors, "FruitID")
setkey(tastes, "FruitID")
result <- tastes[colors[fruits, allow.cartesian=TRUE], allow.cartesian=TRUE]
setkey(tastes, "TasteID")
setkey(colors, "ColorID")

result
   TasteID FruitID  Taste ColorID  Color      Fruit
1:       1       1 Sweeet       1    Red      Apple
2:       2       1   Sour       1    Red      Apple
3:       1       1 Sweeet       2 Yellow      Apple
4:       2       1   Sour       2 Yellow      Apple
5:       1       1 Sweeet       3  Green      Apple
6:       2       1   Sour       3  Green      Apple
7:      NA       2     NA       4 Yellow     Banana
8:       3       3  Sweet       5    Red Strawberry

也许我错过了使用 data.table 包中的方法的优雅解决方案?谢谢

(编辑:修正了我的数据中的一个错误)

【问题讨论】:

    标签: r data.table


    【解决方案1】:

    我刚刚在data.table, v1.9.5 中提交了一个新功能,我们可以使用它加入而不需要设置键(即直接指定要加入的列,而不必先使用setkey()):

    有了它,这很简单:

    require(data.table) # v1.9.5+
    fruits[tastes, on="FruitID"][colors, on="FruitID"] # no setkey required
    #    FruitID      Fruit TasteID  Taste ColorID  Color
    # 1:       1      Apple       1 Sweeet       1    Red
    # 2:       1      Apple       2   Sour       1    Red
    # 3:       1      Apple       1 Sweeet       2 Yellow
    # 4:       1      Apple       2   Sour       2 Yellow
    # 5:       1      Apple       1 Sweeet       3  Green
    # 6:       1      Apple       2   Sour       3  Green
    # 7:       2         NA      NA     NA       4 Yellow
    # 8:       3 Strawberry       3  Sweet       5    Red
    

    【讨论】:

    • 嗨阿伦。在扩展场景中,我们有 N 个 data.tables 的列表(而不是这个线程中的 3 个)和相同的目标(在指定变量上全部连接),有没有比链接 N 个表达式更方便的方法?
    • @J.G.,是的,只需使用 for 循环。假设 'a'、'b' 和 'c' 是您的表并加入 col 'x':ans &lt;- copy(a); for (el in list(b, c)) ans &lt;- ans[el, on="x"]
    【解决方案2】:

    您可以同时使用基本 R 的 Reduceleft_join (来自 dplyrdata.table 对象列表,因为您正在加入具有通用列名的表和愿意避免为data.table 对象多次设置keys

    library(data.table) # <= v1.9.4
    library(dplyr) # left_join
    
    Reduce(function(...) left_join(...), list(fruits,colors,tastes))
    
    # Source: local data table [8 x 6]
    
    #  FruitID      Fruit ColorID  Color TasteID  Taste
    #1       1      Apple       1    Red       1 Sweeet
    #2       1      Apple       1    Red       2   Sour
    #3       1      Apple       2 Yellow       1 Sweeet
    #4       1      Apple       2 Yellow       2   Sour
    #5       1      Apple       3  Green       1 Sweeet
    #6       1      Apple       3  Green       2   Sour
    #7       2     Banana       4 Yellow      NA     NA
    #8       3 Strawberry       5    Red       3  Sweet
    

    @Frank 提到的另一个使用纯 data.table 方法的选项 (注意,这需要将所有 data.table 对象的键设置为 fruitID

    library(data.table) # <= v1.9.4
    Reduce(function(x,y) y[x, allow.cartesian=TRUE], list(fruits,colors,tastes))
    

    【讨论】:

    • np,顺便说一句,您可能使用的是较旧的 data.table 版本,因为我认为不需要 allow.cartesian=TRUE
    • @eddi 是的,我和 OP 都需要 allow.cartesian(如上所示)
    • 感谢您的解决方案。当我运行Reduce(function(x,y) y[x, allow.cartesian=TRUE], list(fruits,colors,tastes)) 时,我只得到三行输出。应该是 8。 - 哦等等 - 只需阅读“假设所有键都设置为fruitID”部分,但这正是我想要避免的。
    • @Ben 那么你到底想做什么?如果您想控制要加入的列,那么您可以在left_join 中使用by,就像Reduce(function(...) left_join(..., by = "FruitID"), list(fruits,colors,tastes)) 一样。顺便说一句,这两种解决方案的结果都完全符合您在问题中提到的期望输出
    • @VeerendraGadekar 虽然 Frank 的解决方案有效,但每次我想使用它时,我的代码中都需要多个 setkey 语句。我正在寻找一种基于 data.table 的解决方案来避免该问题。 (如果您想以 10 种不同的方式离开 10 个不同的表,则想象所有的 setkey 语句。)
    猜你喜欢
    • 2015-03-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多