wake-up-neo.com

Fassen Sie gleichzeitig mehrere data.frames in einer Liste zusammen

Ich habe eine Liste mit vielen data.frames, die ich zusammenführen möchte. Das Problem hierbei ist, dass sich jeder data.frame in der Anzahl der Zeilen und Spalten unterscheidet, dass jedoch alle die Schlüsselvariablen gemeinsam haben (die ich im Code unten "var1" und "var2" genannt habe). Wenn die data.frames in Bezug auf Spalten identisch wären, könnte ich lediglich rbind verwenden, wofür plyr's rbind.fill die Arbeit machen würde, aber das ist bei diesen Daten nicht der Fall. 

Da der Befehl merge nur für 2 data.frames funktioniert, habe ich mich wegen Ideen für das Internet entschieden. Ich habe dieses hier von hier bekommen, was 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 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 Auflösung).

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

199
bshor

Eine andere 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 drei Beispiel-Datenrahmen:

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 Juni 2018: Ich habe die Antwort in drei Abschnitte unterteilt, die drei verschiedene Arten der Zusammenführung darstellen. Sie möchten wahrscheinlich die Variable purrr verwenden, wenn Sie bereits die Pakete tidyverse verwenden. Zu Vergleichszwecken finden Sie unten eine R-Basisversion, die das gleiche Beispiel-Dataset verwendet.

Verbinden Sie sie mit reduce aus dem purrr-Paket

Das Paket purrr bietet eine reduce-Funktion mit einer kurzen 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. einen full_join oder 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() 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

Basis R merge() mit Basis R Reduce()

Und zum Vergleich ist hier eine Basisversion R 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
110
Paul Rougieux

Reduzieren macht dies ziemlich einfach:

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

Hier ist ein vollständiges Beispiel mit einigen Mock-Daten:

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 mit diese Daten um my.list zu replizieren:

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>

Anmerkung: Es sieht so aus, als wäre dies wahrscheinlich ein Fehler in merge. Das Problem ist, dass es keine Überprüfung gibt, dass das Hinzufügen der Suffixe (um überschneidende, nicht übereinstimmende Namen zu behandeln) sie tatsächlich eindeutig macht. An einem bestimmten Punkt werden [.data.frame verwendet, wobei die Namenmake.unique verwendet, wodurch rbind fehlschlägt.

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

Der einfachste Weg zur Behebung besteht darin, die Feldumbenennung für Dublettenfelder (von denen hier viele vorhanden sind) nicht bis zu merge zu belassen. 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))

Die merge/Reduce funktioniert dann einwandfrei.

206
Charles

Sie können dies mit merge_all im Paket reshape tun. Sie können Parameter mit dem Argument ... an merge übergeben

reshape::merge_all(list_of_dataframes, ...)

Hier ist eine ausgezeichnete Quelle für verschiedene Methoden zum Zusammenführen von Datenrahmen .

47
Ramnath

Sie können Rekursion dazu verwenden. Ich habe Folgendes nicht bestätigt, 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 ]] , ... ) )
}
4
SFun28

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 purrr und tidyr

library(tidyverse)

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

Die Funktion eat meines Pakets safejoin hat eine solche Funktion. Wenn Sieit eine Liste von data.frames als zweite Eingabe angeben, werden sie Rekursiv mit der ersten Eingabe verbunden.

Daten der akzeptierten Antwort ausleihen und erweitern:

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 mitnehmen, wir können ausgewählte Helfer aus tidyselect und Auswählen. (Wenn wir von .x beginnen, werden alle .x-Spalten 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 bestimmte entfernen:

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

Bei Spaltenkonflikten können Sie das .conflict-Argument auflösen, Zum Beispiel, indem Sie den ersten/zweiten verwenden, hinzufügen, verschmelzen, Oder verschachteln.

zuerst halten:

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

letzte 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

NA-Werte können mit dem .fill-Argument 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 handelt es sich um einen erweiterten left_join, aber alle dplyr - Joins werden vom Argument .mode unterstützt. Fuzzy-Joins werden auch vom match_fun-Argument (es wird um das Paket fuzzyjoin gewickelt) oder .__ Formel wie ~ X("var1") > Y("var2") & X("var3") < Y("var4") mit dem Argument by.

0

Ich hatte eine Liste von Datenrahmen ohne gemeinsame ID-Spalte.
Ich hatte fehlende Daten zu vielen DFS. Es gab Nullwerte. Die Datenrahmen wurden mit der Tabellenfunktion erstellt. Das Reduzieren, Zusammenführen, Rinden, Recken und dergleichen konnte mir nicht helfen, mein Ziel zu erreichen. Mein Ziel war es, ein verständliches zusammengefügtes Datenframe zu erstellen, das für die fehlenden Daten und die allgemeine ID-Spalte keine Rolle spielt. 

Deshalb habe ich 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

}

Das Beispiel ausführen

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

rbind_null_df_lists ( list_of_df )
0