Simultáneamente fusionar múltiples data.frames en una lista

Tengo una lista de muchos data.frames que quiero fusionar. El problema aquí es que cada data.frame difiere en términos del número de filas y columnas, pero todas comparten las variables clave (que he llamado "var1" y "var2" en el siguiente código). Si los data.frames fueran idénticos en términos de columnas, podría simplemente rbind , para lo cual rbind.fill de plyr haría el trabajo, pero ese no es el caso con estos datos.

Como el comando merge solo funciona en 2 data.frames, recurrí a Internet para obtener ideas. Obtuve este de aquí , que funcionó perfectamente en R 2.7.2, que es lo que tenía en ese momento:

 merge.rec <- function(.list, ...){ if(length(.list)==1) return(.list[[1]]) Recall(c(list(merge(.list[[1]], .list[[2]], ...)), .list[-(1:2)]), ...) } 

Y yo llamaría a la función así:

 df <- merge.rec(my.list, by.x = c("var1", "var2"), by.y = c("var1", "var2"), all = T, suffixes=c("", "")) 

Pero en cualquier versión R después de 2.7.2, incluidos 2.11 y 2.12, este código falla con el siguiente error:

 Error in match.names(clabs, names(xi)) : names do not match previous names 

(Incidentalmente, veo otras referencias a este error en otro lugar sin resolución).

¿Hay alguna forma de resolver esto?

Otra pregunta preguntó específicamente cómo realizar múltiples combinaciones a la izquierda usando dplyr en R. La pregunta se marcó como un duplicado de esta, así que respondo aquí, utilizando los 3 marcos de datos de muestra a continuación:

 library(dplyr) x <- data_frame(i = c("a","b","c"), j = 1:3) y <- data_frame(i = c("b","c","d"), k = 4:6) z <- data_frame(i = c("c","d","a"), l = 7:9) 

Actualización de junio de 2018 : dividí la respuesta en tres secciones que representan tres formas diferentes de realizar la combinación. Es probable que desee utilizar el método de purrr si ya está utilizando los paquetes de tidyverse . Para propósitos de comparación a continuación, encontrará una versión R base utilizando el mismo conjunto de datos de muestra.

Únete a ellos con reduce del paquete purrr

El paquete purrr proporciona una función de reduce que tiene una syntax concisa:

 library(tidyverse) list(x, y, z) %>% reduce(left_join, by = "i") # A tibble: 3 x 4 # ijkl #     # 1 a 1 NA 9 # 2 b 2 4 NA # 3 c 3 5 7 

También puede realizar otras uniones, como un full_join o inner_join :

 list(x, y, z) %>% reduce(full_join, by = "i") # A tibble: 4 x 4 # ijkl #     # 1 a 1 NA 9 # 2 b 2 4 NA # 3 c 3 5 7 # 4 d NA 6 8 list(x, y, z) %>% reduce(inner_join, by = "i") # A tibble: 1 x 4 # ijkl #     # 1 c 3 5 7 

dplyr::left_join() con la base R Reduce()

 list(x,y,z) %>% Reduce(function(dtf1,dtf2) left_join(dtf1,dtf2,by="i"), .) # ijkl # 1 a 1 NA 9 # 2 b 2 4 NA # 3 c 3 5 7 

Combinación base R merge() con base R Reduce()

Y para fines de comparación, aquí hay una versión R de base de la combinación izquierda

  Reduce(function(dtf1, dtf2) merge(dtf1, dtf2, by = "i", all.x = TRUE), list(x,y,z)) # ijkl # 1 a 1 NA 9 # 2 b 2 4 NA # 3 c 3 5 7 

Reduce hace esto bastante fácil:

 merged.data.frame = Reduce(function(...) merge(..., all=T), list.of.data.frames) 

Aquí hay un ejemplo completo usando algunos datos falsos:

 set.seed(1) list.of.data.frames = list(data.frame(x=1:10, a=1:10), data.frame(x=5:14, b=11:20), data.frame(x=sample(20, 10), y=runif(10))) merged.data.frame = Reduce(function(...) merge(..., all=T), list.of.data.frames) tail(merged.data.frame) # xaby #12 12 NA 18 NA #13 13 NA 19 NA #14 14 NA 20 0.4976992 #15 15 NA NA 0.7176185 #16 16 NA NA 0.3841037 #17 19 NA NA 0.3800352 

Y aquí hay un ejemplo usando estos datos para replicar my.list :

 merged.data.frame = Reduce(function(...) merge(..., by=match.by, all=T), my.list) merged.data.frame[, 1:12] # matchname party st district chamber senate1993 name.x v2.x v3.x v4.x senate1994 name.y #1 ALGIERE 200 RI 026 S NA  NA NA NA NA  #2 ALVES 100 RI 019 S NA  NA NA NA NA  #3 BADEAU 100 RI 032 S NA  NA NA NA NA  

Nota: Parece que esto es posiblemente un error en la merge . El problema es que no hay ninguna verificación de que agregar los sufijos (para manejar nombres superpuestos que no coinciden) los haga únicos. En cierto punto, usa [.data.frame que hace que los nombres make.unique [.data.frame que hace que rbind falle.

 # first merge will end up with 'name.x' & 'name.y' merge(my.list[[1]], my.list[[2]], by=match.by, all=T) # [1] matchname party st district chamber senate1993 name.x # [8] votes.year.x senate1994 name.y votes.year.y #<0 rows> (or 0-length row.names) # as there is no clash, we retain 'name.x' & 'name.y' and get 'name' again merge(merge(my.list[[1]], my.list[[2]], by=match.by, all=T), my.list[[3]], by=match.by, all=T) # [1] matchname party st district chamber senate1993 name.x # [8] votes.year.x senate1994 name.y votes.year.y senate1995 name votes.year #<0 rows> (or 0-length row.names) # the next merge will fail as 'name' will get renamed to a pre-existing field. 

La forma más fácil de solucionarlo es no dejar el campo renombrado para campos duplicados (de los cuales hay muchos aquí) hasta que se merge . P.ej:

 my.list2 = Map(function(x, i) setNames(x, ifelse(names(x) %in% match.by, names(x), sprintf('%s.%d', names(x), i))), my.list, seq_along(my.list)) 

La merge / Reduce funcionará bien.

Puedes hacerlo usando merge_all en el paquete de reshape . Puede pasar parámetros para merge usando el argumento ...

 reshape::merge_all(list_of_dataframes, ...) 

Aquí hay un recurso excelente sobre diferentes métodos para combinar marcos de datos .

Puede usar la recursión para hacer esto. No he verificado lo siguiente, pero debería darle la idea correcta:

 MergeListOfDf = function( data , ... ) { if ( length( data ) == 2 ) { return( merge( data[[ 1 ]] , data[[ 2 ]] , ... ) ) } return( merge( MergeListOfDf( data[ -1 ] , ... ) , data[[ 1 ]] , ... ) ) } 

Reutilizaré el ejemplo de datos de @PaulRougieux

 x <- data_frame(i = c("a","b","c"), j = 1:3) y <- data_frame(i = c("b","c","d"), k = 4:6) z <- data_frame(i = c("c","d","a"), l = 7:9) 

Aquí hay una solución corta y dulce usando purrr y tidyr

 library(tidyverse) list(x, y, z) %>% map_df(gather, key=key, value=value, -i) %>% spread(key, value)