¿Por qué X se une a data.tables no permite una combinación externa completa, o una combinación izquierda?

Esta es una cuestión filosófica sobre la syntax de unión de datos. Estoy encontrando más y más usos para data.tables, pero aún estoy aprendiendo …

El formato de combinación X[Y] para data.tables es muy conciso, práctico y eficiente, pero hasta donde puedo decir, solo admite combinaciones internas y combinaciones externas derechas. Para obtener una combinación externa izquierda o completa, necesito usar merge :

  • X[Y, nomatch = NA] – todas las filas en Y – unión externa derecha (predeterminado)
  • X[Y, nomatch = 0] – solo filas con coincidencias en X e Y – unión interna
  • merge(X, Y, all = TRUE) – todas las filas de X e Y – unión externa completa
  • merge(X, Y, all.x = TRUE) – todas las filas en X – combinación externa izquierda

Me parece que sería útil si el formato de unión X[Y] admite los 4 tipos de combinaciones. ¿Hay alguna razón por la que solo se admitan dos tipos de combinaciones?

Para mí, los valores de los parámetros nomatch = 0 y nomatch = NA no son muy intuitivos para las acciones que se realizan. Es más fácil para mí entender y recordar la syntax de merge : all = TRUE , all.x = TRUE y all.y = TRUE . Dado que la operación X[Y] parece a merge mucho más que una match , ¿por qué no utilizar la syntax de merge para las uniones en lugar del parámetro nomatch la función de nomatch ?

Aquí hay ejemplos de código de los 4 tipos de unión:

 # 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 

Actualización: data.table v1.9.6 introdujo la syntax on= , que permite uniones ad hoc en campos distintos de la clave principal. Respuesta de jangorecki a la pregunta ¿Cómo unir (combinar) marcos de datos (interno, externo, izquierdo, derecho)? proporciona algunos ejemplos de tipos de combinación adicionales que data.table puede manejar.

Para citar las preguntas frecuentes de data.table 1.12

1.12

¿Cuál es la diferencia entre X [Y] y fusión (X, Y)?

  • X[Y] es una unión, buscando las filas de X usando Y (o la tecla Y si tiene una) como índice.
  • Y[X] es un join, buscando las filas de Y usando X (o la tecla de X si tiene una)
  • merge(X,Y) hace las dos cosas al mismo tiempo.

El número de filas de X[Y] e Y[X] suele diferir; mientras que el número de filas devueltas por merge(X,Y) y merge(Y,X) es el mismo. PERO eso pasa por alto el punto principal. La mayoría de las tareas requieren que se haga algo sobre los datos después de unir o unir. ¿Por qué fusionar todas las columnas de datos, solo para usar un pequeño subconjunto de ellas luego? Puede sugerir merge(X[,ColsNeeded1],Y[,ColsNeeded2]) , pero eso toma copias de los subconjuntos de datos, y requiere que el progtwigdor determine qué columnas son necesarias. X[Y,j ] en data.table hace todo eso en un solo paso para usted. Cuando escribe X[Y,sum(foo*bar)] , data.table inspecciona automáticamente la expresión j para ver qué columnas usa. Solo subconjunto esas columnas solamente; los otros son ignorados La memoria solo se crea para las columnas que utiliza j, y las columnas Y disfrutan de las reglas de reciclaje estándar R dentro del contexto de cada grupo. Digamos que foo está en X, y la barra está en Y (junto con otras 20 columnas en Y). ¿No es X[Y,sum(foo*bar)] más rápido de progtwigr y más rápido de ejecutar que una fusión seguida de un subconjunto?

Si quieres una combinación externa izquierda 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 

Si quieres una combinación 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)]] 

La respuesta de @mnel es acertada, así que acepta esa respuesta. Esto es solo seguimiento, demasiado tiempo para comentarios.

Como dice mnel, la combinación externa izquierda / derecha se obtiene al intercambiar Y y X : Y[X] -vs- X[Y] . Entonces 3 de los 4 tipos de unión son compatibles con esa syntax, no con 2, iiuc.

Agregar el 4to parece una buena idea. Digamos que agregamos full=TRUE o both=TRUE o merge=TRUE (no estoy seguro del mejor nombre de argumento?) Entonces no se me había ocurrido antes que X[Y,j,merge=TRUE] fuera útil por las razones después de PERO en FAQ 1.12. Nueva solicitud de función ahora agregada y vinculada aquí, gracias:

FR # 2301: Agregue merge = TRUE argumento tanto para X [Y] como para Y [X] join como merge ().

Las versiones recientes han acelerado merge.data.table (tomando una copia poco profunda internamente para establecer las claves de manera más eficiente, por ejemplo). Por lo tanto, estamos tratando de acercar merge() y X[Y] y brindarle al usuario todas las opciones para una flexibilidad total. Hay pros y contras de ambos. Otra solicitud destacada de funciones es:

FR # 2033: Agregue by.x y by.y a merge.data.table

Si hay otros, por favor manténgalos venir.

Por esta parte en la pregunta:

¿por qué no usar la syntax de fusión para las uniones en lugar del parámetro de nomatch de la función de coincidencia?

Si prefiere la syntax de merge() y sus 3 argumentos all , all.x y all.y , simplemente use eso en lugar de X[Y] . Creo que debería cubrir todos los casos. ¿O quisiste decir por qué el argumento es un solo nomatch en [.data.table ? Si es así, es la manera que parecía natural, dada la Pregunta frecuente 2.14: “¿Puede explicar más por qué data.table está inspirado en la syntax A [B] en la base?”. Pero también, nomatch solo toma dos valores actualmente 0 y NA . Eso podría extenderse de modo que un valor negativo significara algo, o 12 significaría usar los valores de la fila 12 para completar las NA, por ejemplo, o nomatch en el futuro podría ser un vector o incluso una data.table .

Hm. ¿Cómo interactuaría by-without-by con merge = TRUE? Tal vez deberíamos llevar esto a datatable-help .

Esta “respuesta” es una propuesta para discusión: como indiqué en mi comentario, sugiero agregar un parámetro de join a [.data.table () para habilitar tipos adicionales de combinaciones, es decir: X[Y,j,join=string] . Además de los 4 tipos de combinaciones comunes, también sugiero admitir 3 tipos de combinaciones exclusivas y la combinación cruzada .

Se propone que los valores de cadena de join (y alias) para los diversos tipos de unión sean:

  1. "all.y" y "right" – right join, el valor actual de data.table (nomatch = NA) – todas las filas Y con NA donde no hay coincidencia X;
  2. "both" e "inner" – unión interna (nomatch = 0) – solo filas donde coinciden X e Y;

  3. "all.x" y "left" – left join – todas las filas de X, NA donde no coinciden Y:

  4. "outer" y "full" – unión externa completa – todas las filas de X e Y, NA donde no coinciden

  5. "only.x" y "not.y" : filas X que no se unen o "not.y" devuelven X filas donde no hay coincidencia Y

  6. "only.y" y "not.x" : filas Y que no se unen o "not.x" regresan donde no hay coincidencia X
  7. "not.both" : combinación exclusiva que devuelve las filas X e Y en las que no hay coincidencia con la otra tabla, es decir, una exclusiva o (XOR)
  8. "cross" – producto cruzado o cartesiano con cada fila de X combinada con cada fila de Y

El valor predeterminado es join="all.y" que corresponde al valor predeterminado actual.

Los valores de cadena “all”, “all.x” y “all.y” corresponden a los parámetros merge() . Las cadenas “derecha”, “izquierda”, “interna” y “externa” pueden ser más favorables para los usuarios de SQL.

Las cadenas de “ambos” y “no. Ambos” son mi mejor sugerencia en este momento, pero alguien puede tener mejores sugerencias de cuerdas para la unión interna y la unión exclusiva. (No estoy seguro si la terminología correcta es “exclusiva”, corrígeme si hay un término apropiado para una unión “XOR”).

El uso de join="not.y" es una alternativa para X[-Y,j] o X[!Y,j] syntax de unión y tal vez más clara (para mí), aunque no estoy seguro si son lo mismo (nueva función en data.table versión 1.8.3).

La combinación cruzada puede ser útil a veces, pero puede no encajar en el paradigma de tabla de datos.