Führen Sie mehrere data.frames gleichzeitig in einer Liste zusammen


258

Ich habe eine Liste mit vielen data.frames, die ich zusammenführen möchte. Das Problem hierbei ist, dass sich jeder data.frame in Bezug auf die Anzahl der Zeilen und Spalten unterscheidet, aber alle die Schlüsselvariablen gemeinsam haben (die ich aufgerufen habe "var1"und "var2"im folgenden Code). Wenn die data.frames in Bezug auf Spalten identisch wären , könnte ich nur rbind, für welche plyrs rbind.fill die Arbeit erledigen würde, aber das ist bei diesen Daten nicht der Fall.

Da der mergeBefehl nur für 2 data.frames funktioniert, habe ich mich für Ideen an das Internet gewandt. Ich habe dieses von hier bekommen , das in R 2.7.2 perfekt funktioniert hat, was ich damals hatte:

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

Und ich würde die Funktion so nennen:

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

In jeder R-Version nach 2.7.2, einschließlich 2.11 und 2.12, schlägt dieser Code jedoch mit dem folgenden Fehler fehl:

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

(Übrigens sehe ich andere Verweise auf diesen Fehler an anderer Stelle ohne Lösung).

Gibt es eine Möglichkeit, dies zu lösen?

Antworten:


182

In einer anderen Frage wurde speziell gefragt, wie mehrere Linksverknüpfungen mit dplyr in R ausgeführt werden sollen . Die Frage wurde als Duplikat dieser Frage markiert, daher beantworte ich sie hier anhand der folgenden 3 Beispieldatenrahmen:

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)

Update Juni 2018 : Ich habe die Antwort in drei Abschnitte unterteilt, die drei verschiedene Möglichkeiten zur Durchführung der Zusammenführung darstellen. Sie möchten wahrscheinlich den purrrWeg verwenden, wenn Sie bereits die tidyverse- Pakete verwenden. Zu Vergleichszwecken finden Sie unten eine Basis-R-Version, die denselben Beispieldatensatz verwendet.


1) Verbinden Sie sie mit reduceaus dem purrrPaket:

Das purrrPaket bietet eine reduceFunktion mit einer übersichtlichen Syntax:

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

Sie können auch andere Verknüpfungen ausführen, z. B. a full_joinoder 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()mit Basis 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) Basis R merge()mit Basis R Reduce():

Zu Vergleichszwecken finden Sie hier eine Basis-R-Version des linken Joins

 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
Die full_join-Variante funktioniert perfekt und sieht viel weniger beängstigend aus als die akzeptierte Antwort. Kein großer Geschwindigkeitsunterschied.
bshor

1
@ Axeman ist richtig, aber Sie können möglicherweise vermeiden, (sichtbar) eine Liste von Datenrahmen überhaupt zurückzugeben, indem Sie map_dfr()odermap_dfc()
DaveRGP

Ich dachte, ich könnte eine Reihe von DF basierend auf einem Muster mit ´ls (pattern = "DF_name_contains_this") ´ verbinden, aber nein. Verwendet ´noquote (paste (()) ´, aber ich produziere immer noch einen Zeichenvektor anstelle einer Liste von DF. Am Ende habe ich die Namen eingegeben, was widerlich ist.
George William Russels Stift

Eine andere Frage liefert eine Python-Implementierung : Liste der Pandas-Datenrahmen dfs = [df1, df2, df3]dann reduce(pandas.merge, dfs).
Paul Rougieux

222

Reduzieren macht das ziemlich einfach:

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

Hier ist ein vollständiges Beispiel mit einigen Scheindaten:

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

Und hier ist ein Beispiel, in dem diese Daten zum Replizieren verwendet werden 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>

Hinweis: Es sieht so aus, als wäre dies wohl ein Fehler merge. Das Problem ist, dass nicht überprüft wird, ob das Hinzufügen der Suffixe (um überlappende nicht übereinstimmende Namen zu behandeln) sie tatsächlich eindeutig macht. An einem bestimmten Punkt verwendet es [.data.framedie tut make.unique , die Namen, so dass die rbindzum Scheitern verurteilt.

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

Die einfachste Möglichkeit zur Behebung besteht darin, das Umbenennen des Felds für doppelte Felder (von denen es hier viele gibt) nicht zu belassen merge. Z.B:

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

Das merge/ Reducewird dann gut funktionieren.


Vielen Dank! Ich habe diese Lösung auch auf dem Link von Ramnath gesehen. Sieht einfach aus. Ich erhalte jedoch die folgende Fehlermeldung: "Fehler in match.names (clabs, names (xi)): Namen stimmen nicht mit vorherigen Namen überein". Die Variablen, mit denen ich übereinstimme, sind alle in allen Datenrahmen in der Liste vorhanden, sodass ich nicht verstehe, was mir dieser Fehler sagt.
bshor

1
Ich habe diese Lösung auf R2.7.2 getestet und erhalte den gleichen Fehler match.names. Es gibt also ein grundlegenderes Problem mit dieser Lösung und meinen Daten. Ich habe den Code verwendet: Reduzieren (Funktion (x, y) zusammenführen (x, y, alle = T, by.x = match.by, by.y = match.by), my.list, accumulate = F)
bshor

1
Seltsamerweise habe ich den Code hinzugefügt, mit dem ich ihn getestet habe und der einwandfrei funktioniert. Ich denke, es gibt einige Feldumbenennungen, die auf den von Ihnen verwendeten Zusammenführungsargumenten basieren. Das zusammengeführte Ergebnis muss noch die relevanten Schlüssel enthalten, um mit dem nachfolgenden Datenrahmen zusammengeführt zu werden.
Charles

Ich vermute, dass etwas mit leeren Datenrahmen passiert. Ich habe einige Beispiele wie dieses ausprobiert: empty <- data.frame(x=numeric(0),a=numeric(0); L3 <- c(empty,empty,list.of.data.frames,empty,empty,empty)und es sind einige seltsame Dinge passiert, die ich noch nicht herausgefunden habe.
Ben Bolker

@ Charles Du bist auf etwas. Ihr Code läuft oben gut für mich. Und wenn ich es an meine anpasse, läuft es auch einwandfrei - außer dass es eine Zusammenführung durchführt und die gewünschten Schlüsselvariablen ignoriert. Wenn ich versuche, Schlüsselvariablen hinzuzufügen, anstatt sie wegzulassen, wird der neue Fehler "Fehler in is.null (x): 'x' fehlt" angezeigt. Die Codezeile lautet "test.reduce <- Reduce (function (...) merge (by = match.by, all = T), my.list)", wobei match.by der Vektor der Schlüsselvariablennamen ist, die zusammengeführt werden sollen durch.
bshor

52

Sie können dies mit merge_allim reshapePaket tun . Sie können Parameter an mergedas ...Argument übergeben

reshape::merge_all(list_of_dataframes, ...)

Hier finden Sie eine hervorragende Ressource zu verschiedenen Methoden zum Zusammenführen von Datenrahmen .


Es sieht so aus, als hätte ich gerade merge_recurse =) repliziert. Gut zu wissen, dass diese Funktion bereits vorhanden ist.
SFun28

16
Ja. Wenn ich eine Idee habe, überprüfe ich immer, ob @hadley sie bereits gemacht hat, und meistens hat er :-)
Ramnath

1
Ich bin ein wenig verwirrt; soll ich merge_all oder merge_recurse machen? In jedem Fall erhalte ich beim Versuch, meine zusätzlichen Argumente zu beiden hinzuzufügen, den Fehler "formales Argument", das alle "mit mehreren tatsächlichen Argumenten übereinstimmt".
bshor

2
Ich glaube, ich habe das von reshape2 entfernt. Reduzieren + Zusammenführen ist genauso einfach.
Hadley

2
@ Ramnath, Link ist tot, gibt es einen Spiegel?
Eduardo

4

Sie können dazu die Rekursion verwenden. Ich habe Folgendes nicht überprüft, aber es sollte Ihnen die richtige Idee geben:

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

2

Ich werde das Datenbeispiel von @PaulRougieux wiederverwenden

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)

Hier ist eine kurze und süße Lösung mit purrrundtidyr

library(tidyverse)

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

1

Die Funktion eatmeines Pakets safejoin hat eine solche Funktion. Wenn Sie ihm eine Liste von data.frames als zweite Eingabe geben, werden sie rekursiv mit der ersten Eingabe verknüpft.

Ausleihen und Erweitern der Daten der akzeptierten Antwort:

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

Wir müssen nicht alle Spalten übernehmen, wir können ausgewählte Helfer von tidyselect verwenden und auswählen (da wir von .xallen .xSpalten ausgehen, werden diese beibehalten):

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

oder entfernen Sie bestimmte:

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

Wenn die Liste benannt ist, werden die Namen als Präfixe verwendet:

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

Wenn es Spaltenkonflikte gibt, .conflictkönnen Sie diese mit dem Argument lösen, indem Sie beispielsweise den ersten / zweiten nehmen, hinzufügen, zusammenführen oder verschachteln.

zuerst behalten:

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

zuletzt halten:

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

hinzufügen:

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

verschmelzen:

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

Nest:

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

NAWerte können durch das .fillArgument ersetzt werden.

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

Standardmäßig ist es erweitert, left_joinaber alle dplyr-Verknüpfungen werden durch das .modeArgument unterstützt, Fuzzy- Verknüpfungen werden auch durch das match_fun Argument (es wird um das Paket gewickelt fuzzyjoin) oder durch Angabe einer Formel wie z. B. ~ X("var1") > Y("var2") & X("var3") < Y("var4")des byArguments unterstützt.


0

Ich hatte eine Liste von Datenrahmen ohne gemeinsame ID-Spalte.
Ich hatte fehlende Daten auf vielen dfs. Es gab Nullwerte. Die Datenrahmen wurden unter Verwendung der Tabellenfunktion erzeugt. Das Reduzieren, Zusammenführen, rbind, rbind.fill und dergleichen konnte mir nicht helfen, mein Ziel zu erreichen. Mein Ziel war es, einen verständlichen zusammengeführten Datenrahmen zu erstellen, der unabhängig von den fehlenden Daten und der gemeinsamen ID-Spalte ist.

Daher habe ich die folgende Funktion gemacht. Vielleicht kann diese Funktion jemandem helfen.

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

es folgt der Funktion

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

}

Beispiel ausführen

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

rbind_null_df_lists ( list_of_df )

0

Wenn Sie eine Liste mit dfs haben und eine Spalte die "ID" enthält, in einigen Listen jedoch einige IDs fehlen, können Sie diese Version von Reduce / Merge verwenden, um mehrere Dfs mit fehlenden Zeilen-IDs oder Beschriftungen zu verknüpfen:

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

0

Hier ist ein generischer Wrapper, mit dem eine Binärfunktion in eine Funktion mit mehreren Parametern konvertiert werden kann. Der Vorteil dieser Lösung ist, dass sie sehr allgemein gehalten ist und auf alle Binärfunktionen angewendet werden kann. Sie müssen es nur einmal tun und können es dann überall anwenden.

Um die Idee zu demonstrieren, verwende ich eine einfache Rekursion, um sie zu implementieren. Es kann natürlich auf elegantere Weise implementiert werden, was von der guten Unterstützung von R für das funktionale Paradigma profitiert.

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

Dann können Sie einfach alle Binärfunktionen damit umschließen und mit Positionsparametern (normalerweise data.frames) in den ersten Klammern und benannten Parametern in den zweiten Klammern (wie by =oder suffix =) aufrufen . Wenn keine benannten Parameter vorhanden sind, lassen Sie die zweiten Klammern leer.

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)()
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.