Secara bersamaan menggabungkan banyak data.frame dalam daftar

259

Saya memiliki daftar banyak data.frame yang ingin saya gabungkan. Masalahnya di sini adalah bahwa setiap data.frame berbeda dalam hal jumlah baris dan kolom, tetapi mereka semua berbagi variabel kunci (yang saya panggil "var1"dan "var2"dalam kode di bawah). Jika data.frame identik dalam hal kolom, saya hanya bisa rbind, untuk yang plyr's rbind.fill akan melakukan pekerjaan, tetapi itu tidak terjadi dengan data ini.

Karena mergeperintah hanya berfungsi pada 2 frame data, saya beralih ke Internet untuk mencari ide. Saya mendapatkan yang ini dari sini , yang bekerja dengan sempurna di R 2.7.2, yang merupakan apa yang saya miliki saat itu:

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

Dan saya akan memanggil fungsi seperti ini:

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

Tetapi dalam versi R apa pun setelah 2.7.2, termasuk 2.11 dan 2.12, kode ini gagal dengan kesalahan berikut:

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

(Kebetulan, saya melihat referensi lain untuk kesalahan ini di tempat lain tanpa resolusi).

Apakah ada cara untuk menyelesaikan ini?

bshor
sumber

Jawaban:

183

Pertanyaan lain bertanya secara khusus bagaimana melakukan beberapa kiri bergabung menggunakan dplyr di R . Pertanyaan itu ditandai sebagai duplikat dari yang ini jadi saya jawab di sini, menggunakan 3 frame data sampel di bawah ini:

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)

Pembaruan Juni 2018 : Saya membagi jawaban dalam tiga bagian yang mewakili tiga cara berbeda untuk melakukan penggabungan. Anda mungkin ingin menggunakan purrrcaranya jika Anda sudah menggunakan paket yang rapi . Untuk keperluan perbandingan di bawah ini, Anda akan menemukan versi R dasar menggunakan dataset sampel yang sama.


1) Bergabung dengan mereka reducedari purrrpaket:

The purrrpaket menyediakan reducefungsi yang memiliki sintaks ringkas:

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

Anda juga dapat melakukan gabungan lainnya, seperti a full_joinatau 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()dengan 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()dengan basis R Reduce():

Dan untuk tujuan perbandingan, ini adalah versi dasar R dari gabungan kiri

 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
Paul Rougieux
sumber
1
Varian full_join bekerja dengan sempurna, dan terlihat jauh lebih menakutkan daripada jawaban yang diterima. Namun, tidak banyak perbedaan kecepatan.
bshor
1
@ Alex adalah benar, tetapi Anda mungkin dapat menghindari (tampak) mengembalikan daftar bingkai data sama sekali dengan menggunakan map_dfr()ataumap_dfc()
DaveRGP
Saya pikir saya bisa bergabung dengan sejumlah DF berdasarkan pola menggunakan ´ls (pattern = "DF_name_contains_this") ´, tetapi tidak. Digunakan ´noquote (paste (()) ´, tapi saya masih menghasilkan vektor karakter dan bukan daftar DF. Saya akhirnya mengetikkan nama-nama yang menjengkelkan
Pena George William Russel
Pertanyaan lain memberikan implementasi python : daftar frame data panda dfs = [df1, df2, df3]lalu reduce(pandas.merge, dfs).
Paul Rougieux
222

Mengurangi membuatnya cukup mudah:

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

Berikut adalah contoh lengkap menggunakan beberapa data tiruan:

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

Dan inilah contoh menggunakan data ini untuk mereplikasi 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>

Catatan: Sepertinya ini adalah bug di merge. Masalahnya adalah tidak ada pemeriksaan yang menambahkan sufiks (untuk menangani nama yang tidak cocok yang tumpang tindih) benar-benar membuatnya menjadi unik. Pada titik tertentu yang digunakannya[.data.frame yang melakukan make.unique nama, menyebabkan rbindgagal.

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

Cara termudah untuk memperbaikinya adalah dengan tidak meninggalkan bidang yang berganti nama menjadi duplikat bidang (yang ada banyak di sini) hingga merge. Misalnya:

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

The merge/ Reducekemudian akan bekerja dengan baik.

Charles
sumber
Terima kasih! Saya melihat solusi ini juga pada tautan dari Ramnath. Terlihat cukup mudah. Tapi saya mendapatkan kesalahan berikut: "Kesalahan dalam match.names (clabs, names (xi)): nama tidak cocok dengan nama sebelumnya". Variabel yang saya cocokkan semuanya ada di semua kerangka data dalam daftar, jadi saya tidak mengetahui apa yang dikatakan kesalahan ini kepada saya.
bshor
1
Saya menguji solusi ini pada R2.7.2 dan saya mendapatkan error match.names yang sama. Jadi ada beberapa masalah mendasar dengan solusi ini dan data saya. Saya menggunakan kode: Reduce (function (x, y) merge (x, y, all = T, by.x = match.by, by.y = match.by), my.list, akumulasi = F)
bshor
1
Aneh, saya menambahkan kode yang saya uji dengan yang berjalan dengan baik. Saya kira ada beberapa penggantian nama bidang yang terjadi berdasarkan gabungan arg yang Anda gunakan? Hasil yang digabungkan harus tetap memiliki kunci yang relevan untuk digabungkan dengan kerangka data berikutnya.
Charles
Saya menduga sesuatu terjadi dengan frame data kosong. Saya mencoba beberapa contoh seperti ini: empty <- data.frame(x=numeric(0),a=numeric(0); L3 <- c(empty,empty,list.of.data.frames,empty,empty,empty)dan mendapatkan beberapa hal aneh yang belum saya ketahui.
Ben Bolker
@ Charles Kau ke sesuatu. Kode Anda berjalan dengan baik di atas untuk saya. Dan ketika saya mengadaptasinya untuk menambang, itu berjalan dengan baik juga - kecuali bahwa ia melakukan penggabungan mengabaikan variabel kunci yang saya inginkan. Ketika saya mencoba untuk menambahkan variabel kunci daripada membiarkannya keluar, saya mendapatkan kesalahan baru "Kesalahan dalam is.null (x): 'x' hilang". Baris kode adalah "test.reduce <- Reduce (function (...) merge (by = match.by, all = T), my.list)" di mana match.by adalah vektor nama variabel kunci yang ingin saya gabungkan oleh.
bshor
52

Anda dapat melakukannya dengan menggunakan merge_alldalam reshapepaket. Anda dapat melewatkan parameter untuk mergemenggunakan ...argumen

reshape::merge_all(list_of_dataframes, ...)

Berikut adalah sumber yang bagus untuk berbagai metode untuk menggabungkan bingkai data .

Ramnath
sumber
sepertinya saya baru saja meniru merge_recurse =) senang mengetahui fungsi ini sudah ada.
SFun28
16
Iya. setiap kali saya punya ide, saya selalu memeriksa apakah @adley sudah melakukannya, dan sebagian besar kali dia punya :-)
Ramnath
1
Saya agak bingung; saya harus melakukan merge_all atau merge_recurse? Bagaimanapun, ketika saya mencoba menambahkan argumen tambahan saya ke salah satu, saya mendapatkan kesalahan "argumen formal" semua "cocok dengan beberapa argumen aktual".
bshor
2
Saya pikir saya menjatuhkan ini dari membentuk kembali2. Kurangi + gabungkan juga sesederhana itu.
Hadley
2
@Ramnath, tautannya sudah mati, apakah ada cermin?
Eduardo
4

Anda dapat menggunakan rekursi untuk melakukan ini. Saya belum memverifikasi yang berikut, tetapi seharusnya memberi Anda ide yang tepat:

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

Saya akan menggunakan kembali contoh data dari @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)

Inilah solusi singkat dan manis menggunakan purrrdantidyr

library(tidyverse)

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

Fungsi eatpaket saya safejoin memiliki fitur seperti itu, jika Anda memberikannya daftar data.frame sebagai input kedua, ia akan bergabung secara rekursif ke input pertama.

Meminjam dan memperluas data jawaban yang diterima:

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

Kami tidak harus mengambil semua kolom, kami dapat menggunakan pembantu terpilih dari tidyselect dan pilih (saat kami mulai dari .xsemua .xkolom disimpan):

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

atau hapus yang spesifik:

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

Jika daftar ini dinamai, nama akan digunakan sebagai awalan:

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

Jika ada konflik kolom, .conflictargumen ini memungkinkan Anda untuk menyelesaikannya, misalnya dengan mengambil yang pertama / kedua, menambahkannya, menyatukannya, atau menumpuknya.

pertahankan dulu:

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

tetap bertahan:

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

Menambahkan:

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

bersatu:

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

sarang:

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

NAnilai bisa diganti dengan menggunakan .fillargumen.

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

Secara default itu disempurnakan left_jointapi semua dplyr bergabung didukung melalui .modeargumen, kabur bergabung juga didukung melalui match_fun argumen (itu melilit paket fuzzyjoin) atau memberikan susu formula seperti ~ X("var1") > Y("var2") & X("var3") < Y("var4")pada byargumen.

Moody_Mudskipper
sumber
0

Saya punya daftar dataframe tanpa kolom id umum.
Saya memiliki data yang hilang pada banyak dfs. Ada nilai Null. Dataframe diproduksi menggunakan fungsi tabel. Reduce, Merging, rbind, rbind.fill, dan sejenisnya tidak dapat membantu saya untuk tujuan saya. Tujuan saya adalah untuk menghasilkan kerangka data gabungan yang dapat dimengerti, tidak relevan dengan data yang hilang dan kolom id umum.

Karena itu, saya membuat fungsi berikut. Mungkin fungsi ini bisa membantu seseorang.

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

itu mengikuti fungsi

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

}

Menjalankan contoh

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

rbind_null_df_lists ( list_of_df )
Elias EstatisticsEU
sumber
0

Ketika Anda memiliki daftar dfs, dan sebuah kolom berisi "ID", tetapi dalam beberapa daftar, beberapa ID hilang, maka Anda dapat menggunakan versi Reduce / Gabung ini untuk bergabung dengan beberapa Df dari Id Baris atau label yang hilang:

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

Berikut ini adalah pembungkus generik yang dapat digunakan untuk mengubah fungsi biner menjadi fungsi multi-parameter. Manfaat dari solusi ini adalah sangat generik dan dapat diterapkan ke fungsi biner apa pun. Anda hanya perlu melakukannya sekali dan kemudian Anda bisa menerapkannya di mana saja.

Untuk demo ide, saya menggunakan rekursi sederhana untuk diterapkan. Tentu saja dapat diimplementasikan dengan cara yang lebih elegan yang mendapat manfaat dari dukungan R yang baik untuk paradigma fungsional.

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

Kemudian Anda bisa membungkus fungsi biner dengan itu dan memanggil dengan parameter posisi (biasanya data.frame) dalam tanda kurung pertama dan parameter bernama dalam tanda kurung kedua (seperti by =atau suffix =). Jika tidak ada parameter bernama, biarkan tanda kurung kedua kosong.

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)()
englealuze
sumber