web-dev-qa-db-fra.com

Fusionner simultanément plusieurs data.frames dans une liste

J'ai une liste de nombreux data.frames que je veux fusionner. Le problème ici est que chaque data.frame diffère en termes de nombre de lignes et de colonnes, mais ils partagent tous les variables clés (que j'ai appelées "var1" et "var2" dans le code ci-dessous). Si les data.frames étaient identiques en termes de colonnes, je pourrais simplement rbind, pour lequel rbind.fill de plyr ferait l'affaire, mais ce n'est pas le cas avec ces données. 

Comme la commande merge ne fonctionne que sur 2 data.frames, je me suis tourné vers Internet pour trouver des idées. J'ai eu celui-ci de here , qui fonctionnait parfaitement dans R 2.7.2, ce que j'avais à l'époque:

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

Et j'appellerais la fonction comme suit:

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

Mais dans toute version de R ultérieure à la version 2.7.2, y compris 2.11 et 2.12, ce code échoue avec l’erreur suivante:

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

(Incidemment, je vois d'autres références à cette erreur ailleurs sans résolution).

Est-ce qu'il y a un moyen de résoudre ceci? 

199
bshor

Une autre question posée spécifiquement comment effectuer plusieurs jointures à gauche en utilisant dplyr dans R . La question a été marquée comme une copie de celle-ci, alors je réponds ici en utilisant les 3 exemples de trames de données ci-dessous:

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)

Mise à jour juin 2018: J'ai divisé la réponse en trois sections représentant trois manières différentes d'effectuer la fusion. Vous voudrez probablement utiliser la méthode purrr si vous utilisez déjà les packages tidyverse. À des fins de comparaison ci-dessous, vous trouverez une version de base R utilisant le même échantillon de données.

Rejoignez-les avec reduce du paquet purrr

Le paquetage purrr fournit une fonction reduce qui a une syntaxe concise:

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

Vous pouvez également effectuer d'autres jointures, telles que full_join ou inner_join:

list(x, y, z) %>% reduce(full_join, by = "i")
# A tibble: 4 x 4
# i       j     k     l
# <chr> <int> <int> <int>
# 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
# i       j     k     l
# <chr> <int> <int> <int>
# 1 c     3     5     7

dplyr::left_join() avec base R Reduce()

list(x,y,z) %>%
    Reduce(function(dtf1,dtf2) left_join(dtf1,dtf2,by="i"), .)

#   i j  k  l
# 1 a 1 NA  9
# 2 b 2  4 NA
# 3 c 3  5  7

Base R merge() avec base R Reduce()

Et à des fins de comparaison, voici une version R de base de la jointure gauche

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

Réduire rend cela assez facile:

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

Voici un exemple complet utilisant des données fictives:

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)
#    x  a  b         y
#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

Et voici un exemple utilisant ces données pour répliquer 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         NA   <NA>
#2     ALVES   100 RI      019       S         NA   <NA>   NA   NA   NA         NA   <NA>
#3    BADEAU   100 RI      032       S         NA   <NA>   NA   NA   NA         NA   <NA>

Note: Il semble que ce soit un bug dans merge. Le problème est qu’il n’ya pas de vérification que l’ajout des suffixes (pour gérer les noms superposés qui se chevauchent) les rend uniques. À un moment donné, il utilise [.data.frame qui faitmake.unique les noms, entraînant l'échec de rbind.

# 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 solution la plus simple consiste à ne pas laisser le champ renommé pour les champs en double (il y en a beaucoup ici) jusqu'à merge. Par exemple:

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 fonctionnera alors correctement.

206
Charles

Vous pouvez le faire en utilisant merge_all dans le package reshape. Vous pouvez passer des paramètres à merge en utilisant l'argument ...

reshape::merge_all(list_of_dataframes, ...)

Voici une excellente ressource sur différentes méthodes de fusion de trames de données .

47
Ramnath

Vous pouvez utiliser la récursivité pour le faire. Je n'ai pas vérifié ce qui suit, mais cela devrait vous donner la bonne idée:

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

Je vais réutiliser l'exemple de données 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)

Voici une solution courte et agréable utilisant purrr et tidyr

library(tidyverse)

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

J'ai eu une liste de dataframes sans colonne d'id commun.
Il me manquait des données sur de nombreux dfs. Il y avait des valeurs nulles. Les images ont été produites en utilisant la fonction table. Réduire, Fusionner, rapprocher, combler, et leurs semblables ne pouvaient pas m'aider à atteindre mon objectif. Mon objectif était de produire une trame de données fusionnée compréhensible, non pertinente pour la colonne des données manquantes et de la colonne id commune. 

Par conséquent, j'ai créé la fonction suivante. Peut-être que cette fonction peut aider quelqu'un. 

##########################################################
####             Dependencies                        #####
##########################################################

# Depends on Base R only

##########################################################
####             Example DF                          #####
##########################################################

# Example df
ex_df           <- cbind(c( seq(1, 10, 1), rep("NA", 0), seq(1,10, 1) ), 
                         c( seq(1, 7, 1),  rep("NA", 3), seq(1, 12, 1) ), 
                         c( seq(1, 3, 1),  rep("NA", 7), seq(1, 5, 1), rep("NA", 5) ))

# Making colnames and rownames
colnames(ex_df) <- 1:dim(ex_df)[2]
rownames(ex_df) <- 1:dim(ex_df)[1]

# Making an unequal list of dfs, 
# without a common id column
list_of_df      <- apply(ex_df=="NA", 2, ( table) )

il suit la fonction

##########################################################
####             The function                        #####
##########################################################


# The function to rbind it
rbind_null_df_lists <- function ( list_of_dfs ) {
  length_df     <- do.call(rbind, (lapply( list_of_dfs, function(x) length(x))))
  max_no        <- max(length_df[,1])
  max_df        <- length_df[max(length_df),]
  name_df       <- names(length_df[length_df== max_no,][1])
  names_list    <- names(list_of_dfs[ name_df][[1]])

  df_dfs <- list()
  for (i in 1:max_no ) {

    df_dfs[[i]]            <- do.call(rbind, lapply(1:length(list_of_dfs), function(x) list_of_dfs[[x]][i]))

  }

  df_cbind               <- do.call( cbind, df_dfs )
  rownames( df_cbind )   <- rownames (length_df)
  colnames( df_cbind )   <- names_list

  df_cbind

}

Exécuter l'exemple

##########################################################
####             Running the example                 #####
##########################################################

rbind_null_df_lists ( list_of_df )
0