Por que X join de data.tables não permite uma junit externa completa ou uma junit esquerda?

Esta é uma questão filosófica sobre data.table join syntax. Eu estou encontrando mais e mais usos para data.tables, mas ainda aprendendo …

O formato de junit X[Y] para data.tables é muito conciso, prático e eficiente, mas, até onde eu sei, ele apenas suporta junções internas e junções externas à direita. Para obter uma junit externa completa ou esquerda, preciso usar a merge :

  • X[Y, nomatch = NA] – todas as linhas em Y – junit externa direita (padrão)
  • X[Y, nomatch = 0] – somente linhas com correspondências em X e Y – junit interna
  • merge(X, Y, all = TRUE) – todas as linhas de X e Y – junit externa completa
  • merge(X, Y, all.x = TRUE) – todas as linhas em X – junit externa esquerda

Parece-me que seria útil se o formato de junit X[Y] suportasse todos os 4 tipos de junções. Existe uma razão pela qual apenas dois tipos de junções são suportados?

Para mim, os valores dos parâmetros nomatch = 0 e nomatch = NA não são muito intuitivos para as ações que estão sendo executadas. É mais fácil para mim entender e lembrar a syntax de merge : all = TRUE , all.x = TRUE e all.y = TRUE . Como a operação X[Y] parece muito com a merge , por que não usar a syntax de merge para junções em vez do parâmetro nomatch da function de nomatch ?

Aqui estão exemplos de código dos 4 tipos de junit:

 # sample X and Y data.tables library(data.table) X <- data.table(t = 1:4, a = (1:4)^2) setkey(X, t) X # ta # 1: 1 1 # 2: 2 4 # 3: 3 9 # 4: 4 16 Y <- data.table(t = 3:6, b = (3:6)^2) setkey(Y, t) Y # tb # 1: 3 9 # 2: 4 16 # 3: 5 25 # 4: 6 36 # all rows from Y - right outer join X[Y] # default # tab # 1: 3 9 9 # 2: 4 16 16 # 3: 5 NA 25 # 4: 6 NA 36 X[Y, nomatch = NA] # same as above # tab # 1: 3 9 9 # 2: 4 16 16 # 3: 5 NA 25 # 4: 6 NA 36 merge(X, Y, by = "t", all.y = TRUE) # same as above # tab # 1: 3 9 9 # 2: 4 16 16 # 3: 5 NA 25 # 4: 6 NA 36 identical(X[Y], merge(X, Y, by = "t", all.y = TRUE)) # [1] TRUE # only rows in both X and Y - inner join X[Y, nomatch = 0] # tab # 1: 3 9 9 # 2: 4 16 16 merge(X, Y, by = "t") # same as above # tab # 1: 3 9 9 # 2: 4 16 16 merge(X, Y, by = "t", all = FALSE) # same as above # tab # 1: 3 9 9 # 2: 4 16 16 identical( X[Y, nomatch = 0], merge(X, Y, by = "t", all = FALSE) ) # [1] TRUE # all rows from X - left outer join merge(X, Y, by = "t", all.x = TRUE) # tab # 1: 1 1 NA # 2: 2 4 NA # 3: 3 9 9 # 4: 4 16 16 # all rows from both X and Y - full outer join merge(X, Y, by = "t", all = TRUE) # tab # 1: 1 1 NA # 2: 2 4 NA # 3: 3 9 9 # 4: 4 16 16 # 5: 5 NA 25 # 6: 6 NA 36 

Atualização: o data.table v1.9.6 introduziu a syntax on= , que permite junções ad hoc em campos diferentes da chave primária. resposta jangorecki para a pergunta Como juntar (mesclar) frameworks de dados (interior, exterior, esquerda, direita)? fornece alguns exemplos de tipos de junit adicionais que o data.table pode manipular.

Para citar a FAQ do data.table 1.12

1,12

Qual é a diferença entre X [Y] e mesclar (X, Y)?

  • X[Y] é uma junit, procurando as linhas de X usando Y (ou a chave de Y, se tiver uma) como um índice.
  • Y[X] é uma junit, procurando as linhas de Y usando X (ou a chave de X, se tiver uma)
  • merge(X,Y) faz as duas maneiras ao mesmo tempo.

O número de linhas de X[Y] e Y[X] geralmente diferem; enquanto o número de linhas retornadas por merge(X,Y) e merge(Y,X) é o mesmo. MAS isso erra o ponto principal. A maioria das tarefas requer que algo seja feito nos dados após uma junit ou mesclagem. Por que mesclar todas as colunas de dados, apenas para usar um pequeno subconjunto delas depois? Você pode sugerir merge(X[,ColsNeeded1],Y[,ColsNeeded2]) , mas isso leva cópias dos subconjuntos de dados e requer que o programador merge(X[,ColsNeeded1],Y[,ColsNeeded2]) quais colunas são necessárias. X[Y,j ] em data.table faz tudo isso em uma etapa para você. Quando você escreve X[Y,sum(foo*bar)] , o data.table inspeciona automaticamente a expressão j para ver quais colunas ele usa. Apenas subconjuntos dessas colunas; os outros são ignorados. A memory é criada apenas para as colunas que o j usa, e as colunas Y aproveitam as regras de recyclerview padrão do R no contexto de cada grupo. Digamos que foo esteja em X e bar em Y (junto com outras 20 colunas em Y). X[Y,sum(foo*bar)] mais rápido para programar e mais rápido para executar do que uma mesclagem seguida por um subconjunto?

Se você quiser uma junit externa esquerda de X[Y]

 le < - Y[X] mallx <- merge(X, Y, all.x = T) # the column order is different so change to be the same as `merge` setcolorder(le, names(mallx)) identical(le, mallx) # [1] TRUE 

Se você quiser uma junit externa completa

 # the unique values for the keys over both data sets unique_keys < - unique(c(X[,t], Y[,t])) Y[X[J(unique_keys)]] ## tba ## 1: 1 NA 1 ## 2: 2 NA 4 ## 3: 3 9 9 ## 4: 4 16 16 ## 5: 5 25 NA ## 6: 6 36 NA # The following will give the same with the column order X,Y X[Y[J(unique_keys)]] 

A resposta do @mnel está correta, então aceite essa resposta. Este é apenas o acompanhamento, muito longo para comentários.

Como diz mnel, a junit externa esquerda / direita é obtida trocando Y e X : Y[X] -vs- X[Y] . Então, 3 dos 4 tipos de junit são suportados nessa syntax, não 2, iiuc.

Adicionar o quarto parece uma boa ideia. Digamos que nós adicionamos full=TRUE ou both=TRUE ou merge=TRUE (não tem certeza do melhor nome de argumento?) Então não ocorreu a mim antes que X[Y,j,merge=TRUE] seria útil pelas razões após o MAS na FAQ 1.12. Nova solicitação de recurso agora adicionada e vinculada de volta aqui, obrigado:

FR # 2301: Adicione o argumento merge = TRUE para X [Y] e Y [X] junte como merge () faz.

Versões recentes aceleraram o merge.data.table (pegando uma cópia superficial internamente para definir as chaves de maneira mais eficiente, por exemplo). Então, estamos tentando aproximar merge() e X[Y] e fornecer todas as opções ao usuário para flexibilidade total. Existem prós e contras de ambos. Outra solicitação de recurso pendente é:

FR # 2033: Adicione by.x e by.y para merge.data.table

Se houver outros, por favor, mantenha-os próximos.

Por esta parte na pergunta:

Por que não usar a syntax de mesclagem para junções em vez do parâmetro nomatch da function de correspondência?

Se você preferir a syntax merge() e seus 3 argumentos all , all.x e all.y então use isso ao invés de X[Y] . Acho que deveria cobrir todos os casos. Ou você quis dizer por que o argumento é um único nomatch em [.data.table ? Se assim for, é apenas a maneira que parecia natural, dada a FAQ 2.14: “Você pode explicar melhor porque o data.table é inspirado pela syntax A [B] na base?”. Mas também, nomatch leva apenas dois valores atualmente 0 e NA . Isso poderia ser estendido de forma que um valor negativo significasse algo, ou 12 significaria usar os valores da 12ª linha para preencher NAs, por exemplo, ou nomatch no futuro poderia ser um vetor ou até mesmo um data.table .

Hm Como seria por-sem-por interagir com mesclar = TRUE? Talvez devêssemos levar isso para a ajuda de dados .

Esta “resposta” é uma proposta para discussão: Como indicado no meu comentário, sugiro adicionar um parâmetro de join a [.data.table () para ativar tipos adicionais de junções, ou seja: X[Y,j,join=string] . Além dos 4 tipos de junções comuns, sugiro também suportar 3 tipos de junções exclusivas e a junit cruzada .

Os valores de cadeia de join (e aliases) para os vários tipos de junit são propostos a serem:

  1. "all.y" e "right" – junit direita, o presente padrão de tabela de dados (nomatch = NA) – todas as linhas Y com NAs onde não há correspondência de X;
  2. "both" e "inner" – junit interna (nomatch = 0) – somente linhas onde X e Y correspondem;

  3. "all.x" e "left" – left join – todas as linhas de X, NAs onde nenhuma correspondência Y:

  4. "outer" e "full" – junit externa completa – todas as linhas de X e Y, NAs onde não coincidem

  5. "only.x" e "not.y" – non-join ou anti-join retornando X linhas onde não há correspondência Y

  6. "only.y" e "not.x" – non-join ou anti-join retornando as linhas Y onde não há correspondência X
  7. "not.both" – junit exclusiva retornando linhas X e Y onde não há correspondência para a outra tabela, isto é, um exclusivo ou (XOR)
  8. "cross" – cross join ou produto cartesiano com cada linha de X correspondente a cada linha de Y

O valor padrão é join="all.y" que corresponde ao padrão atual.

Os valores de string “all”, “all.x” e “all.y” correspondem aos parâmetros merge() . As strings “right”, “left”, “inner” e “outer” podem ser mais acessíveis aos usuários do SQL.

As strings “both” e “not.both” são minha melhor sugestão no momento – mas alguém pode ter sugestões de strings melhores para a junit interna e junit exclusiva. (Não tenho certeza se “exclusivo” é a terminologia correta, corrija-me se houver um termo apropriado para uma associação “XOR”.)

O uso de join="not.y" é uma alternativa para a syntax de não-junit X[-Y,j] ou X[!Y,j] e talvez mais clara (para mim), embora eu não tenha certeza se eles são o mesmo (novo recurso no data.table versão 1.8.3).

A união cruzada pode ser útil às vezes, mas pode não se encheckboxr no paradigma data.table.