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?
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.
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
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
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.
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 .
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 ]] , ... ) )
}
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)
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 )