在另一列中给出条件的两列组合展开data.table

raf*_*ira 5 r combinatorics dataframe data.table pairwise

我有一个data.table给我在不同的公交路线()的位置(origindestination)之间的连接route_id.

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
Run Code Online (Sandbox Code Playgroud)

对于什么我想要做的目的,如果有route_id,给出了一个连接A-B和连接B-C,然后我要添加到数据的连接A-C为相同route_id等.

问题:到目前为止,我已经创建了一个简单的代码来完成这项工作,但是:

  1. 它使用for loop需要很长时间(我的真实数据有数十万个观测值)
  2. 它仍然不能很好地应对方向.连接方向在这里很重要.因此,虽然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)
Run Code Online (Sandbox Code Playgroud)

期望的输出

    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
Run Code Online (Sandbox Code Playgroud)

Fra*_*ank 4

单程:

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
Run Code Online (Sandbox Code Playgroud)

这是假设这c(origin, last(destination))是按顺序排列的完整停靠点列表。如果dt不包含足够的信息来构建完整的订单,任务就会变得更加困难。

如果dt需要 vars from ,则res[dt, on=.(route_id), freq := i.freq]可以使用更新连接。

像这样的任务总是有内存不足的风险。在这种情况下,OP 最多有 100 万行,其中包含最多 341 个停靠点的组,因此最终结果可能高达1e6/341*choose(341,2)= 1.7 亿行。这是可以管理的,但一般来说,这种分析无法扩展。


怎么运行的

一般来说,data.table 语法可以被视为组上的循环:

DT[, { 
  ...
}, by=g]
Run Code Online (Sandbox Code Playgroud)

与循环相比,这有一些优点:

  • 体内创建的任何东西都...不会污染工作空间。
  • 所有列都可以通过名称引用。
  • 可以使用特殊符号.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)
Run Code Online (Sandbox Code Playgroud)

然后抓取pairs = cb[[as.character(.N)]]主要代码。或者,定义一个pairs使用记忆以避免重新计算的函数。