在给定另一列条件的情况下,用两列的组合展开 data.table
Expand data.table with combinations of two columns given condition in another column
我有一个 data.table
可以为我提供不同公交路线 (route_id
) 的位置 (origin
和 destination
) 之间的连接。
library(data.table)
library(magrittr)
# data for reproducible example
dt <- data.table( origin = c('A','B','C', 'F', 'G', 'H'),
destination = c('B','C','D', 'G', 'H', 'I'),
freq = c(2,2,2,10,10,10),
route_id = c(1,1,1,2,2,2), stringsAsFactors=FALSE )
# > dt
# origin destination freq route_id
# 1: A B 2 1
# 2: B C 2 1
# 3: C D 2 1
# 4: F G 10 2
# 5: G H 10 2
# 6: H I 10 2
为了我想做的事情,如果有一个 route_id
给出一个连接 A-B
和一个连接 B-C
,那么我想添加到数据连接 A-C
相同 route_id
等等。
问题:到目前为止,我已经创建了一个简单的代码来完成这项工作,但是:
- 它使用了一个需要很长时间的
for loop
(我的真实数据有几十万个观测值)
- 它仍然不能很好地应对方向。连接的方向在这里很重要。所以虽然原始数据中有
B-C
连接,但输出中应该没有C-B
。
我的慢解决方案
# loop
# a) get a data subset corresponding to each route_id
# b) get all combinations of origin-destination pairs
# c) row bind the new pairs to original data
for (i in unique(dt$route_id)) {
temp <- dt[ route_id== i,]
subset_of_pairs <- expand.grid(temp$origin, temp$destination) %>% setDT()
setnames(subset_of_pairs, c("origin", "destination"))
dt <- rbind(dt, subset_of_pairs, fill=T)
}
# assign route_id and freq to new pairs
dt[, route_id := route_id[1L], by=origin]
dt[, freq := freq[1L], by=route_id]
# Keepe only different pairs that are unique
dt[, origin := as.character(origin) ][, destination := as.character(destination) ]
dt <- dt[ origin != destination, ][order(route_id, origin, destination)]
dt <- unique(dt)
期望的输出
origin destination freq route_id
1: A B 2 1
2: A C 2 1
3: A D 2 1
4: B C 2 1
5: B D 2 1
6: C D 2 1
7: F G 10 2
8: F H 10 2
9: F I 10 2
10: G H 10 2
11: G I 10 2
12: H I 10 2
一种方式:
res = dt[, {
stops = c(origin, last(destination))
pairs = combn(.N + 1L, 2L)
.(o = stops[pairs[1,]], d = stops[pairs[2,]])
}, by=route_id]
route_id o d
1: 1 A B
2: 1 A C
3: 1 A D
4: 1 B C
5: 1 B D
6: 1 C D
7: 2 F G
8: 2 F H
9: 2 F I
10: 2 G H
11: 2 G I
12: 2 H I
这是假设 c(origin, last(destination))
是完整的停靠点列表。如果 dt
没有包含足够的信息来构建完整的订单,任务就会变得更加困难。
如果需要来自 dt
的变量,像 res[dt, on=.(route_id), freq := i.freq]
这样的更新连接可以工作。
像这样的任务总是有 运行 内存不足的风险。在这种情况下,OP 有多达一百万行,其中包含多达 341 个停靠点的组,因此最终结果可能与 1e6/341*choose(341,2)
= 1.7 亿行一样大。这是可管理的,但一般来说,这种分析无法扩展。
工作原理
通常,data.table 语法可以被视为组循环:
DT[, {
...
}, by=g]
这比循环有一些优势:
- 在
...
正文中创建的任何内容都不会污染工作区。
- 所有列都可以通过名称引用。
- 可以使用特殊符号
.N
、.SD
、.GRP
和 .BY
,以及 list()
的 .()
。
在上面的代码中,pairs
找到取自 1 .. #stops (=.N+1 的索引对,其中 .N 是与给定数据相关联的数据子集中的行数route_id)。它是一个矩阵,第一行对应一对中的第一个元素;第二行与第二行。 ...
应该评估为列列表;这里 list()
缩写为 .()
.
进一步改进
我想大部分时间都花在计算 combn
很多次上了。如果多条路线有相同的#stops,可以通过预先计算解决:
Ns = dt[,.N, by=route_id][, unique(N)]
cb = lapply(setNames(,Ns), combn, 2)
然后在主代码中抓取pairs = cb[[as.character(.N)]]
。或者,定义一个 pairs
函数,该函数使用记忆来避免重新计算。
我有一个 data.table
可以为我提供不同公交路线 (route_id
) 的位置 (origin
和 destination
) 之间的连接。
library(data.table)
library(magrittr)
# data for reproducible example
dt <- data.table( origin = c('A','B','C', 'F', 'G', 'H'),
destination = c('B','C','D', 'G', 'H', 'I'),
freq = c(2,2,2,10,10,10),
route_id = c(1,1,1,2,2,2), stringsAsFactors=FALSE )
# > dt
# origin destination freq route_id
# 1: A B 2 1
# 2: B C 2 1
# 3: C D 2 1
# 4: F G 10 2
# 5: G H 10 2
# 6: H I 10 2
为了我想做的事情,如果有一个 route_id
给出一个连接 A-B
和一个连接 B-C
,那么我想添加到数据连接 A-C
相同 route_id
等等。
问题:到目前为止,我已经创建了一个简单的代码来完成这项工作,但是:
- 它使用了一个需要很长时间的
for loop
(我的真实数据有几十万个观测值) - 它仍然不能很好地应对方向。连接的方向在这里很重要。所以虽然原始数据中有
B-C
连接,但输出中应该没有C-B
。
我的慢解决方案
# loop
# a) get a data subset corresponding to each route_id
# b) get all combinations of origin-destination pairs
# c) row bind the new pairs to original data
for (i in unique(dt$route_id)) {
temp <- dt[ route_id== i,]
subset_of_pairs <- expand.grid(temp$origin, temp$destination) %>% setDT()
setnames(subset_of_pairs, c("origin", "destination"))
dt <- rbind(dt, subset_of_pairs, fill=T)
}
# assign route_id and freq to new pairs
dt[, route_id := route_id[1L], by=origin]
dt[, freq := freq[1L], by=route_id]
# Keepe only different pairs that are unique
dt[, origin := as.character(origin) ][, destination := as.character(destination) ]
dt <- dt[ origin != destination, ][order(route_id, origin, destination)]
dt <- unique(dt)
期望的输出
origin destination freq route_id
1: A B 2 1
2: A C 2 1
3: A D 2 1
4: B C 2 1
5: B D 2 1
6: C D 2 1
7: F G 10 2
8: F H 10 2
9: F I 10 2
10: G H 10 2
11: G I 10 2
12: H I 10 2
一种方式:
res = dt[, {
stops = c(origin, last(destination))
pairs = combn(.N + 1L, 2L)
.(o = stops[pairs[1,]], d = stops[pairs[2,]])
}, by=route_id]
route_id o d
1: 1 A B
2: 1 A C
3: 1 A D
4: 1 B C
5: 1 B D
6: 1 C D
7: 2 F G
8: 2 F H
9: 2 F I
10: 2 G H
11: 2 G I
12: 2 H I
这是假设 c(origin, last(destination))
是完整的停靠点列表。如果 dt
没有包含足够的信息来构建完整的订单,任务就会变得更加困难。
如果需要来自 dt
的变量,像 res[dt, on=.(route_id), freq := i.freq]
这样的更新连接可以工作。
像这样的任务总是有 运行 内存不足的风险。在这种情况下,OP 有多达一百万行,其中包含多达 341 个停靠点的组,因此最终结果可能与 1e6/341*choose(341,2)
= 1.7 亿行一样大。这是可管理的,但一般来说,这种分析无法扩展。
工作原理
通常,data.table 语法可以被视为组循环:
DT[, {
...
}, by=g]
这比循环有一些优势:
- 在
...
正文中创建的任何内容都不会污染工作区。 - 所有列都可以通过名称引用。
- 可以使用特殊符号
.N
、.SD
、.GRP
和.BY
,以及list()
的.()
。
在上面的代码中,pairs
找到取自 1 .. #stops (=.N+1 的索引对,其中 .N 是与给定数据相关联的数据子集中的行数route_id)。它是一个矩阵,第一行对应一对中的第一个元素;第二行与第二行。 ...
应该评估为列列表;这里 list()
缩写为 .()
.
进一步改进
我想大部分时间都花在计算 combn
很多次上了。如果多条路线有相同的#stops,可以通过预先计算解决:
Ns = dt[,.N, by=route_id][, unique(N)]
cb = lapply(setNames(,Ns), combn, 2)
然后在主代码中抓取pairs = cb[[as.character(.N)]]
。或者,定义一个 pairs
函数,该函数使用记忆来避免重新计算。