【问题标题】:Set R data.table row order by chaining 2 columns通过链接 2 列设置 R data.table 行顺序
【发布时间】:2020-07-26 03:38:33
【问题描述】:

我正在试图弄清楚如何根据 2 列的链接对 R 数据表进行排序。

这是我的示例 data.table。

dt <- data.table(id = c('A', 'A', 'A', 'A', 'A')
         , col1 = c(7521, 0, 7915, 5222, 5703)
         , col2 = c(7907, 5703, 8004, 7521, 5222))

   id col1 col2
1:  A 7521 7907
2:  A    0 5703
3:  A 7915 8004
4:  A 5222 7521
5:  A 5703 5222

我需要从 col1 = 0 开始的行顺序。第 2 行中的 col1 值应该等于前一行中 col2 的值,以此类推。

此外,通常应该始终有一个匹配值来链接行顺序。但如果不是,它应该选择最接近的值(请参见下面的第 4 行和第 5 行)。

我正在寻找的结果如下所示:

   id col1 col2
1:  A    0 5703
2:  A 5703 5222
3:  A 5222 7521
4:  A 7521 7907
5:  A 7915 8004

我想我可以编写一个疯狂的函数来做到这一点.. 但我想知道是否有一个优雅的 data.table 解决方案。

编辑
我更新了表格以包含一个带有重复行的附加 ID,以及一个唯一的源列:

dt <- data.table(id = c('A', 'A', 'A', 'A', 'A', 'B', 'B', 'B', 'B', 'B')
               , col1 = c(7521, 0, 7915, 5222, 5703, 1644, 1625, 0, 1625, 1625)
               , col2 = c(7907, 5703, 8004, 7521, 5222, 1625, 1625, 1644, 1625, 1505)
               , source = c('c', 'b', 'a', 'e', 'd', 'y', 'z', 'x', 'w', 'v'))

    id col1 col2 source
 1:  A 7521 7907      c
 2:  A    0 5703      b
 3:  A 7915 8004      a
 4:  A 5222 7521      e
 5:  A 5703 5222      d
 6:  B 1644 1625      y
 7:  B 1625 1625      z
 8:  B    0 1644      x
 9:  B 1625 1625      w
10:  B 1625 1505      v

ID 中可以有匹配的值。见上文第 7 行和第 9 行的 B。但是,这些数据的每一行都有一个唯一的来源。

期望的输出是:

    id col1 col2 source
 1:  A    0 5703      b
 2:  A 5703 5222      d
 3:  A 5222 7521      e
 4:  A 7521 7907      c
 5:  A 7915 8004      a
 6:  B    0 1644      x
 7:  B 1644 1625      y
 8:  B 1625 1625      w
 9:  B 1625 1625      z
10:  B 1625 1625      v

在输出中,匹配的行 8 和 9 可以是任意顺序。

谢谢!

【问题讨论】:

  • col2 会在 ID 中有重复项吗?您的示例将按原样工作,但如果有更多行,col2 将是 1625 或不匹配。
  • 是的。不是我想到的。有关更多数据集详细信息,请参阅编辑后的帖子。

标签: r data.table


【解决方案1】:

base 中使用Reduce 的链式排序解决方案。

fun <- function(j,k) {
    i[j] <<- FALSE
    r[i][which.min(abs(x$col2[j] - x$col1[i]))]
}

do.call(rbind, lapply(split(dt, dt$id), function(x) {
    assign("x", x, envir = .GlobalEnv)
    assign("i", rep(TRUE, nrow(x)), envir = .GlobalEnv)
    assign("r", seq_along(i), envir = .GlobalEnv)
    x[Reduce(fun, r[-1], which.min(x$col1), accumulate = TRUE),]
}))
#     id col1 col2 source
#A.2   A    0 5703      b
#A.5   A 5703 5222      d
#A.4   A 5222 7521      e
#A.1   A 7521 7907      c
#A.3   A 7915 8004      a
#B.8   B    0 1644      x
#B.6   B 1644 1625      y
#B.7   B 1625 1625      z
#B.9   B 1625 1625      w
#B.10  B 1625 1505      v

或者使用for循环:

fun <- function(init, from, to) {
  i <- integer(length(to))
  i[1] <- init
  j <- seq_along(to)[-init]
  for(k in seq_along(i)[-1]) {
    x <- which.min(abs(to[i[k-1]] - from[j]))
    i[k] <- j[x]
    j <- j[-x]
  }
  i
}
do.call(rbind, lapply(split(dt, dt$id), function(x) {
 x[fun(which.min(x$col1), x$col1, x$col2),]}))
#     id col1 col2 source
#A.2   A    0 5703      b
#A.5   A 5703 5222      d
#A.4   A 5222 7521      e
#A.1   A 7521 7907      c
#A.3   A 7915 8004      a
#B.8   B    0 1644      x
#B.6   B 1644 1625      y
#B.7   B 1625 1625      z
#B.9   B 1625 1625      w
#B.10  B 1625 1505      v

数据:

dt <- data.frame(id = c('A', 'A', 'A', 'A', 'A', 'B', 'B', 'B', 'B', 'B')
               , col1 = c(7521, 0, 7915, 5222, 5703, 1644, 1625, 0, 1625, 1625)
               , col2 = c(7907, 5703, 8004, 7521, 5222, 1625, 1625, 1644, 1625, 1505)
               , source = c('c', 'b', 'a', 'e', 'd', 'y', 'z', 'x', 'w', 'v'))

【讨论】:

    【解决方案2】:

    这是一个使用igraphdata.table 的选项:

    #add id in front of cols to distinguishes them as vertices
    cols <- paste0("col", 1L:2L)
    dt[, (cols) := lapply(.SD, function(x) paste0(id, x)), .SDcols=cols]
    
    #permutations of root nodes and leaf nodes
    chains <- dt[, CJ(root=setdiff(col1, col2), leaf=setdiff(col2, col1)), id]
    
    #find all paths from root nodes to leaf nodes
    #note that igraph requires vertices to be of character type
    library(igraph)
    g <- graph_from_data_frame(dt[, .(col1, col2)])
    l <- lapply(unlist(
      apply(chains, 1L, function(x) all_simple_paths(g, x[["root"]], x[["leaf"]])), 
      recursive=FALSE), names)
    links <- data.table(g=rep(seq_along(l), lengths(l)), col1=unlist(l))
    
    #look up edges
    dt[links, on=.(col1), nomatch=0L]
    

    输出:

        id  col1  col2 source g
     1:  A    A0 A5703      b 1
     2:  A A5703 A5222      d 1
     3:  A A5222 A7521      e 1
     4:  A A7521 A7907      c 1
     5:  A A7915 A8004      a 2
     6:  B    B0 B1644      x 3
     7:  B B1644 B1625      y 3
     8:  B B1625 B1625      z 3
     9:  B B1625 B1625      w 3
    10:  B B1625 B1505      v 3
    

    数据:

    library(data.table)
    dt <- data.table(id = c('A', 'A', 'A', 'A', 'A', 'B', 'B', 'B', 'B', 'B')
      , col1 = c(7521, 0, 7915, 5222, 5703, 1644, 1625, 0, 1625, 1625)
      , col2 = c(7907, 5703, 8004, 7521, 5222, 1625, 1625, 1644, 1625, 1505)
      , source = c('c', 'b', 'a', 'e', 'd', 'y', 'z', 'x', 'w', 'v'))
    

    【讨论】:

    • Hmmm .. 执行 lapply 时出现错误:all_simple_paths(g, x[1L], x[2L]) 中的错误:在 paths.c:77:起始顶点无效,无效价值
    • 链的输出是根叶 1: 0 7907 2: 0 8004 3: 7915 7907 4: 7915 8004
    • @AlexP,图的顶点必须是字符类型。因此,这就是我在 col* 上使用 as.character 的原因
    • 啊啊啊好吧!我错过了将列更改为字符类。有用!非常感谢!
    • 我应该在原帖里发,但是如果有额外的ID,代码有什么变化?
    【解决方案3】:

    这是另一种方法:

    1. 重新排序将首先放置 0 值的数据。
    2. 循环遍历其余值以返回col2col1 匹配的索引。
    setorder(dt, col1)
    
    neworder = seq_len(nrow(dt))
    init = 1L
    col1 = dt[['col1']]; col2 = dt[['col2']]
    
    for (i in seq_along(neworder)[-1L]) {
      ind = match(col2[init], col1)
      if (is.na(ind)) break
      neworder[i] = init = ind
    }
    
    dt[neworder]
    
    ##       id  col1  col2
    ##   <char> <num> <num>
    ##1:      A     0  5703
    ##2:      A  5703  5222
    ##3:      A  5222  7521
    ##4:      A  7521  7907
    ##5:      A  7915  8004
    

    如果您使用分组进行操作,您可以将循环包装在 dt[, .I[{...}, by = id]$V1 中以返回索引。或者为了让它看起来更好,我们可以做一个函数。

    recursive_order = function (x, y) {
      neworder = seq_len(length(x))
      init = 1L
    
      for (i in neworder[-1L]) {
        ind = match(y[init], x)
        if (is.na(ind)) break
    
        # Multiple matches which means all the maining matches are the same number
        if (ind == init) { 
          inds = which(x %in% y[init])
          l = length(inds)
          neworder[i:(i + l - 2L)] = inds[-1L]
          break
        }
        neworder[i] = init = ind
      }
      return(neworder)
    }
    
    dt <- data.table(id = c('A', 'A', 'A', 'A', 'A', 'B', 'B', 'B', 'B', 'B')
                     , col1 = c(7521, 0, 7915, 5222, 5703, 1644, 1625, 0, 1625, 1625)
                     , col2 = c(7907, 5703, 8004, 7521, 5222, 1625, 1625, 1644, 1625, 1505)
                     , source = c('c', 'b', 'a', 'e', 'd', 'y', 'z', 'x', 'w', 'v'))
    
    setorder(dt, col1)
    dt[dt[, .I[recursive_order(col1, col2)], by = id]$V1]
    
           id  col1  col2 source
        <char> <num> <num> <char>
     1:      A     0  5703      b
     2:      A  5703  5222      d
     3:      A  5222  7521      e
     4:      A  7521  7907      c
     5:      A  7915  8004      a
     6:      B     0  1644      x
     7:      B  1644  1625      y
     8:      B  1625  1625      z
     9:      B  1625  1625      w
    10:      B  1625  1505      v
    

    【讨论】:

    • 这行得通!我仍然需要更好地理解这一点,但效果很好。如果 'id' 列有更多值,你会怎么做?假设它有 id 'b' 和 'c' 有各自的值?
    • @AlexP 请查看编辑。这符合您修改后问题的预期输出。
    猜你喜欢
    • 2021-11-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-21
    • 1970-01-01
    • 2015-02-05
    • 1970-01-01
    相关资源
    最近更新 更多