Unisci contemporaneamente più data.frames in un elenco


259

Ho un elenco di molti data.frames che voglio unire. Il problema qui è che ogni data.frame differisce in termini di numero di righe e colonne, ma condividono tutte le variabili chiave (che ho chiamato "var1"e "var2"nel codice seguente). Se i data.frames fossero identici in termini di colonne, potrei semplicemente rbind, per cui plyr's rbind.fill farebbe il lavoro, ma non è il caso di questi dati.

Poiché il mergecomando funziona solo su 2 data.frames, mi sono rivolto a Internet per idee. Ho preso questo da qui , che ha funzionato perfettamente in R 2.7.2, che è quello che avevo in quel momento:

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

E chiamerei la funzione in questo modo:

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

Ma in qualsiasi versione R successiva alla 2.7.2, inclusi 2.11 e 2.12, questo codice fallisce con il seguente errore:

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

(Per inciso, vedo altri riferimenti a questo errore altrove senza risoluzione).

C'è un modo per risolverlo?

Risposte:


183

Un'altra domanda ha chiesto specificamente come eseguire molteplici sinistra si unisce usando dplyr in R . La domanda è stata contrassegnata come duplicata di questa, quindi rispondo qui, utilizzando i 3 frame di dati di esempio seguenti:

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

Aggiornamento giugno 2018 : ho diviso la risposta in tre sezioni che rappresentano tre diversi modi per eseguire l'unione. Probabilmente vuoi usare il purrrmodo se stai già usando i pacchetti tidyverse . Ai fini del confronto di seguito, troverai una versione R di base che utilizza lo stesso set di dati di esempio.


1) Unisciti a loro reducedal purrrpacchetto:

Il purrrpacchetto fornisce una reducefunzione che ha una sintassi concisa:

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

Puoi anche eseguire altri join, ad esempio a full_joino 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

2) dplyr::left_join()con 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

3) Base R merge()con base RReduce() :

E a fini di confronto, ecco una versione R di base del join sinistro

 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

1
La variante full_join funziona perfettamente e sembra molto meno spaventosa della risposta accettata. Tuttavia, non c'è molta differenza di velocità.
bshor,

1
@Axeman ha ragione, ma potresti essere in grado di evitare (visibilmente) di restituire un elenco di frame di dati utilizzando map_dfr()omap_dfc()
DaveRGP

Ho pensato di poter unire un certo numero di DF in base a un modello usando ´ls (pattern = "DF_name_contains_this") ´, ma no. Ho usato "noquote (paste (())", ma sto ancora producendo un vettore di caratteri anziché un elenco di DF. Ho finito per scrivere i nomi, che è odioso.
La penna di George William Russel il

Un'altra domanda fornisce un'implementazione di Python : l'elenco dei frame di dati Panda dfs = [df1, df2, df3]quindi reduce(pandas.merge, dfs).
Paul Rougieux,

222

Ridurre rende tutto abbastanza semplice:

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

Ecco un esempio completo usando alcuni dati simulati:

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

Ed ecco un esempio usando questi dati per replicare 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>

Nota: sembra che questo sia probabilmente un bug merge. Il problema è che non si verifica che l'aggiunta dei suffissi (per gestire nomi non corrispondenti sovrapposti) li renda davvero unici. Ad un certo punto usa [.data.framequale fa make.unique i nomi, causando il rbindfallimento.

# 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.

Il modo più semplice per risolvere è quello di non lasciare il campo rinominando i campi duplicati (di cui ce ne sono molti qui) fino a merge. Per esempio:

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))

Il merge/ Reducefunzionerà quindi bene.


Grazie! Ho visto questa soluzione anche sul link di Ramnath. Sembra abbastanza facile. Ma ottengo il seguente errore: "Errore in match.names (clabs, names (xi)): i nomi non corrispondono ai nomi precedenti". Le variabili su cui sto confrontando sono tutte presenti in tutti i frame di dati nell'elenco, quindi non capisco cosa mi dice questo errore.
bshor,

1
Ho provato questa soluzione su R2.7.2 e ottengo lo stesso errore match.names. Quindi c'è qualche problema più fondamentale con questa soluzione e i miei dati. Ho usato il codice: Riduci (funzione (x, y) unisci (x, y, all = T, by.x = match.by, by.y = match.by), my.list, accumulate = F)
bshor

1
Strano, ho aggiunto il codice con cui l'ho provato e che funziona bene. Immagino che si stia verificando qualche ridenominazione del campo in base agli argomenti di merge che stai utilizzando? Il risultato unito deve avere ancora le chiavi pertinenti per poter essere unito al successivo frame di dati.
Charles,

Ho il sospetto che accada qualcosa con frame di dati vuoti. Ho provato alcuni esempi come questo: empty <- data.frame(x=numeric(0),a=numeric(0); L3 <- c(empty,empty,list.of.data.frames,empty,empty,empty)e sono successe alcune cose strane che non ho ancora capito.
Ben Bolker,

@Charles Sei su qualcosa. Il tuo codice funziona benissimo per me. E quando lo adeguo al mio, funziona anche bene, tranne per il fatto che si fonde ignorando le variabili chiave che desidero. Quando provo ad aggiungere variabili chiave anziché lasciarle fuori, ricevo un nuovo errore "Errore in is.null (x): 'x' mancante". La riga di codice è "test.reduce <- Riduci (funzione (...) unione (di = match.by, all = T), my.list)" dove match.by sono il vettore dei nomi delle variabili chiave che voglio unire di.
bshor,

52

Puoi farlo usando merge_allnel reshapepacchetto. È possibile passare i parametri mergeall'utilizzo ...dell'argomento

reshape::merge_all(list_of_dataframes, ...)

Ecco un'eccellente risorsa su diversi metodi per unire i frame di dati .


sembra che ho appena replicato merge_recurse =) bello sapere che questa funzione esiste già.
SFun28,

16
sì. ogni volta che ho un'idea, controllo sempre se @hadley l'ha già fatto, e la maggior parte delle volte lo ha fatto :-)
Ramnath,

1
Sono un po 'confuso; dovrei fare merge_all o merge_recurse? In ogni caso, quando provo ad aggiungere i miei argomenti aggiuntivi a uno dei due, ottengo l'errore "argomento formale" tutto "abbinato a più argomenti effettivi".
bshor,

2
Penso di averlo abbandonato da reshape2. Ridurre + unire è altrettanto semplice.
Hadley,

2
@Ramnath, il link è morto, c'è uno specchio?
Eduardo,

4

È possibile utilizzare la ricorsione per fare questo. Non ho verificato quanto segue, ma dovrebbe darti l'idea giusta:

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

2

Riutilizzerò l'esempio di dati da @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)

Ecco una soluzione breve e dolce con purrretidyr

library(tidyverse)

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

1

La funzione eatdel mio pacchetto safejoin ha tale caratteristica, se gli dai un elenco di data.frames come secondo input, li unirà ricorsivamente al primo input.

Prendere in prestito ed estendere i dati della risposta accettata:

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)
z2 <- data_frame(i = c("a","b","c"), l = rep(100L,3),l2 = rep(100L,3)) # for later

# devtools::install_github("moodymudskipper/safejoin")
library(safejoin)
eat(x, list(y,z), .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

Non dobbiamo prendere tutte le colonne, possiamo usare gli helper selezionati da tidyselect e scegliere (poiché partiamo da .xtutte le .xcolonne vengono mantenute):

eat(x, list(y,z), starts_with("l") ,.by = "i")
# # A tibble: 3 x 3
#   i         j     l
#   <chr> <int> <int>
# 1 a         1     9
# 2 b         2    NA
# 3 c         3     7

o rimuovi quelli specifici:

eat(x, list(y,z), -starts_with("l") ,.by = "i")
# # A tibble: 3 x 3
#   i         j     k
#   <chr> <int> <int>
# 1 a         1    NA
# 2 b         2     4
# 3 c         3     5

Se l'elenco è denominato, i nomi verranno utilizzati come prefissi:

eat(x, dplyr::lst(y,z), .by = "i")
# # A tibble: 3 x 4
#   i         j   y_k   z_l
#   <chr> <int> <int> <int>
# 1 a         1    NA     9
# 2 b         2     4    NA
# 3 c         3     5     7

Se ci sono conflitti di colonna il .conflict argomento ti consente di risolverlo, ad esempio prendendo il primo / secondo, aggiungendoli, fondendoli o annidandoli.

tenere prima:

eat(x, list(y, z, z2), .by = "i", .conflict = ~.x)
# # 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

mantieni ultimo:

eat(x, list(y, z, z2), .by = "i", .conflict = ~.y)
# # A tibble: 3 x 4
#   i         j     k     l
#   <chr> <int> <int> <dbl>
# 1 a         1    NA   100
# 2 b         2     4   100
# 3 c         3     5   100

Inserisci:

eat(x, list(y, z, z2), .by = "i", .conflict = `+`)
# # A tibble: 3 x 4
#   i         j     k     l
#   <chr> <int> <int> <dbl>
# 1 a         1    NA   109
# 2 b         2     4    NA
# 3 c         3     5   107

coalesce:

eat(x, list(y, z, z2), .by = "i", .conflict = dplyr::coalesce)
# # A tibble: 3 x 4
#   i         j     k     l
#   <chr> <int> <int> <dbl>
# 1 a         1    NA     9
# 2 b         2     4   100
# 3 c         3     5     7

nido:

eat(x, list(y, z, z2), .by = "i", .conflict = ~tibble(first=.x, second=.y))
# # A tibble: 3 x 4
#   i         j     k l$first $second
#   <chr> <int> <int>   <int>   <int>
# 1 a         1    NA       9     100
# 2 b         2     4      NA     100
# 3 c         3     5       7     100

NAi valori possono essere sostituiti usando l' .fillargomento

eat(x, list(y, z), .by = "i", .fill = 0)
# # A tibble: 3 x 4
#   i         j     k     l
#   <chr> <int> <dbl> <dbl>
# 1 a         1     0     9
# 2 b         2     4     0
# 3 c         3     5     7

Di default è un potenziato left_joinma tutti i join dplyr sono supportati attraverso l' .modeargomento, i join fuzzy sono supportati anche tramite l' match_fun argomento (è racchiuso nel pacchetto fuzzyjoin) o forniscono una formula come ~ X("var1") > Y("var2") & X("var3") < Y("var4")l' byargomento.


0

Avevo un elenco di frame di dati senza colonna ID comune.
Avevo dati mancanti su molti dfs. C'erano valori Null. I frame di dati sono stati prodotti utilizzando la funzione tabella. The Reduce, Merging, rbind, rbind.fill e i loro simili non potevano aiutarmi a raggiungere il mio obiettivo. Il mio obiettivo era quello di produrre un comprensibile dataframe unito, irrilevante per i dati mancanti e la colonna id comune.

Pertanto, ho effettuato la seguente funzione. Forse questa funzione può aiutare qualcuno.

##########################################################
####             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) )

sta seguendo la funzione

##########################################################
####             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

}

Eseguendo l'esempio

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

rbind_null_df_lists ( list_of_df )

0

Quando si dispone di un elenco di dfs e una colonna contiene "ID", ma in alcuni elenchi mancano alcuni ID, è possibile utilizzare questa versione di Riduci / Unisci per unire più DFS di ID riga o etichette mancanti:

Reduce(function(x, y) merge(x=x, y=y, by="V1", all.x=T, all.y=T), list_of_dfs)

0

Ecco un wrapper generico che può essere utilizzato per convertire una funzione binaria in una funzione multiparametrica. Il vantaggio di questa soluzione è che è molto generico e può essere applicato a qualsiasi funzione binaria. Devi solo farlo una volta e poi puoi applicarlo ovunque.

Per provare l'idea, utilizzo l'implementazione semplice della ricorsione. Naturalmente può essere implementato con un modo più elegante che beneficia del buon supporto di R per il paradigma funzionale.

fold_left <- function(f) {
return(function(...) {
    args <- list(...)
    return(function(...){
    iter <- function(result,rest) {
        if (length(rest) == 0) {
            return(result)
        } else {
            return(iter(f(result, rest[[1]], ...), rest[-1]))
        }
    }
    return(iter(args[[1]], args[-1]))
    })
})}

Quindi puoi semplicemente avvolgere qualsiasi funzione binaria con esso e chiamare con i parametri posizionali (di solito data.frames) tra le prime parentesi e i parametri con nome tra le seconde parentesi (come by =o suffix =). Se non ci sono parametri nominati, lasciare vuote le seconde parentesi.

merge_all <- fold_left(merge)
merge_all(df1, df2, df3, df4, df5)(by.x = c("var1", "var2"), by.y = c("var1", "var2"))

left_join_all <- fold_left(left_join)
left_join_all(df1, df2, df3, df4, df5)(c("var1", "var2"))
left_join_all(df1, df2, df3, df4, df5)()
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.