Defina a ordem das linhas da tabela de dados R encadeando 2 colunas

8

Estou tentando descobrir como ordenar uma tabela de dados R com base no encadeamento de 2 colunas.

Aqui está o meu exemplo 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

Eu preciso da ordem da linha para começar com col1 = 0. O valor col1 na linha 2 deve ser igual ao valor de col2 na linha anterior e assim por diante.

Além disso, geralmente sempre deve haver um valor correspondente que encadeie a ordem das linhas. Caso contrário, ele deve selecionar o valor mais próximo (consulte as linhas 4 e 5 abaixo).

O resultado que estou procurando é mostrado abaixo:

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

Acho que posso escrever uma função maluca para fazer isso ... mas estou me perguntando se existe uma solução elegante de data.table.

EDIT
Atualizei a tabela para incluir um ID adicional com linhas duplicadas e uma coluna de origem exclusiva:

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

Pode haver valores correspondentes em um ID. Veja B, linhas 7 e 9 acima. No entanto, há uma fonte exclusiva para cada linha de onde esses dados vêm.

A saída desejada seria:

    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

Na saída, as linhas correspondentes 8 e 9 podem estar em qualquer ordem.

Obrigado!

AlexP
fonte
Teria col2duplicatas em um ID? Seu exemplo funcionaria como está, mas se houver mais linhas, col2seria 1625 ou não corresponderia.
Cole
Sim. Não é algo que pensei. Consulte a postagem editada para obter mais detalhes do conjunto de dados.
AlexP 24/04

Respostas:

3

Aqui está outra abordagem que:

  1. Reordena os dados que colocarão o valor 0 primeiro.
  2. Faz um loop pelo restante dos valores para retornar o índice de onde col2corresponde col1.
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

Se você estiver fazendo isso com o agrupamento, poderá envolver o loop dentro de a dt[, .I[{...}, by = id]$V1para retornar os índices. Ou, para melhorar a aparência, podemos criar uma função.

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
Cole
fonte
Isso funciona! Ainda preciso entender isso um pouco melhor, mas com bons resultados. O que você faria se a coluna 'id' tivesse mais valores? Digamos que ele tenha os IDs 'b' e 'c' com seus próprios valores respectivos?
AlexP 23/04
@AlexP, consulte editar. Isso corresponde ao resultado esperado da sua pergunta revisada.
Cole
7

Aqui está uma opção usando igraphcom data.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]

resultado:

    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

dados:

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'))
chinsoon12
fonte
Hmmm .. Eu recebo um erro quando faço o lapply: Erro em todos os caminhos_ simples (g, x [1L], x [2L]): No paths.c: 77: vértice inicial inválido, valor inválido
AlexP
A saída para cadeias é folha de raiz 1: 0 7907 2: 0 8004 3: 7915 7907 4: 7915 8004
AlexP
@AlexP, o vértice do gráfico deve ser do tipo caractere. Por isso, é por isso que eu uso as.character emcol*
chinsoon12
Ahhhh ok! Perdi a alteração das colunas para uma classe de personagem. Funciona! Muito obrigado!
AlexP
1
@AlexP eu adicionei código para manipular id
chinsoon12 24/04