Pol*_*ase 4 join r dplyr data.table
我正在学习data.table.我很难转换dplyr连接语法.您能为以下测试用例推荐data.table等价吗?
library(data.table)
library(dplyr)
dtProduct <- data.table(
ProductID = c(6, 33, 17, 88, 44, 51),
ProductName= c("Shirt", "Helmet", "Gloves", "Towel", "Chair", "Detergent"),
Price= c(25, 60, 10, 7.5, 135, 16),
key = 'ProductID'
)
set.seed(20141216)
dtOrder <- data.table(
OrderID = sample(1001:9999, 12),
CustomerID = sample(271:279, 12, replace=TRUE),
# NOTE: some non-existent ProductID intentionally introduced
ProductID = sample(c(dtProduct[, ProductID], 155, 439), 12, replace=TRUE),
Qty = sample(1:3, 12, replace=TRUE),
key = 'OrderID'
)
> tables()
NAME NROW NCOL MB COLS KEY
[1,] dtOrder 12 4 1 OrderID,CustomerID,ProductID,Qty OrderID
[2,] dtProduct 6 3 1 ProductID,ProductName,Price ProductID
> dtProduct
ProductID ProductName Price
1: 6 Shirt 25.0
2: 17 Gloves 10.0
3: 33 Helmet 60.0
4: 44 Chair 135.0
5: 51 Detergent 16.0
6: 88 Towel 7.5
> dtOrder
OrderID CustomerID ProductID Qty
1: 1651 275 6 3
2: 2726 272 88 2
3: 3079 275 88 2
4: 3168 274 17 1
5: 4816 277 88 1
6: 4931 278 51 1
7: 5134 274 439 2
8: 5265 272 33 3
9: 7702 275 33 2
10: 7727 279 155 2
11: 8412 273 88 2
12: 9130 271 17 3
Run Code Online (Sandbox Code Playgroud)
案例1:显示订单明细,不匹配产品ID被隐藏
dtOrder %>%
inner_join(dtProduct, by="ProductID") %>%
transmute(OrderID, ProductID, ProductName, Qty, Price, ExtPrice=Qty*Price)
OrderID ProductID ProductName Qty Price ExtPrice
1 1651 6 Shirt 3 25.0 75.0
2 3168 17 Gloves 1 10.0 10.0
3 9130 17 Gloves 3 10.0 30.0
4 5265 33 Helmet 3 60.0 180.0
5 7702 33 Helmet 2 60.0 120.0
6 4931 51 Detergent 1 16.0 16.0
7 2726 88 Towel 2 7.5 15.0
8 3079 88 Towel 2 7.5 15.0
9 4816 88 Towel 1 7.5 7.5
10 8412 88 Towel 2 7.5 15.0
Run Code Online (Sandbox Code Playgroud)
案例2:显示订单明细,包括不匹配的产品ID
dtOrder %>%
left_join(dtProduct, by="ProductID") %>%
transmute(OrderID, ProductID, ProductName, Qty, Price, ExtPrice=Qty*Price)
OrderID ProductID ProductName Qty Price ExtPrice
1 1651 6 Shirt 3 25.0 75.0
2 3168 17 Gloves 1 10.0 10.0
3 9130 17 Gloves 3 10.0 30.0
4 5265 33 Helmet 3 60.0 180.0
5 7702 33 Helmet 2 60.0 120.0
6 4931 51 Detergent 1 16.0 16.0
7 2726 88 Towel 2 7.5 15.0
8 3079 88 Towel 2 7.5 15.0
9 4816 88 Towel 1 7.5 7.5
10 8412 88 Towel 2 7.5 15.0
11 7727 155 NA 2 NA NA
12 5134 439 NA 2 NA NA
Run Code Online (Sandbox Code Playgroud)
案例3:显示订单错误(仅限不匹配的产品ID)
dtOrder %>%
left_join(dtProduct, by="ProductID") %>%
filter(is.na(ProductName)) %>%
select(OrderID, ProductID, ProductName, Qty)
OrderID ProductID ProductName Qty
1 7727 155 NA 2
2 5134 439 NA 2
Run Code Online (Sandbox Code Playgroud)
案例4:按ProductID的各种聚合,按TotalSales降序排序结果
dtOrder %>%
inner_join(dtProduct, by="ProductID") %>%
group_by(ProductID) %>%
summarize(OrderCount=n(), TotalQty=sum(Qty), TotalSales=sum(Qty*Price)) %>%
arrange(desc(TotalSales))
ProductID OrderCount TotalQty TotalSales
1 33 2 5 300.0
2 6 1 3 75.0
3 88 4 7 52.5
4 17 2 4 40.0
5 51 1 1 16.0
Run Code Online (Sandbox Code Playgroud)
案例5:按ProductID的各种聚合,按TotalSales降序排序结果
注意2:按降序TotalSales排序不再有效(BUG?)
dtOrder %>%
inner_join(dtProduct, by="ProductID") %>%
group_by(ProductID, ProductName) %>%
summarize(OrderCount=n(), TotalQty=sum(Qty), TotalSales=sum(Qty*Price)) %>%
arrange(desc(TotalSales))
ProductID ProductName OrderCount TotalQty TotalSales
1 6 Shirt 1 3 75.0
2 17 Gloves 2 4 40.0
3 33 Helmet 2 5 300.0
4 51 Detergent 1 1 16.0
5 88 Towel 4 7 52.5
Run Code Online (Sandbox Code Playgroud)非常感谢您提前寻求帮助.
setkey(dtOrder, ProductID)
Run Code Online (Sandbox Code Playgroud)
(1-2)
# this will be literally what you wrote
dtProduct[dtOrder,
list(OrderID, ProductID, ProductName, Qty, Price, ExtPrice=Qty*Price),
nomatch = 0 # or omit this to get (2)
]
# but I think you'd be better off with this
dtProduct[dtOrder][, ExtPrice := Qty*Price][]
Run Code Online (Sandbox Code Playgroud)
(3)
# you can again take the literal direction:
dtProduct[dtOrder][!is.na(ProductName)][,
list(OrderID, ProductID, ProductName, Qty)]
# but again I think you'd be better off with
dtOrder[!dtProduct]
Run Code Online (Sandbox Code Playgroud)
(4-5)
dtProduct[dtOrder, nomatch = 0][,
list(OrderCount=.N, TotalQty=sum(Qty), TotalSales=sum(Qty*Price)),
by = list(ProductID, ProductName)][
order(-TotalSales)]
Run Code Online (Sandbox Code Playgroud)
您应该查看?data.table并查看那里的示例.这是一种非常好的学习方式.我们正在编写更详细的小插曲FR#944,计划于1.9.8.但在那之前:
data.table的语法形式如下:
x[i, j, by, ...] # i = where, j = select|modify|update, by = group by
Run Code Online (Sandbox Code Playgroud)
什么时候i是integer或者logical expression,我们称之为子集操作.例如:
x[a > 1]
Run Code Online (Sandbox Code Playgroud)
这是做什么的?检查adata.table中的列x是否存在条件> 1,从而得到逻辑向量= length(a).并且TRUE标识条件评估的那些行,并返回与这些行对应的所有列.
在data.table中,连接可以看作是子集的自然扩展.也就是说,我们可以将连接视为子集操作,但使用另一个data.table.这就是我们所说的具有一致语法的意思- 表单x[i, j, by]完整无缺.
加入data.tables的第一步是设置密钥.这可以使用setkey()目的是双重的函数来完成:
按提供的列按递增顺序(升序)重新排序data.table的行.这是通过参考存储器效率来完成的.
将那些列标记为可在其上执行连接的键列(如果执行连接时).
请注意,目前,对于表单的连接
x[i],x需要绝对设置键列.i可能有也可能没有它的关键设置.
如果
i还有它的键列集,那么通过匹配第一个键列的i第一个键列x,第二个键和第二个键等来执行连接.如果
i没有设置键列,则第一列i与x第二列的第二列匹配,依此类推i第二列x.是的,我们知道,当
i没有键列时,按列名匹配会很好,但我们还没有时间去做.
第二步也是最后一步是执行加入:-).
但是如何将连接操作作为子集的扩展?什么i是data.table,对于每一行i,它x通过匹配x我们设置的键列来找到匹配的行索引.这x将为每行返回一组行索引i(或者NA如果找不到匹配项).
现在我们有匹配的行索引.我们要返回的只是列.但由于i它也是一个data.table,它也可能有其他列.因此,我们返回两者的列x以及i匹配行索引的列.
在我们继续之前,这是一个帮助您内化概念的小例子.考虑两个data.tables X,Y如下所示:
X = data.table(a=c(1,1,1,2,2,5,6), b=1:7, key="a")
# a b
# 1: 1 1
# 2: 1 2
# 3: 1 3
# 4: 2 4
# 5: 2 5
# 6: 5 6
# 7: 6 7
key(X)
# [1] "a"
Y = data.table(a=c(6,2), c=letters[1:2])
# a c
# 1: 6 a
# 2: 2 b
key(Y)
# NULL
# join
X[Y]
# a b c
# 1: 6 7 a
# 2: 2 4 b
# 3: 2 5 b
Run Code Online (Sandbox Code Playgroud)
请注意,我们
key=在data.table()函数中使用了参数来直接设置键列.或者,我们可以在X没有键的情况下创建setkey(X, a).该函数
key()返回键列(如果有).如果未设置任何键,则返回NULL.
Y没有键列,X只有一个键列.因此,加入使用第一列是做a的Y和第一个键列a的X.a=6在Y与第7行匹配X和a=2上行4和5.
您可以使用参数来检查which = TRUE:
X[as.data.table(6), which=TRUE] # [1] 7
X[as.data.table(2), which=TRUE] # [1] 4 5
Run Code Online (Sandbox Code Playgroud)
这也是一种方便(快速)的方法来对data.table进行子集化,但是使用data.table的基于快速二进制搜索的子集.由于此操作非常有用,因此data.table提供了一种简单的方法,而不必as.data.table()每次都写入.
# faster way of doing X[a == 6] on data.table with 'a' as key column
X[J(6)] # J for Join
X[J(2)]
# (or)
X[.(6)] # . is an alias for J
X[.(2)]
Run Code Online (Sandbox Code Playgroud)
我认为这应该进一步帮助理解我们所说的子集是连接的扩展.
现在,让我们暂时忘记所有这些"左","右","内","外"等等.然后看看你想要执行的实际操作.你有两个data.tables - dtP和dtO(为方便起见缩短).
对于列ProductID中的每一行dtO,您希望在中找到匹配的行dtP,但不想返回NA.您还要选择要输出的列以及一些计算.
那是,i = dtO和x = dtP.键列dtP正确设置.但关键的一栏dtO是orderID.如果我们joine正因为如此,它会被加入orderID由dtO对productID从dtP,这是不对的.
要么我们必须设置键的dtO键productID或设置键dtO为NULL并将列productID作为第一列移动(直到实现匹配名称).我们在productID这里设置关键:
# set key
setkey(dtO, ProductID)
# join
dtP[dtO, .(OrderID, ProductID, ProductName, Qty, Price, ExtPrice=Qty*Price), nomatch=0L]
Run Code Online (Sandbox Code Playgroud)
现在这应该是很明显的.仅匹配行索引时,提取所有这些列(包括表达式).
我们为什么要先加入并选择/聚合?
与案例1相同,但您甚至需要不匹配的行.已从案例1正确设置密钥.
# join
dtP[dtO, .(OrderID, ProductID, ProductName, Qty, Price, ExtPrice=Qty*Price)]
Run Code Online (Sandbox Code Playgroud)
orderID即使没有匹配,也返回所有行,并指定所有列(包括表达式).
您希望其中的所有行dtO都不匹配dtP.
not-join or anti-join
dtO[!dtP]
Run Code Online (Sandbox Code Playgroud)
查找dtP的键列与dtO匹配的所有行.从dtO返回所有其他行.如有必要,您还可以指定所有必需的列j.
阅读关于by=.EACHI从这个职位.
您正在加入productID,然后通过同一列进行聚合.但为什么我们需要那个中间结果呢?这是完全没必要的,浪费内存和计算时间!相反,我们可以利用by=.EACHI哪个来评估每行中匹配行的j表达式i.
dtO[dtP, .(.N, sQty = sum(Qty), sSales = sum(Qty*Price)), by=.EACHI, nomatch=0L][order(-sSales)]
Run Code Online (Sandbox Code Playgroud)
为了测试你的理解,试着找出我们为什么不在dtP[dtO, ...]这里做的..
与@ eddi相同.
我个人觉得从我想要执行的实际任务来考虑更自然,而不是找出与我想要执行的任务相关联的连接函数的类型(我永远不会记得哪些data.table是"左",哪个一个是"正确的"......顺便说一下,"内部","外部"和"全外部"的加入究竟是什么?).
HTH