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

时间:2017-05-01 20:25:49

标签: r dataframe data.table combinatorics pairwise

我有一个Public Class Location { private int piecesA; private int piecesB; private int pieces = piecesA + piecesB; //methods etc. } ,它为不同的公交路线(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 提供连接route_id和连接A-B,那么我想添加到数据连接B-C用于相同的A-C,依此类推。

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

  1. 它使用需要很长时间的route_id(我的真实数据有数十万次观察)
  2. 它仍然不能很好地应对方向。连接方向在这里很重要。因此,虽然原始数据中存在for loop连接,但输出中应该没有B-Center image description here
  3. 我的慢解决方案

    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)
    

1 个答案:

答案 0 :(得分: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

这假设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以及.()的{​​{1}}。

在上面的代码中,list()找到从1开始的索引对。#stop(= .N + 1,其中.N是与给定route_id关联的数据子集中的行数) 。它是一个矩阵,第一行对应一对的第一个元素;和第二行与第二行。 pairs应评估为列列表;此处...缩写为list()

进一步改进

我猜这段时间主要用于多次计算.()。如果多个路由具有相同的#stops,则可以通过预先计算来解决此问题:

combn

然后在主代码中抓取Ns = dt[,.N, by=route_id][, unique(N)] cb = lapply(setNames(,Ns), combn, 2) 。或者,定义一个使用memoization的pairs = cb[[as.character(.N)]]函数,以避免重新计算。