than multiple more left inner funcion data columns r join merge dataframe r-faq

multiple - ¿Cómo unir(combinar) marcos de datos(interno, externo, izquierdo, derecho)?



merge more than 2 data frames in r (13)

Dados dos marcos de datos:

df1 = data.frame(CustomerId = c(1:6), Product = c(rep("Toaster", 3), rep("Radio", 3))) df2 = data.frame(CustomerId = c(2, 4, 6), State = c(rep("Alabama", 2), rep("Ohio", 1))) df1 # CustomerId Product # 1 Toaster # 2 Toaster # 3 Toaster # 4 Radio # 5 Radio # 6 Radio df2 # CustomerId State # 2 Alabama # 4 Alabama # 6 Ohio

¿Cómo puedo hacer un estilo de base de datos, es decir, estilo sql, uniones ? Es decir, ¿cómo obtengo?

  • Una unión interna de df1 y df2 :
    Devuelve solo las filas en las que la tabla izquierda tiene claves coincidentes en la tabla derecha.
  • Una unión externa de df1 y df2 :
    Devuelve todas las filas de ambas tablas, une registros desde la izquierda que tienen claves coincidentes en la tabla derecha.
  • Una combinación externa izquierda (o simplemente combinación izquierda) de df1 y df2
    Devuelva todas las filas de la tabla izquierda y cualquier fila con claves coincidentes de la tabla derecha.
  • Una unión externa derecha de df1 y df2
    Devuelva todas las filas de la tabla derecha y cualquier fila con claves coincidentes de la tabla izquierda.

Crédito adicional:

¿Cómo puedo hacer una declaración de selección de estilo SQL?


  1. Usando la mergefunción podemos seleccionar la variable de la tabla izquierda o la tabla derecha, de la misma manera que todos estamos familiarizados con la instrucción de selección en SQL (EX: Seleccionar a. * ... o Seleccionar b. * De .....)
  2. Tenemos que agregar un código adicional que se subcontratará de la tabla recién agregada

    • SQL: - select a.* from df1 a inner join df2 b on a.CustomerId=b.CustomerId

    • R: - merge(df1, df2, by.x = "CustomerId", by.y = "CustomerId")[,names(df1)]

Mismo camino

  • SQL: - select b.* from df1 a inner join df2 b on a.CustomerId=b.CustomerId

  • R: - merge(df1, df2, by.x = "CustomerId", by.y = "CustomerId")[,names(df2)]


Actualización en data.table métodos para unir conjuntos de datos. Vea los ejemplos a continuación para cada tipo de unión. Hay dos métodos, uno de [.data.table cuando se pasa el segundo data.table como el primer argumento del subconjunto, otra forma es usar la función de merge que se envió al método fast data.table.

Actualización del 2016-04-01 - ¡y no es una broma de April Fools!
En la versión 1.9.7 de data.table, las combinaciones ahora son capaces de usar el índice existente, lo que reduce enormemente el tiempo de una combinación. Debajo del código y del índice de referencia NO se usan los índices data.table en la unión . Si está buscando una unión casi en tiempo real, debe utilizar los índices data.table.

df1 = data.frame(CustomerId = c(1:6), Product = c(rep("Toaster", 3), rep("Radio", 3))) df2 = data.frame(CustomerId = c(2L, 4L, 7L), State = c(rep("Alabama", 2), rep("Ohio", 1))) # one value changed to show full outer join library(data.table) dt1 = as.data.table(df1) dt2 = as.data.table(df2) setkey(dt1, CustomerId) setkey(dt2, CustomerId) # right outer join keyed data.tables dt1[dt2] setkey(dt1, NULL) setkey(dt2, NULL) # right outer join unkeyed data.tables - use `on` argument dt1[dt2, on = "CustomerId"] # left outer join - swap dt1 with dt2 dt2[dt1, on = "CustomerId"] # inner join - use `nomatch` argument dt1[dt2, nomatch=0L, on = "CustomerId"] # anti join - use `!` operator dt1[!dt2, on = "CustomerId"] # inner join merge(dt1, dt2, by = "CustomerId") # full outer join merge(dt1, dt2, by = "CustomerId", all = TRUE) # see ?merge.data.table arguments for other cases

Por debajo de las pruebas de referencia, base R, sqldf, dplyr y data.table.
Pruebas de referencia conjuntos de datos sin clave / no indexados. Puede obtener un rendimiento aún mejor si está utilizando claves en sus tablas de datos o índices con sqldf. Base R y dplyr no tienen índices ni claves, por lo que no incluí ese escenario en el punto de referencia.
El punto de referencia se realiza en los conjuntos de datos de las filas de 5M-1, hay valores comunes de 5M-2 en la columna de unión, por lo que cada escenario (izquierda, derecha, completo, interno) puede probarse y la combinación aún no es trivial.

library(microbenchmark) library(sqldf) library(dplyr) library(data.table) n = 5e6 set.seed(123) df1 = data.frame(x=sample(n,n-1L), y1=rnorm(n-1L)) df2 = data.frame(x=sample(n,n-1L), y2=rnorm(n-1L)) dt1 = as.data.table(df1) dt2 = as.data.table(df2) # inner join microbenchmark(times = 10L, base = merge(df1, df2, by = "x"), sqldf = sqldf("SELECT * FROM df1 INNER JOIN df2 ON df1.x = df2.x"), dplyr = inner_join(df1, df2, by = "x"), data.table = dt1[dt2, nomatch = 0L, on = "x"]) #Unit: milliseconds # expr min lq mean median uq max neval # base 15546.0097 16083.4915 16687.117 16539.0148 17388.290 18513.216 10 # sqldf 44392.6685 44709.7128 45096.401 45067.7461 45504.376 45563.472 10 # dplyr 4124.0068 4248.7758 4281.122 4272.3619 4342.829 4411.388 10 # data.table 937.2461 946.0227 1053.411 973.0805 1214.300 1281.958 10 # left outer join microbenchmark(times = 10L, base = merge(df1, df2, by = "x", all.x = TRUE), sqldf = sqldf("SELECT * FROM df1 LEFT OUTER JOIN df2 ON df1.x = df2.x"), dplyr = left_join(df1, df2, by = c("x"="x")), data.table = dt2[dt1, on = "x"]) #Unit: milliseconds # expr min lq mean median uq max neval # base 16140.791 17107.7366 17441.9538 17414.6263 17821.9035 19453.034 10 # sqldf 43656.633 44141.9186 44777.1872 44498.7191 45288.7406 47108.900 10 # dplyr 4062.153 4352.8021 4780.3221 4409.1186 4450.9301 8385.050 10 # data.table 823.218 823.5557 901.0383 837.9206 883.3292 1277.239 10 # right outer join microbenchmark(times = 10L, base = merge(df1, df2, by = "x", all.y = TRUE), sqldf = sqldf("SELECT * FROM df2 LEFT OUTER JOIN df1 ON df2.x = df1.x"), dplyr = right_join(df1, df2, by = "x"), data.table = dt1[dt2, on = "x"]) #Unit: milliseconds # expr min lq mean median uq max neval # base 15821.3351 15954.9927 16347.3093 16044.3500 16621.887 17604.794 10 # sqldf 43635.5308 43761.3532 43984.3682 43969.0081 44044.461 44499.891 10 # dplyr 3936.0329 4028.1239 4102.4167 4045.0854 4219.958 4307.350 10 # data.table 820.8535 835.9101 918.5243 887.0207 1005.721 1068.919 10 # full outer join microbenchmark(times = 10L, base = merge(df1, df2, by = "x", all = TRUE), #sqldf = sqldf("SELECT * FROM df1 FULL OUTER JOIN df2 ON df1.x = df2.x"), # not supported dplyr = full_join(df1, df2, by = "x"), data.table = merge(dt1, dt2, by = "x", all = TRUE)) #Unit: seconds # expr min lq mean median uq max neval # base 16.176423 16.908908 17.485457 17.364857 18.271790 18.626762 10 # dplyr 7.610498 7.666426 7.745850 7.710638 7.832125 7.951426 10 # data.table 2.052590 2.130317 2.352626 2.208913 2.470721 2.951948 10


Al unir dos marcos de datos con ~ 1 millón de filas cada uno, uno con 2 columnas y el otro con ~ 20, sorprendentemente encontré que merge(..., all.x = TRUE, all.y = TRUE) es más rápido que dplyr::full_join() . Esto es con dplyr v0.4

La fusión toma ~ 17 segundos, full_join toma ~ 65 segundos.

Un poco de comida para, sin embargo, ya que en general por defecto dplyr para tareas de manipulación.


Existe el enfoque data.table para una unión interna, que es muy eficiente en tiempo y memoria (y es necesario para algunos cuadros de datos más grandes):

library(data.table) dt1 <- data.table(df1, key = "CustomerId") dt2 <- data.table(df2, key = "CustomerId") joined.dt1.dt.2 <- dt1[dt2]

merge también funciona en data.tables (ya que es genérico y llama merge.data.table )

merge(dt1, dt2)

data.table documentado en :
Cómo hacer una operación de fusión data.table
Traducción de uniones SQL en claves externas a sintaxis de datos R
Alternativas eficientes para fusionar para datos más grandes. Marcos R
¿Cómo hacer una unión externa izquierda básica con data.table en R?

Otra opción más es la función de join que se encuentra en el paquete plyr

library(plyr) join(df1, df2, type = "inner") # CustomerId Product State # 1 2 Toaster Alabama # 2 4 Radio Alabama # 3 6 Radio Ohio

Opciones de type : inner , left , right , full .

Desde ?join : a diferencia de merge , [ join ] conserva el orden de x sin importar qué tipo de unión se use.


Hay algunos buenos ejemplos de cómo hacer esto en el R Wiki . Robaré un par aquí

Método de fusión

Dado que sus claves tienen el mismo nombre, la forma más corta de hacer una unión interna es fusionar ():

merge(df1,df2)

se puede crear una unión interna completa (todos los registros de ambas tablas) con la palabra clave "todos":

merge(df1,df2, all=TRUE)

una unión externa izquierda de df1 y df2:

merge(df1,df2, all.x=TRUE)

una unión externa derecha de df1 y df2:

merge(df1,df2, all.y=TRUE)

puedes voltearlos, abofetearlos y frotarlos para obtener las otras dos uniones externas sobre las que has preguntado :)

Método de subíndice

Una combinación externa izquierda con df1 a la izquierda usando un método de subíndice sería:

df1[,"State"]<-df2[df1[ ,"Product"], "State"]

La otra combinación de combinaciones externas se puede crear agrupando el ejemplo del subíndice de combinación externa izquierda. (Sí, sé que es el equivalente a decir "Lo dejaré como un ejercicio para el lector ...")


Nuevo en 2014:

Especialmente si también está interesado en la manipulación de datos en general (incluyendo clasificación, filtrado, subconjunto, resumen, etc.), definitivamente debería echar un vistazo a dplyr , que viene con una variedad de funciones diseñadas para facilitar su trabajo específicamente con datos. Marcos y algunos otros tipos de bases de datos. Incluso ofrece una interfaz SQL bastante elaborada, e incluso una función para convertir (la mayoría) del código SQL directamente en R.

Las cuatro funciones relacionadas con la unión en el paquete dplyr son (para citar):

  • inner_join(x, y, by = NULL, copy = FALSE, ...) : devuelve todas las filas de x donde hay valores coincidentes en y, y todas las columnas de x e y
  • left_join(x, y, by = NULL, copy = FALSE, ...) : devuelve todas las filas de x, y todas las columnas de x e y
  • semi_join(x, y, by = NULL, copy = FALSE, ...) : devuelve todas las filas de x donde hay valores coincidentes en y, manteniendo solo las columnas de x.
  • anti_join(x, y, by = NULL, copy = FALSE, ...) : devuelve todas las filas de x donde no hay valores coincidentes en y, manteniendo solo las columnas de x

Todo está here en gran detalle.

La selección de columnas se puede hacer select(df,"column") . Si eso no es suficiente para SQL, entonces está la función sql() , en la que puede ingresar el código SQL como está, y realizará la operación que especificó tal como estaba escribiendo en R todo el tiempo (para obtener más información , por favor refiérase a la viñeta dplyr / database ). Por ejemplo, si se aplica correctamente, sql("SELECT * FROM hflights") seleccionará todas las columnas de la tabla dplyr de "hflights" (una "tbl").


Para el caso de una combinación izquierda con una cardinalidad 0..*:0..1 o una combinación derecha con una cardinalidad 0..1:0..* es posible asignar en el lugar las columnas unilaterales desde la combinación ( la tabla 0..1 ) directamente sobre la junta (la tabla 0..* ), y por lo tanto evita la creación de una tabla de datos completamente nueva. Esto requiere hacer coincidir las columnas clave de la unión con el ensamblador e indexar + ordenar las filas del ensamblador en consecuencia para la asignación.

Si la clave es una sola columna, entonces podemos usar una sola llamada para que match() para hacer la coincidencia. Este es el caso que cubriré en esta respuesta.

Aquí hay un ejemplo basado en el OP, excepto que he agregado una fila adicional a df2 con un id de 7 para probar el caso de una clave no coincidente en el carpintero. Esto es efectivamente df1 left join df2 :

df1 <- data.frame(CustomerId=1:6,Product=c(rep(''Toaster'',3L),rep(''Radio'',3L))); df2 <- data.frame(CustomerId=c(2L,4L,6L,7L),State=c(rep(''Alabama'',2L),''Ohio'',''Texas'')); df1[names(df2)[-1L]] <- df2[match(df1[,1L],df2[,1L]),-1L]; df1; ## CustomerId Product State ## 1 1 Toaster <NA> ## 2 2 Toaster Alabama ## 3 3 Toaster <NA> ## 4 4 Radio Alabama ## 5 5 Radio <NA> ## 6 6 Radio Ohio

En lo anterior, codifiqué por asunción que la columna clave es la primera columna de ambas tablas de entrada. Yo diría que, en general, esto no es una suposición irrazonable, ya que, si tiene un data.frame con una columna clave, sería extraño si no se hubiera configurado como la primera columna del data.frame de El principio. Y siempre puedes reordenar las columnas para hacerlo así. Una consecuencia ventajosa de esta suposición es que el nombre de la columna clave no tiene que estar codificado, aunque supongo que simplemente está reemplazando una suposición por otra. La concisión es otra ventaja de la indexación de enteros, así como la velocidad. En los puntos de referencia a continuación, cambiaré la implementación para usar la indexación de nombres de cadenas para que coincida con las implementaciones de la competencia.

Creo que esta es una solución particularmente apropiada si tiene varias tablas que desea unir en una sola tabla grande. La reconstrucción repetida de la tabla completa para cada combinación sería innecesaria e ineficiente.

Por otro lado, si necesita que la junta permanezca inalterada a través de esta operación por cualquier motivo, esta solución no se puede usar, ya que modifica la unión directamente. Aunque en ese caso, simplemente podría hacer una copia y realizar la (s) asignación (es) en el lugar de la copia.

Como nota al margen, examiné brevemente posibles soluciones de coincidencia para claves de varias columnas. Desafortunadamente, las únicas soluciones coincidentes que encontré fueron:

  • Concatenaciones ineficientes. por ejemplo, match(interaction(df1$a,df1$b),interaction(df2$a,df2$b)) , o la misma idea con paste() .
  • conjunciones cartesianas ineficientes, por ejemplo, outer(df1$a,df2$a,`==`) & outer(df1$b,df2$b,`==`) .
  • base R merge() y funciones de combinación equivalentes basadas en paquetes, que siempre asignan una nueva tabla para devolver el resultado combinado y, por lo tanto, no son adecuadas para una solución basada en la asignación en el lugar.

Por ejemplo, vea Cómo hacer coincidir varias columnas en diferentes marcos de datos y obtener otra columna como resultado , hacer coincidir dos columnas con otras dos columnas , Hacer coincidir en varias columnas y la duplicación de esta pregunta donde originalmente se me ocurrió la solución in situ, Combinar Dos marcos de datos con diferente número de filas en R.

Benchmarking

Decidí hacer mi propia evaluación comparativa para ver cómo el enfoque de asignación en el lugar se compara con las otras soluciones que se han ofrecido en esta pregunta.

Código de prueba:

library(microbenchmark); library(data.table); library(sqldf); library(plyr); library(dplyr); solSpecs <- list( merge=list(testFuncs=list( inner=function(df1,df2,key) merge(df1,df2,key), left =function(df1,df2,key) merge(df1,df2,key,all.x=T), right=function(df1,df2,key) merge(df1,df2,key,all.y=T), full =function(df1,df2,key) merge(df1,df2,key,all=T) )), data.table.unkeyed=list(argSpec=''data.table.unkeyed'',testFuncs=list( inner=function(dt1,dt2,key) dt1[dt2,on=key,nomatch=0L,allow.cartesian=T], left =function(dt1,dt2,key) dt2[dt1,on=key,allow.cartesian=T], right=function(dt1,dt2,key) dt1[dt2,on=key,allow.cartesian=T], full =function(dt1,dt2,key) merge(dt1,dt2,key,all=T,allow.cartesian=T) ## calls merge.data.table() )), data.table.keyed=list(argSpec=''data.table.keyed'',testFuncs=list( inner=function(dt1,dt2) dt1[dt2,nomatch=0L,allow.cartesian=T], left =function(dt1,dt2) dt2[dt1,allow.cartesian=T], right=function(dt1,dt2) dt1[dt2,allow.cartesian=T], full =function(dt1,dt2) merge(dt1,dt2,all=T,allow.cartesian=T) ## calls merge.data.table() )), sqldf.unindexed=list(testFuncs=list( ## note: must pass connection=NULL to avoid running against the live DB connection, which would result in collisions with the residual tables from the last query upload inner=function(df1,df2,key) sqldf(paste0(''select * from df1 inner join df2 using('',paste(collapse='','',key),'')''),connection=NULL), left =function(df1,df2,key) sqldf(paste0(''select * from df1 left join df2 using('',paste(collapse='','',key),'')''),connection=NULL), right=function(df1,df2,key) sqldf(paste0(''select * from df2 left join df1 using('',paste(collapse='','',key),'')''),connection=NULL) ## can''t do right join proper, not yet supported; inverted left join is equivalent ##full =function(df1,df2,key) sqldf(paste0(''select * from df1 full join df2 using('',paste(collapse='','',key),'')''),connection=NULL) ## can''t do full join proper, not yet supported; possible to hack it with a union of left joins, but too unreasonable to include in testing )), sqldf.indexed=list(testFuncs=list( ## important: requires an active DB connection with preindexed main.df1 and main.df2 ready to go; arguments are actually ignored inner=function(df1,df2,key) sqldf(paste0(''select * from main.df1 inner join main.df2 using('',paste(collapse='','',key),'')'')), left =function(df1,df2,key) sqldf(paste0(''select * from main.df1 left join main.df2 using('',paste(collapse='','',key),'')'')), right=function(df1,df2,key) sqldf(paste0(''select * from main.df2 left join main.df1 using('',paste(collapse='','',key),'')'')) ## can''t do right join proper, not yet supported; inverted left join is equivalent ##full =function(df1,df2,key) sqldf(paste0(''select * from main.df1 full join main.df2 using('',paste(collapse='','',key),'')'')) ## can''t do full join proper, not yet supported; possible to hack it with a union of left joins, but too unreasonable to include in testing )), plyr=list(testFuncs=list( inner=function(df1,df2,key) join(df1,df2,key,''inner''), left =function(df1,df2,key) join(df1,df2,key,''left''), right=function(df1,df2,key) join(df1,df2,key,''right''), full =function(df1,df2,key) join(df1,df2,key,''full'') )), dplyr=list(testFuncs=list( inner=function(df1,df2,key) inner_join(df1,df2,key), left =function(df1,df2,key) left_join(df1,df2,key), right=function(df1,df2,key) right_join(df1,df2,key), full =function(df1,df2,key) full_join(df1,df2,key) )), in.place=list(testFuncs=list( left =function(df1,df2,key) { cns <- setdiff(names(df2),key); df1[cns] <- df2[match(df1[,key],df2[,key]),cns]; df1; }, right=function(df1,df2,key) { cns <- setdiff(names(df1),key); df2[cns] <- df1[match(df2[,key],df1[,key]),cns]; df2; } )) ); getSolTypes <- function() names(solSpecs); getJoinTypes <- function() unique(unlist(lapply(solSpecs,function(x) names(x$testFuncs)))); getArgSpec <- function(argSpecs,key=NULL) if (is.null(key)) argSpecs$default else argSpecs[[key]]; initSqldf <- function() { sqldf(); ## creates sqlite connection on first run, cleans up and closes existing connection otherwise if (exists(''sqldfInitFlag'',envir=globalenv(),inherits=F) && sqldfInitFlag) { ## false only on first run sqldf(); ## creates a new connection } else { assign(''sqldfInitFlag'',T,envir=globalenv()); ## set to true for the one and only time }; ## end if invisible(); }; ## end initSqldf() setUpBenchmarkCall <- function(argSpecs,joinType,solTypes=getSolTypes(),env=parent.frame()) { ## builds and returns a list of expressions suitable for passing to the list argument of microbenchmark(), and assigns variables to resolve symbol references in those expressions callExpressions <- list(); nms <- character(); for (solType in solTypes) { testFunc <- solSpecs[[solType]]$testFuncs[[joinType]]; if (is.null(testFunc)) next; ## this join type is not defined for this solution type testFuncName <- paste0(''tf.'',solType); assign(testFuncName,testFunc,envir=env); argSpecKey <- solSpecs[[solType]]$argSpec; argSpec <- getArgSpec(argSpecs,argSpecKey); argList <- setNames(nm=names(argSpec$args),vector(''list'',length(argSpec$args))); for (i in seq_along(argSpec$args)) { argName <- paste0(''tfa.'',argSpecKey,i); assign(argName,argSpec$args[[i]],envir=env); argList[[i]] <- if (i%in%argSpec$copySpec) call(''copy'',as.symbol(argName)) else as.symbol(argName); }; ## end for callExpressions[[length(callExpressions)+1L]] <- do.call(call,c(list(testFuncName),argList),quote=T); nms[length(nms)+1L] <- solType; }; ## end for names(callExpressions) <- nms; callExpressions; }; ## end setUpBenchmarkCall() harmonize <- function(res) { res <- as.data.frame(res); ## coerce to data.frame for (ci in which(sapply(res,is.factor))) res[[ci]] <- as.character(res[[ci]]); ## coerce factor columns to character for (ci in which(sapply(res,is.logical))) res[[ci]] <- as.integer(res[[ci]]); ## coerce logical columns to integer (works around sqldf quirk of munging logicals to integers) ##for (ci in which(sapply(res,inherits,''POSIXct''))) res[[ci]] <- as.double(res[[ci]]); ## coerce POSIXct columns to double (works around sqldf quirk of losing POSIXct class) ----- POSIXct doesn''t work at all in sqldf.indexed res <- res[order(names(res))]; ## order columns res <- res[do.call(order,res),]; ## order rows res; }; ## end harmonize() checkIdentical <- function(argSpecs,solTypes=getSolTypes()) { for (joinType in getJoinTypes()) { callExpressions <- setUpBenchmarkCall(argSpecs,joinType,solTypes); if (length(callExpressions)<2L) next; ex <- harmonize(eval(callExpressions[[1L]])); for (i in seq(2L,len=length(callExpressions)-1L)) { y <- harmonize(eval(callExpressions[[i]])); if (!isTRUE(all.equal(ex,y,check.attributes=F))) { ex <<- ex; y <<- y; solType <- names(callExpressions)[i]; stop(paste0(''non-identical: '',solType,'' '',joinType,''.'')); }; ## end if }; ## end for }; ## end for invisible(); }; ## end checkIdentical() testJoinType <- function(argSpecs,joinType,solTypes=getSolTypes(),metric=NULL,times=100L) { callExpressions <- setUpBenchmarkCall(argSpecs,joinType,solTypes); bm <- microbenchmark(list=callExpressions,times=times); if (is.null(metric)) return(bm); bm <- summary(bm); res <- setNames(nm=names(callExpressions),bm[[metric]]); attr(res,''unit'') <- attr(bm,''unit''); res; }; ## end testJoinType() testAllJoinTypes <- function(argSpecs,solTypes=getSolTypes(),metric=NULL,times=100L) { joinTypes <- getJoinTypes(); resList <- setNames(nm=joinTypes,lapply(joinTypes,function(joinType) testJoinType(argSpecs,joinType,solTypes,metric,times))); if (is.null(metric)) return(resList); units <- unname(unlist(lapply(resList,attr,''unit''))); res <- do.call(data.frame,c(list(join=joinTypes),setNames(nm=solTypes,rep(list(rep(NA_real_,length(joinTypes))),length(solTypes))),list(unit=units,stringsAsFactors=F))); for (i in seq_along(resList)) res[i,match(names(resList[[i]]),names(res))] <- resList[[i]]; res; }; ## end testAllJoinTypes() testGrid <- function(makeArgSpecsFunc,sizes,overlaps,solTypes=getSolTypes(),joinTypes=getJoinTypes(),metric=''median'',times=100L) { res <- expand.grid(size=sizes,overlap=overlaps,joinType=joinTypes,stringsAsFactors=F); res[solTypes] <- NA_real_; res$unit <- NA_character_; for (ri in seq_len(nrow(res))) { size <- res$size[ri]; overlap <- res$overlap[ri]; joinType <- res$joinType[ri]; argSpecs <- makeArgSpecsFunc(size,overlap); checkIdentical(argSpecs,solTypes); cur <- testJoinType(argSpecs,joinType,solTypes,metric,times); res[ri,match(names(cur),names(res))] <- cur; res$unit[ri] <- attr(cur,''unit''); }; ## end for res; }; ## end testGrid()

Aquí hay un punto de referencia del ejemplo basado en el OP que demostré anteriormente:

## OP''s example, supplemented with a non-matching row in df2 argSpecs <- list( default=list(copySpec=1:2,args=list( df1 <- data.frame(CustomerId=1:6,Product=c(rep(''Toaster'',3L),rep(''Radio'',3L))), df2 <- data.frame(CustomerId=c(2L,4L,6L,7L),State=c(rep(''Alabama'',2L),''Ohio'',''Texas'')), ''CustomerId'' )), data.table.unkeyed=list(copySpec=1:2,args=list( as.data.table(df1), as.data.table(df2), ''CustomerId'' )), data.table.keyed=list(copySpec=1:2,args=list( setkey(as.data.table(df1),CustomerId), setkey(as.data.table(df2),CustomerId) )) ); ## prepare sqldf initSqldf(); sqldf(''create index df1_key on df1(CustomerId);''); ## upload and create an sqlite index on df1 sqldf(''create index df2_key on df2(CustomerId);''); ## upload and create an sqlite index on df2 checkIdentical(argSpecs); testAllJoinTypes(argSpecs,metric=''median''); ## join merge data.table.unkeyed data.table.keyed sqldf.unindexed sqldf.indexed plyr dplyr in.place unit ## 1 inner 644.259 861.9345 923.516 9157.752 1580.390 959.2250 270.9190 NA microseconds ## 2 left 713.539 888.0205 910.045 8820.334 1529.714 968.4195 270.9185 224.3045 microseconds ## 3 right 1221.804 909.1900 923.944 8930.668 1533.135 1063.7860 269.8495 218.1035 microseconds ## 4 full 1302.203 3107.5380 3184.729 NA NA 1593.6475 270.7055 NA microseconds

Aquí comparto los datos de entrada aleatorios, probando diferentes escalas y diferentes patrones de superposición de claves entre las dos tablas de entrada. Este punto de referencia todavía está restringido al caso de una clave de entero de una sola columna. Además, para garantizar que la solución in situ funcione para las combinaciones izquierda y derecha de las mismas tablas, todos los datos de prueba aleatorios utilizan la 0..1:0..1cardinalidad. Esto se implementa mediante el muestreo sin reemplazo de la columna de clave del primer data.frame al generar la columna de clave del segundo data.frame.

makeArgSpecs.singleIntegerKey.optionalOneToOne <- function(size,overlap) { com <- as.integer(size*overlap); argSpecs <- list( default=list(copySpec=1:2,args=list( df1 <- data.frame(id=sample(size),y1=rnorm(size),y2=rnorm(size)), df2 <- data.frame(id=sample(c(if (com>0L) sample(df1$id,com) else integer(),seq(size+1L,len=size-com))),y3=rnorm(size),y4=rnorm(size)), ''id'' )), data.table.unkeyed=list(copySpec=1:2,args=list( as.data.table(df1), as.data.table(df2), ''id'' )), data.table.keyed=list(copySpec=1:2,args=list( setkey(as.data.table(df1),id), setkey(as.data.table(df2),id) )) ); ## prepare sqldf initSqldf(); sqldf(''create index df1_key on df1(id);''); ## upload and create an sqlite index on df1 sqldf(''create index df2_key on df2(id);''); ## upload and create an sqlite index on df2 argSpecs; }; ## end makeArgSpecs.singleIntegerKey.optionalOneToOne() ## cross of various input sizes and key overlaps sizes <- c(1e1L,1e3L,1e6L); overlaps <- c(0.99,0.5,0.01); system.time({ res <- testGrid(makeArgSpecs.singleIntegerKey.optionalOneToOne,sizes,overlaps); }); ## user system elapsed ## 22024.65 12308.63 34493.19

Escribí algo de código para crear gráficos de log-log de los resultados anteriores. Generé un gráfico separado para cada porcentaje de superposición. Está un poco desordenado, pero me gusta tener todos los tipos de soluciones y tipos de unión representados en la misma trama.

Usé la interpolación spline para mostrar una curva suave para cada combinación de tipo de solución / unión, dibujada con símbolos pch individuales. El tipo de unión es capturado por el símbolo pch, usando un punto para los corchetes internos, los ángulos izquierdo y derecho para la izquierda y la derecha, y un diamante para la totalidad El tipo de solución es capturado por el color como se muestra en la leyenda.

plotRes <- function(res,titleFunc,useFloor=F) { solTypes <- setdiff(names(res),c(''size'',''overlap'',''joinType'',''unit'')); ## derive from res normMult <- c(microseconds=1e-3,milliseconds=1); ## normalize to milliseconds joinTypes <- getJoinTypes(); cols <- c(merge=''purple'',data.table.unkeyed=''blue'',data.table.keyed=''#00DDDD'',sqldf.unindexed=''brown'',sqldf.indexed=''orange'',plyr=''red'',dplyr=''#00BB00'',in.place=''magenta''); pchs <- list(inner=20L,left=''<'',right=''>'',full=23L); cexs <- c(inner=0.7,left=1,right=1,full=0.7); NP <- 60L; ord <- order(decreasing=T,colMeans(res[res$size==max(res$size),solTypes],na.rm=T)); ymajors <- data.frame(y=c(1,1e3),label=c(''1ms'',''1s''),stringsAsFactors=F); for (overlap in unique(res$overlap)) { x1 <- res[res$overlap==overlap,]; x1[solTypes] <- x1[solTypes]*normMult[x1$unit]; x1$unit <- NULL; xlim <- c(1e1,max(x1$size)); xticks <- 10^seq(log10(xlim[1L]),log10(xlim[2L])); ylim <- c(1e-1,10^((if (useFloor) floor else ceiling)(log10(max(x1[solTypes],na.rm=T))))); ## use floor() to zoom in a little more, only sqldf.unindexed will break above, but xpd=NA will keep it visible yticks <- 10^seq(log10(ylim[1L]),log10(ylim[2L])); yticks.minor <- rep(yticks[-length(yticks)],each=9L)*1:9; plot(NA,xlim=xlim,ylim=ylim,xaxs=''i'',yaxs=''i'',axes=F,xlab=''size (rows)'',ylab=''time (ms)'',log=''xy''); abline(v=xticks,col=''lightgrey''); abline(h=yticks.minor,col=''lightgrey'',lty=3L); abline(h=yticks,col=''lightgrey''); axis(1L,xticks,parse(text=sprintf(''10^%d'',as.integer(log10(xticks))))); axis(2L,yticks,parse(text=sprintf(''10^%d'',as.integer(log10(yticks)))),las=1L); axis(4L,ymajors$y,ymajors$label,las=1L,tick=F,cex.axis=0.7,hadj=0.5); for (joinType in rev(joinTypes)) { ## reverse to draw full first, since it''s larger and would be more obtrusive if drawn last x2 <- x1[x1$joinType==joinType,]; for (solType in solTypes) { if (any(!is.na(x2[[solType]]))) { xy <- spline(x2$size,x2[[solType]],xout=10^(seq(log10(x2$size[1L]),log10(x2$size[nrow(x2)]),len=NP))); points(xy$x,xy$y,pch=pchs[[joinType]],col=cols[solType],cex=cexs[joinType],xpd=NA); }; ## end if }; ## end for }; ## end for ## custom legend ## due to logarithmic skew, must do all distance calcs in inches, and convert to user coords afterward ## the bottom-left corner of the legend will be defined in normalized figure coords, although we can convert to inches immediately leg.cex <- 0.7; leg.x.in <- grconvertX(0.275,''nfc'',''in''); leg.y.in <- grconvertY(0.6,''nfc'',''in''); leg.x.user <- grconvertX(leg.x.in,''in''); leg.y.user <- grconvertY(leg.y.in,''in''); leg.outpad.w.in <- 0.1; leg.outpad.h.in <- 0.1; leg.midpad.w.in <- 0.1; leg.midpad.h.in <- 0.1; leg.sol.w.in <- max(strwidth(solTypes,''in'',leg.cex)); leg.sol.h.in <- max(strheight(solTypes,''in'',leg.cex))*1.5; ## multiplication factor for greater line height leg.join.w.in <- max(strheight(joinTypes,''in'',leg.cex))*1.5; ## ditto leg.join.h.in <- max(strwidth(joinTypes,''in'',leg.cex)); leg.main.w.in <- leg.join.w.in*length(joinTypes); leg.main.h.in <- leg.sol.h.in*length(solTypes); leg.x2.user <- grconvertX(leg.x.in+leg.outpad.w.in*2+leg.main.w.in+leg.midpad.w.in+leg.sol.w.in,''in''); leg.y2.user <- grconvertY(leg.y.in+leg.outpad.h.in*2+leg.main.h.in+leg.midpad.h.in+leg.join.h.in,''in''); leg.cols.x.user <- grconvertX(leg.x.in+leg.outpad.w.in+leg.join.w.in*(0.5+seq(0L,length(joinTypes)-1L)),''in''); leg.lines.y.user <- grconvertY(leg.y.in+leg.outpad.h.in+leg.main.h.in-leg.sol.h.in*(0.5+seq(0L,length(solTypes)-1L)),''in''); leg.sol.x.user <- grconvertX(leg.x.in+leg.outpad.w.in+leg.main.w.in+leg.midpad.w.in,''in''); leg.join.y.user <- grconvertY(leg.y.in+leg.outpad.h.in+leg.main.h.in+leg.midpad.h.in,''in''); rect(leg.x.user,leg.y.user,leg.x2.user,leg.y2.user,col=''white''); text(leg.sol.x.user,leg.lines.y.user,solTypes[ord],cex=leg.cex,pos=4L,offset=0); text(leg.cols.x.user,leg.join.y.user,joinTypes,cex=leg.cex,pos=4L,offset=0,srt=90); ## srt rotation applies *after* pos/offset positioning for (i in seq_along(joinTypes)) { joinType <- joinTypes[i]; points(rep(leg.cols.x.user[i],length(solTypes)),ifelse(colSums(!is.na(x1[x1$joinType==joinType,solTypes[ord]]))==0L,NA,leg.lines.y.user),pch=pchs[[joinType]],col=cols[solTypes[ord]]); }; ## end for title(titleFunc(overlap)); readline(sprintf(''overlap %.02f'',overlap)); }; ## end for }; ## end plotRes() titleFunc <- function(overlap) sprintf(''R merge solutions: single-column integer key, 0..1:0..1 cardinality, %d%% overlap'',as.integer(overlap*100)); plotRes(res,titleFunc,T);

Aquí hay un segundo punto de referencia a gran escala que es más pesado, con respecto al número y los tipos de columnas clave, así como la cardinalidad. Para este punto de referencia, uso tres columnas clave: un carácter, un entero y uno lógico, sin restricciones de cardinalidad (es decir, 0..*:0..*). (En general, no es aconsejable definir columnas clave con valores dobles o complejos debido a complicaciones de comparación de punto flotante, y básicamente nadie usa el tipo en bruto, mucho menos para las columnas clave, por lo que no he incluido esos tipos en la clave También, por el bien de la información, inicialmente intenté usar cuatro columnas clave al incluir una columna de clave POSIXct, pero el tipo POSIXct no funcionó bien con la sqldf.indexedsolución por alguna razón, posiblemente debido a anomalías de comparación de punto flotante quitado.)

makeArgSpecs.assortedKey.optionalManyToMany <- function(size,overlap,uniquePct=75) { ## number of unique keys in df1 u1Size <- as.integer(size*uniquePct/100); ## (roughly) divide u1Size into bases, so we can use expand.grid() to produce the required number of unique key values with repetitions within individual key columns ## use ceiling() to ensure we cover u1Size; will truncate afterward u1SizePerKeyColumn <- as.integer(ceiling(u1Size^(1/3))); ## generate the unique key values for df1 keys1 <- expand.grid(stringsAsFactors=F, idCharacter=replicate(u1SizePerKeyColumn,paste(collapse='''',sample(letters,sample(4:12,1L),T))), idInteger=sample(u1SizePerKeyColumn), idLogical=sample(c(F,T),u1SizePerKeyColumn,T) ##idPOSIXct=as.POSIXct(''2016-01-01 00:00:00'',''UTC'')+sample(u1SizePerKeyColumn) )[seq_len(u1Size),]; ## rbind some repetitions of the unique keys; this will prepare one side of the many-to-many relationship ## also scramble the order afterward keys1 <- rbind(keys1,keys1[sample(nrow(keys1),size-u1Size,T),])[sample(size),]; ## common and unilateral key counts com <- as.integer(size*overlap); uni <- size-com; ## generate some unilateral keys for df2 by synthesizing outside of the idInteger range of df1 keys2 <- data.frame(stringsAsFactors=F, idCharacter=replicate(uni,paste(collapse='''',sample(letters,sample(4:12,1L),T))), idInteger=u1SizePerKeyColumn+sample(uni), idLogical=sample(c(F,T),uni,T) ##idPOSIXct=as.POSIXct(''2016-01-01 00:00:00'',''UTC'')+u1SizePerKeyColumn+sample(uni) ); ## rbind random keys from df1; this will complete the many-to-many relationship ## also scramble the order afterward keys2 <- rbind(keys2,keys1[sample(nrow(keys1),com,T),])[sample(size),]; ##keyNames <- c(''idCharacter'',''idInteger'',''idLogical'',''idPOSIXct''); keyNames <- c(''idCharacter'',''idInteger'',''idLogical''); ## note: was going to use raw and complex type for two of the non-key columns, but data.table doesn''t seem to fully support them argSpecs <- list( default=list(copySpec=1:2,args=list( df1 <- cbind(stringsAsFactors=F,keys1,y1=sample(c(F,T),size,T),y2=sample(size),y3=rnorm(size),y4=replicate(size,paste(collapse='''',sample(letters,sample(4:12,1L),T)))), df2 <- cbind(stringsAsFactors=F,keys2,y5=sample(c(F,T),size,T),y6=sample(size),y7=rnorm(size),y8=replicate(size,paste(collapse='''',sample(letters,sample(4:12,1L),T)))), keyNames )), data.table.unkeyed=list(copySpec=1:2,args=list( as.data.table(df1), as.data.table(df2), keyNames )), data.table.keyed=list(copySpec=1:2,args=list( setkeyv(as.data.table(df1),keyNames), setkeyv(as.data.table(df2),keyNames) )) ); ## prepare sqldf initSqldf(); sqldf(paste0(''create index df1_key on df1('',paste(collapse='','',keyNames),'');'')); ## upload and create an sqlite index on df1 sqldf(paste0(''create index df2_key on df2('',paste(collapse='','',keyNames),'');'')); ## upload and create an sqlite index on df2 argSpecs; }; ## end makeArgSpecs.assortedKey.optionalManyToMany() sizes <- c(1e1L,1e3L,1e5L); ## 1e5L instead of 1e6L to respect more heavy-duty inputs overlaps <- c(0.99,0.5,0.01); solTypes <- setdiff(getSolTypes(),''in.place''); system.time({ res <- testGrid(makeArgSpecs.assortedKey.optionalManyToMany,sizes,overlaps,solTypes); }); ## user system elapsed ## 38895.50 784.19 39745.53

Los gráficos resultantes, utilizando el mismo código de trazado dado anteriormente:

titleFunc <- function(overlap) sprintf(''R merge solutions: character/integer/logical key, 0..*:0..* cardinality, %d%% overlap'',as.integer(overlap*100)); plotRes(res,titleFunc,F);


Recomendaría revisar el paquete sqldf de Gabor Grothendieck , que le permite expresar estas operaciones en SQL.

library(sqldf) ## inner join df3 <- sqldf("SELECT CustomerId, Product, State FROM df1 JOIN df2 USING(CustomerID)") ## left join (substitute ''right'' for right join) df4 <- sqldf("SELECT CustomerId, Product, State FROM df1 LEFT JOIN df2 USING(CustomerID)")

Encuentro que la sintaxis de SQL es más simple y más natural que su equivalente en R (pero esto puede reflejar mi sesgo RDBMS).

Consulte el gabinete GitHub de Gabor para obtener más información sobre las combinaciones.


También puede hacer uniones usando el impresionante paquete dplyr Hadley Wickham.

library(dplyr) #make sure that CustomerId cols are both type numeric #they ARE not using the provided code in question and dplyr will complain df1$CustomerId <- as.numeric(df1$CustomerId) df2$CustomerId <- as.numeric(df2$CustomerId)

Uniones mutantes: agregue columnas a df1 usando coincidencias en df2

#inner inner_join(df1, df2) #left outer left_join(df1, df2) #right outer right_join(df1, df2) #alternate right outer left_join(df2, df1) #full join full_join(df1, df2)

Filtrado de combinaciones: filtre las filas en df1, no modifique columnas

semi_join(df1, df2) #keep only observations in df1 that match in df2. anti_join(df1, df2) #drops all observations in df1 that match in df2.


Usando la función de merge y sus parámetros opcionales:

La combinación interna: merge(df1, df2) funcionará para estos ejemplos porque R une los marcos por nombres de variables comunes, pero lo más probable es que desee especificar la merge(df1, df2, by = "CustomerId") para asegurarse de que coincidían solo en los campos que deseabas. También puede usar los parámetros by.x y by.y si las variables coincidentes tienen nombres diferentes en los diferentes marcos de datos.

Unión externa: merge(x = df1, y = df2, by = "CustomerId", all = TRUE)

merge(x = df1, y = df2, by = "CustomerId", all.x = TRUE) izquierdo: merge(x = df1, y = df2, by = "CustomerId", all.x = TRUE)

merge(x = df1, y = df2, by = "CustomerId", all.y = TRUE) derecho: merge(x = df1, y = df2, by = "CustomerId", all.y = TRUE)

Unión cruzada: merge(x = df1, y = df2, by = NULL)

Al igual que con la unión interna, probablemente querrá pasar explícitamente "CustomerId" a R como la variable coincidente. Creo que casi siempre es mejor indicar explícitamente los identificadores en los que desea fusionarse; es más seguro si los cuadros de datos de entrada cambian inesperadamente y es más fácil de leer más adelante.

Puede fusionar en varias columnas dando by un vector, por ejemplo, by = c("CustomerId", "OrderId") .

Si los nombres de las columnas a fusionar no son iguales, puede especificar, por ejemplo, by.x = "CustomerId_in_df1", by.y = "CustomerId_in_df2" donde CustomerId_in_df1 es el nombre de la columna en el primer marco de datos y CustomerId_in_df2 es el Nombre de la columna en el segundo marco de datos. (Estos también pueden ser vectores si necesita combinar en varias columnas).


dplyr desde la versión 0.4 implementó todas esas uniones, incluida outer_join, pero valió la pena señalar que durante las primeras versiones solía no ofrecer outer_join, y como resultado había una gran cantidad de código de usuario hacky muy malo que andaba dando vueltas durante bastante tiempo ( todavía puedes encontrar esto en las respuestas de SO y Kaggle de ese período).

Aspectos destacados del lanzamiento relacionado con la incorporación :

v0.5 (6/2016)

  • Manejo del tipo POSIXct, zonas horarias, duplicados, diferentes niveles de factores. Mejores errores y advertencias.
  • Nuevo argumento de sufijo para controlar qué sufijo reciben los nombres de variable duplicados (# 1296)

v0.4.0 (1/2015)

  • Implementar unión derecha y unión externa (# 96)
  • Uniones mutantes, que agregan nuevas variables a una tabla a partir de filas coincidentes en otra. Filtrado de combinaciones, que filtran las observaciones de una tabla en función de si coinciden o no con una observación en la otra tabla.

v0.3 (10/2014)

  • Ahora puede left_join por diferentes variables en cada tabla: df1%>% left_join (df2, c ("var1" = "var2"))

v0.2 (5/2014)

  • * _join () ya no vuelve a ordenar los nombres de columna (# 324)

v0.1.3 (4/2014)

Soluciones para los comentarios de hadley en ese tema:

  • right_join (x, y) es lo mismo que left_join (y, x) en términos de las filas, solo las columnas serán órdenes diferentes. Fácilmente trabajado con select (new_column_order)
  • external_join es básicamente unión (left_join (x, y), right_join (x, y)), es decir, preservar todas las filas en ambas tramas de datos.

Actualizar unirse. Otra unión importante de estilo SQL es una " unión de actualización " donde las columnas en una tabla se actualizan (o crean) usando otra tabla

Modificando las tablas de ejemplo del OP ...

sales = data.frame( CustomerId = c(1, 1, 1, 3, 4, 6), Year = 2000:2005, Product = c(rep("Toaster", 3), rep("Radio", 3)) ) cust = data.frame( CustomerId = c(1, 1, 4, 6), Year = c(2001L, 2002L, 2002L, 2002L), State = state.name[1:4] ) sales # CustomerId Year Product # 1 2000 Toaster # 1 2001 Toaster # 1 2002 Toaster # 3 2003 Radio # 4 2004 Radio # 6 2005 Radio cust # CustomerId Year State # 1 2001 Alabama # 1 2002 Alaska # 4 2002 Arizona # 6 2002 Arkansas

Supongamos que queremos agregar el estado del cliente custa la tabla de compras sales, ignorando la columna del año. Con la base R, podemos identificar filas coincidentes y luego copiar valores sobre:

sales$State <- cust$State[ match(sales$CustomerId, cust$CustomerId) ] # CustomerId Year Product State # 1 2000 Toaster Alabama # 1 2001 Toaster Alabama # 1 2002 Toaster Alabama # 3 2003 Radio <NA> # 4 2004 Radio Arizona # 6 2005 Radio Arkansas # cleanup for the next example sales$State <- NULL

Como se puede ver aquí, matchselecciona la primera fila coincidente de la tabla de clientes.

Actualizar unirse con varias columnas. El enfoque anterior funciona bien cuando nos unimos en una sola columna y estamos satisfechos con la primera coincidencia. Supongamos que queremos que el año de medición en la tabla de clientes coincida con el año de venta.

Como respuesta @ de bgoldst menciona, matchcon interactionpodría ser una opción para este caso. Más directamente, uno podría usar data.table:

library(data.table) setDT(sales); setDT(cust) sales[, State := cust[sales, on=.(CustomerId, Year), x.State]] # CustomerId Year Product State # 1: 1 2000 Toaster <NA> # 2: 1 2001 Toaster Alabama # 3: 1 2002 Toaster Alaska # 4: 3 2003 Radio <NA> # 5: 4 2004 Radio <NA> # 6: 6 2005 Radio <NA> # cleanup for next example sales[, State := NULL]

Actualización de Rolling unirse. Alternativamente, podemos querer tomar el último estado en que se encontró el cliente en:

sales[, State := cust[sales, on=.(CustomerId, Year), roll=TRUE, x.State]] # CustomerId Year Product State # 1: 1 2000 Toaster <NA> # 2: 1 2001 Toaster Alabama # 3: 1 2002 Toaster Alaska # 4: 3 2003 Radio <NA> # 5: 4 2004 Radio Arizona # 6: 6 2005 Radio Arkansas

Los tres ejemplos anteriores se centran en crear / agregar una nueva columna. Consulte las Preguntas frecuentes relacionadas con R para obtener un ejemplo de actualización / modificación de una columna existente.


Para una unión interna en todas las columnas, también puede utilizar fintersectdesde el archivo data.table -package o intersectdesde el dplyr -package como una alternativa mergesin especificar las by-columns. Esto dará las filas que son iguales entre dos marcos de datos:

merge(df1, df2) # V1 V2 # 1 B 2 # 2 C 3 dplyr::intersect(df1, df2) # V1 V2 # 1 B 2 # 2 C 3 data.table::fintersect(setDT(df1), setDT(df2)) # V1 V2 # 1: B 2 # 2: C 3

Ejemplo de datos:

df1 <- data.frame(V1 = LETTERS[1:4], V2 = 1:4) df2 <- data.frame(V1 = LETTERS[2:3], V2 = 2:3)