Fusionner simultanément plusieurs données.cadres dans une liste

j'ai une liste de nombreuses données.images que je veux fusionner. Le problème ici est que chaque donnée.frame diffère en termes de nombre de lignes et de colonnes, mais ils partagent tous les variables clés (que j'ai appelé "var1" et "var2" dans le code ci-dessous). Si les données.les cadres étaient identiques en termes de colonnes, je pouvais simplement rbind , pour lequel plyr rbind.remplir pour faire le travail, mais ce n'est pas le cas avec ces données.

Parce que la commande merge ne fonctionne que sur 2 données.frames, je me suis tourné vers Internet pour des idées. J'ai eu celui-ci de ici , qui a fonctionné parfaitement dans R 2.7.2, qui est 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 ainsi:

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

mais dans toute version R après 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).

y a-t-il un moyen de résoudre ça?

182
demandé sur Community 2011-11-11 12:16:54

5 réponses

une autre question posée spécifiquement comment effectuer plusieurs jonctions à gauche en utilisant dplyr dans R . La question a été marquée comme une copie de celui-ci donc je réponds ici, en utilisant les 3 cadres de données d'échantillon 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)

Update June 2018 : j'ai divisé la réponse en trois sections représentant trois façons différentes d'effectuer la fusion. Vous voulez probablement utiliser la méthode purrr si vous utilisez déjà les colis tidyverse . Pour les fins de comparaison ci-dessous, vous trouverez une version de base R utilisant le même ensemble de données d'échantillon.

les joindre avec reduce du purrr paquet

le paquet 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, comme un 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 de base R 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
78
répondu Paul Rougieux 2018-08-30 06:33:15

Réduire le rend assez facile:

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

voici un exemple complet en utilisant quelques données simulées:

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 en 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 qu'il s'agisse d'un bug dans merge . Le problème est qu'il n'y a pas de vérification que l'ajout des suffixes (pour gérer le chevauchement les noms qui ne concordent pas) les rend en fait uniques. À un certain point, il utilise [.data.frame qui fait make.unique les noms, provoquant le rbind d'échouer.

# 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 façon la plus facile de corriger est de ne pas laisser le champ Renommer pour les champs en double (dont il y 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))

Le merge / Reduce beau travail.

203
répondu Charles 2015-07-22 13:41:15

Vous pouvez le faire en utilisant merge_all dans le reshape . Vous pouvez passer les paramètres à merge en utilisant le ... argument

reshape::merge_all(list_of_dataframes, ...)

Ici est une excellente ressource sur les différentes méthodes de fusion de données images .

47
répondu Ramnath 2015-05-08 09:15:58

vous pouvez utiliser la récursion pour faire ceci. 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
répondu SFun28 2011-11-11 15:19:05

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 douce en utilisant purrr et tidyr

library(tidyverse)

 list(x, y, z) %>% 
  map_df(gather, key=key, value=value, -i) %>% 
  spread(key, value)
1
répondu dmi3kno 2017-07-28 10:59:54