¿Por qué la combinación X [Y] de data.tables no permite una combinación externa completa o una combinación izquierda?

123

Esta es una pregunta un poco filosófica sobre la sintaxis de unión de data.table. Estoy encontrando cada vez más usos para data.tables, pero sigo aprendiendo ...

El formato X[Y]de combinación para data.tables es muy conciso, práctico y eficiente, pero hasta donde yo sé, 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 tanto en X como en 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 - unión exterior izquierda

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

Para mí, los valores de los parámetros nomatch = 0y nomatch = NAno son muy intuitivos para las acciones que se realizan. Es más fácil para mí entender y recordar la mergesintaxis: all = TRUE, all.x = TRUEy all.y = TRUE. Dado que la X[Y]operación se parece mergemucho más a match, ¿por qué no usar la mergesintaxis para las combinaciones en lugar matchdel nomatchparámetro de la función ?

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

# sample X and Y data.tables
library(data.table)
X <- data.table(t = 1:4, a = (1:4)^2)
setkey(X, t)
X
#    t  a
# 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
#    t  b
# 1: 3  9
# 2: 4 16
# 3: 5 25
# 4: 6 36

# all rows from Y - right outer join
X[Y]  # default
#  t  a  b
# 1: 3  9  9
# 2: 4 16 16
# 3: 5 NA 25
# 4: 6 NA 36

X[Y, nomatch = NA]  # same as above
#    t  a  b
# 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
#    t  a  b
# 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]  
#    t  a  b
# 1: 3  9  9
# 2: 4 16 16

merge(X, Y, by = "t")  # same as above
#    t  a  b
# 1: 3  9  9
# 2: 4 16 16

merge(X, Y, by = "t", all = FALSE)  # same as above
#    t  a  b
# 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)
#    t  a  b
# 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)
#    t  a  b
# 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 on=sintaxis, que permite combinaciones ad hoc en campos distintos a la clave principal. La respuesta de jangorecki a la pregunta ¿Cómo unir (fusionar) marcos de datos (interno, externo, izquierdo, derecho)? proporciona algunos ejemplos de tipos de combinación adicionales que puede manejar data.table.

Douglas Clark
fuente
4
¿Ha leído la pregunta frecuente 1.12 ? Siempre puede llamar Y[X]si desea la combinación externa izquierda de X[Y]y rbind(Y[X],X[Y])si desea una combinación externa completa
mnel
Vea mi respuesta para obtener un enfoque más data.table para la combinación externa completa
mnel
@mnel, supongo que su unique()enfoque a continuación para la combinación completa es preferible rbind(Y[X],X[Y]), ya que el rbind implicaría copiar la tabla. ¿Está bien?
Douglas Clark
a mi leal saber y entender, sí. No he probado si tres llamadas únicas más pequeñas son más rápidas que una grande (por ejemplo unique(c(unique(X[,t]), unique(Y[,t])), esto debería ser más eficiente en memoria, ya que solo combina dos listas que serán menores o iguales que la cantidad de filas en X e Y .
mnel
2
Tu pregunta es una buena descripción; Encontré respuestas a mis preguntas en su pregunta. Gracias
irriss

Respuestas:

71

Para citar las data.table preguntas frecuentes 1.11 ¿Cuál es la diferencia entre X[Y]y merge(X, Y)?

X[Y] es una combinación, buscando las filas de X usando Y (o la clave de Y si tiene una) como índice.

Y[X] es una combinación, buscando las filas de Y usando X (o la tecla de X si tiene una)

merge(X,Y)hace ambas formas al mismo tiempo. El número de filas de X[Y]y Y[X]suele diferir, mientras que el número de filas devuelto por merge(X,Y)y merge(Y,X)es el mismo.

PERO eso pierde el punto principal. La mayoría de las tareas requieren que se realice algo en los datos después de una combinación o fusión. ¿Por qué fusionar todas las columnas de datos, solo para usar un pequeño subconjunto de ellas después? Puede sugerir merge(X[,ColsNeeded1],Y[,ColsNeeded2]), pero eso requiere que el programador determine qué columnas son necesarias. X[Y,j] en data.table hace todo eso en un solo paso. Cuando escribe X[Y,sum(foo*bar)], data.table inspecciona automáticamente la jexpresión para ver qué columnas usa. Solo subconjtará solo esas columnas; los demás son ignorados. La memoria solo se crea para las columnas que jutiliza, y las Ycolumnas disfrutan de reglas de reciclaje R estándar dentro del contexto de cada grupo. Digamos que fooestá adentro Xy la barra está adentro Y(junto con otras 20 columnas adentro Y). No esX[Y,sum(foo*bar)] ¿Más rápido de programar y más rápido de ejecutar que una combinación de todo seguido por un subconjunto?


Si desea 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)]]
##   t  b  a
## 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)]]
mnel
fuente
5
Gracias @mnel. La pregunta frecuente 1.12 no menciona la combinación externa completa o izquierda. Su sugerencia de unión externa completa con unique () es de gran ayuda. Eso debería estar en las preguntas frecuentes. Sé que Matthew Dowle "lo diseñó para su propio uso, y lo quería de esa manera". (FAQ 1.9), pero pensé que X[Y,all=T]podría ser una forma elegante de especificar una combinación externa completa dentro de la sintaxis data.table X [Y]. O X[Y,all.x=T]para la unión de la izquierda. Me pregunté por qué no estaba diseñado de esa manera. Solo un pensamiento.
Douglas Clark
1
@DouglasClark Se agregó la respuesta y se presentó 2302: Agregue la sintaxis de combinación de combinación de mnel a las preguntas frecuentes (con tiempos) . ¡Grandes sugerencias!
Matt Dowle
1
@mnel Gracias por la solución ... me alegró el día ... :)
Ankit
@mnel ¿Hay alguna forma de imputar NA's con 0 cuando actuamos X[Y[J(unique_keys)]]?
Ankit
11
lo que me impresiona de la documentación de data.table es que puede ser tan detallado, pero seguir siendo tan críptico ...
NiuBiBang
24

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

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

Agregar el cuarto parece una buena idea. Digamos que agregamos full=TRUEo both=TRUEo merge=TRUE(¿no está seguro del mejor nombre de argumento?) Entonces no se me había ocurrido antes que X[Y,j,merge=TRUE]sería útil por las razones posteriores al PERO en la pregunta frecuente 1.12. Nueva solicitud de función ahora agregada y vinculada aquí, gracias:

FR # 2301: Agregue el argumento merge = TRUE para las uniones X [Y] e Y [X] como lo hace merge ().

Las versiones recientes se han acelerado merge.data.table(al tomar una copia superficial internamente para configurar las claves de manera más eficiente, por ejemplo). Así que estamos tratando de llevar merge()y X[Y]más cerca, y proporcionar a todas las opciones de usuario para una flexibilidad total. Ambos tienen pros y contras. Otra solicitud de función destacada es:

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

Si hay otros, por favor sigan viniendo.

Por esta parte de la pregunta:

¿Por qué no usar la sintaxis de combinación para combinaciones en lugar del parámetro nomatch de la función de coincidencia?

Si prefiere merge()sintaxis y sus 3 argumentos all, all.xy all.yentonces sólo tiene que utilizar que en lugar de X[Y]. Creo que debería cubrir todos los casos. ¿O quieres decir qué es el argumento de un solo nomatchen [.data.table? Si es así, es la forma que parecía natural dada la pregunta frecuente 2.14: "¿Puede explicar más por qué data.table se inspira en la sintaxis A [B] en base?". Pero además, nomatchsolo toma dos valores actualmente 0y NA. Eso podría extenderse para que un valor negativo signifique algo, o 12 significaría usar los valores de la 12ª fila para completar NA, por ejemplo, o nomatchen el futuro podría ser un vector o incluso él mismo a data.table.

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

Matt Dowle
fuente
Gracias @Matthew. La respuesta de @ mnel es excelente, pero mi pregunta no era cómo hacer una combinación completa o izquierda, sino "¿Hay alguna razón por la que solo se admiten dos tipos de combinaciones?" Así que ahora es un poco más filosófico ;-) En realidad, no prefiero la sintaxis de fusión, pero parece que existe una tradición de R para construir sobre cosas existentes con las que la gente está familiarizada. Había garabateado join="all", join="all.x", join="all.y" and join="x.and.y"en el margen de mis notas. No estoy seguro si eso es mejor.
Douglas Clark
@DouglasClark Tal vez joinasí, buena idea. Publiqué en datatable-help, así que veamos. Quizás también dé data.tablealgo de tiempo para acomodarse. ¿Ha llegado a by-without-by todavía, por ejemplo, y se ha unido al alcance heredado ?
Matt Dowle
Como se indica en mi comentario anterior, sugiero añadir una joinpalabra clave para, cuando i es una tabla de datos: X[Y,j,join=string]. Se sugiere que los posibles valores de cadena para la combinación sean: 1) "all.y" y "right" -
Douglas Clark
1
Hola Matt, la biblioteca data.table es fantástica; gracias por eso; aunque creo que el comportamiento de la combinación (que es una combinación externa derecha por defecto) debe explicarse de manera destacada en la documentación principal; Me tomó 3 días darme cuenta de esto.
Timothée HENRY
1
@tucson Solo para vincular aquí, ahora archivado como número 709 .
Matt Dowle
17

Esta "respuesta" es una propuesta para la discusión: Como se indica en mi comentario, yo sugiero agregar un joinparámetro a [.data.table () para permitir que otros tipos de uniones, es decir: X[Y,j,join=string]. Además de los 4 tipos de combinaciones ordinarias, también sugiero admitir 3 tipos de combinaciones exclusivas y la combinación cruzada .

Se joinpropone que los valores de cadena (y alias) para los distintos tipos de combinación sean:

  1. "all.y"y "right"- combinación derecha, la tabla de datos actual predeterminada (nomatch = NA) - todas las filas Y con NA donde no hay coincidencia X;
  2. "both"y "inner" - unión interna (nomatch = 0) - solo filas donde X e Y coinciden;

  3. "all.x"y "left" - unión a la izquierda - todas las filas de X, NA donde no coincide 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 que no se unen y que regresan donde no hay coincidencia de Y

  6. "only.y" y "not.x"- filas Y de retorno no unidas o anti-unión donde no hay coincidencia X
  7. "not.both" - combinación exclusiva que devuelve filas X e Y donde no hay coincidencia con la otra tabla, es decir, una exclusiva-o (XOR)
  8. "cross"- unión cruzada o producto cartesiano con cada fila de X emparejada con cada fila de Y

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

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

Las cadenas "both" y "not.both" son mi mejor sugerencia en este momento, pero alguien puede tener mejores sugerencias de cadenas para la combinación interna y la combinación exclusiva. (No estoy seguro de si "exclusivo" es la terminología correcta, corrígeme si existe un término adecuado para una unión "XOR").

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

La unión cruzada puede ser útil a veces, pero puede que no encaje en el paradigma data.table.

Douglas Clark
fuente
1
Envíe esto a datatable-help para discutirlo.
Matt Dowle
3
1 Pero, por favor, o bien enviar a tabla de datos-ayuda , o presentar una solicitud de función . No me importa agregar, joinpero a menos que llegue al rastreador, se olvidará.
Matt Dowle
1
Veo que no ha iniciado sesión en SO por un tiempo. Así que archivé esto en FR # 2301
Matt Dowle
@MattDowle, +1 para esta función. (Intenté hacerlo a través de FR # 2301 pero recibo un mensaje de permisos denegados).
adilapapaya
@adilapapaya Nos mudamos de RForge a GitHub. Haga +1 aquí: github.com/Rdatatable/data.table/issues/614 . Arun transfirió los problemas para que no se perdieran.
Matt Dowle