Bir listedeki birden çok veriyi aynı anda birleştirin. Çerçeveler


259

Birleştirmek istediğim birçok data.frames listem var. Buradaki sorun, her data.frame'in satır ve sütun sayısı bakımından farklılık göstermesidir, ancak hepsinin anahtar değişkenlerini (aradığım "var1"ve "var2"aşağıdaki kodda) paylaşmasıdır . rbindData.frames sütunlar açısından özdeş olsaydı, sadece , plyr rbind.fill iş yapmak istiyorum, ama bu verilerde durum böyle değil.

Çünkü mergekomut sadece 2 data.frames çalışır, ben fikirler için Internet döndü. Buradan aldım , R 2.7.2'de mükemmel bir şekilde çalıştı, o sırada sahip olduğum şey:

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

Ve ben fonksiyonu şöyle çağırırdım:

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

Ancak 2.11 ve 2.12 dahil 2.7.2'den sonraki herhangi bir R sürümünde bu kod aşağıdaki hata ile başarısız olur:

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

(Bu arada, bu hatayı başka bir yerde , çözünürlüğü olmayan başka referanslar görüyorum ).

Bunu çözmenin bir yolu var mı?

Yanıtlar:


183

Başka bir soru, özellikle R'de dplyr kullanarak birden fazla sol birleşimin nasıl gerçekleştirileceğini sordu . Soru, bunun bir kopyası olarak işaretlendi, bu yüzden aşağıdaki 3 örnek veri çerçevesini kullanarak burada cevap verdim:

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)

Haziran 2018 Güncellemesi : Cevabı birleştirmenin üç farklı yolunu temsil eden üç bölüme ayırdım. Tidyverse paketlerini purrrkullanıyorsanız, muhtemelen yolu kullanmak istersiniz . Aşağıdaki karşılaştırma amacıyla, aynı örnek veri kümesini kullanan bir temel R sürümü bulacaksınız.


1) ile bunları Üyelik reducegelen purrrpaketin:

purrrPaket sağlayan reducebir kısa sözdizimi sahip işlevi:

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

A full_joinveya gibi diğer birleştirmeleri de gerçekleştirebilirsiniz 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()R tabanı ile 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) R merge()tabanı ile R tabanı Reduce():

Ve karşılaştırma amacıyla, sol birleşimin temel R sürümü

 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
Full_join varyantı mükemmel çalışıyor ve kabul edilen yanıttan çok daha az korkutucu görünüyor. Yine de çok fazla hız farkı yok.
bshor

1
@Axeman haklı, ancak veri çerçevelerinin bir listesini map_dfr()veyamap_dfc()
DaveRGP

´ls (pattern = "DF_name_contains_this") ´ kullanarak bir desene dayalı bir dizi DF katılabilir ama ben hayır. Kullanılmış'noquote (yapıştırın (()) ', ama yine de yerine iğrenç İsimleri yazarak bitti DF listesi, bir karakter vektör üreten ediyorum..
George William Russel kalemi

Başka bir soru bir python uygulaması sağlar : panda veri çerçeveleri listesi dfs = [df1, df2, df3]o zaman reduce(pandas.merge, dfs).
Paul Rougieux

222

Azalt bunu oldukça kolaylaştırır:

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

Aşağıda bazı sahte verileri kullanan tam bir örnek verilmiştir:

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

Ve işte bu verileri çoğaltmak için kullanan bir örnek 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>

Not: Görünüşe göre bu bir hata merge. Sorun, eklerin (örtüşen eşleşmeyen adların işlenmesi için) eklenmenin onları benzersiz kıldığından emin olmamalarıdır. Belli bir noktada kullandığı [.data.framehangi yapar make.unique neden isimler rbindbaşarısız.

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

Düzeltmenin en kolay yolu, yinelenen alanlar (burada çok sayıda var) için alanı yeniden adlandırmayı bırakmamaktır merge. Örneğin:

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

merge/ Reduceİş ince sonra olacak.


Teşekkürler! Bu çözümü Ramnath'ın bağlantısında da gördüm. Yeterince kolay görünüyor. Ancak şu hatayı alıyorum: "match.names (clabs, names (xi)) hatası: adlar önceki adlarla eşleşmiyor". Eşleştirdiğim değişkenlerin hepsi listedeki tüm veri çerçevelerinde mevcut, bu yüzden bu hatanın bana söylediklerini yakalamıyorum.
bshor

1
Bu çözümü R2.7.2'de test ettim ve aynı match.names hatasını alıyorum. Bu çözüm ve verilerimle ilgili daha temel bir sorun var. Kodu kullandım: Reduce (fonksiyon (x, y) birleştirme (x, y, all = T, by.x = match.by, by.y = match.by), listem, biriktir = F)
bshor

1
Garip, test ettiğim kodu eklediğimde iyi çalışıyor. Kullandığınız birleştirme argümanlarına dayalı olarak bazı alan yeniden adlandırma var sanırım? Birleştirilen sonucun, sonraki veri çerçevesiyle birleştirilebilmesi için yine de ilgili anahtarları olması gerekir.
Charles

Boş veri çerçevelerinde bir şey olduğundan şüpheleniyorum. Bunun gibi bazı örnekler denedim: empty <- data.frame(x=numeric(0),a=numeric(0); L3 <- c(empty,empty,list.of.data.frames,empty,empty,empty)ve henüz çözemediğim bazı garip şeyler oldu.
Ben Bolker

@Charles Bir şeye kapılıyorsun. Kodunuz benim için yukarıda iyi çalışıyor. Ve benimkine uyarladığımda, aynı zamanda iyi çalışıyor - tek istediğim temel değişkenleri göz ardı ederek birleştirme yapıyor. Anahtar değişkenleri dışarıda bırakmak yerine eklemeye çalıştığımda, yeni bir hata alıyorum "Hata is.null (x): 'x' eksik". Kod satırı "test.reduce <- Azalt (işlev (...) birleştirme (by = match.by, all = T), my.list)" Burada match.by, birleştirilmesini istediğim anahtar değişken adlarının vektörüdür tarafından.
bshor

52

Sen kullanarak bunu yapabilirsiniz merge_alliçinde reshapepaketin. Parametreleri argümanı mergekullanarak geçirebilirsiniz...

reshape::merge_all(list_of_dataframes, ...)

İşte veri çerçevelerini birleştirmek için farklı yöntemler üzerinde mükemmel bir kaynak .


Ben sadece bu işlevi zaten var bilmek iyi merge_recurse =) çoğaltılmış gibi görünüyor.
SFun28

16
Evet. ne zaman bir fikrim var, ben her zaman @hadley zaten yapmış olup olmadığını kontrol, ve çoğu zaman o :-)
Ramnath

1
Biraz kafam karıştı; merge_all veya merge_recurse yapmalı mıyım? Her halükarda, ek argümanlarımdan birine eklemeye çalıştığımda, "resmi bağımsız değişken" hepsi "birden çok gerçek bağımsız değişkenle eşleşti" hatasını alıyorum.
bshor

2
Sanırım bunu reshape2'den düşürdüm. Azaltma + birleştirme de aynı derecede basittir.
hadley

2
@ Ramnath, bağlantı öldü, ayna var mı?
Eduardo

4

Bunu yapmak için özyineleme kullanabilirsiniz. Aşağıdakileri doğrulamam, ancak size doğru fikri vermelidir:

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

2

@PaulRougieux'daki veri örneğini tekrar kullanacağım

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)

İşte kullanarak kısa ve tatlı bir çözüm purrrvetidyr

library(tidyverse)

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

1

eatBenim safejoin paketimin işlevi böyle bir özelliğe sahiptir, eğer ikinci bir giriş olarak data.frames listesini verirseniz, bunları ilk girdiye özyineli olarak birleştirir.

Kabul edilen cevap verilerinin ödünç alınması ve genişletilmesi:

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

Tüm sütunları almak zorunda değiliz, tidyselect'ten seç yardımcıları kullanabilir ve seçebiliriz ( .xtüm .xsütunlardan başladığımız gibi tutulur):

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

veya belirli olanları kaldırın:

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

Liste adlandırılırsa, adlar önek olarak kullanılır:

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

Sütun çakışmaları varsa, .conflictargüman bunu çözmenize izin verir, örneğin birinci / ikinci olanı alarak, ekleyerek, birleştirerek veya iç içe geçirerek.

önce sakla:

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

sonuncu kal:

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

Ekle:

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

kaynaşabilecek:

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

yuva:

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

NAdeğerler .fillargüman kullanılarak değiştirilebilir .

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

Varsayılan olarak geliştirilmiş bulunuyor left_joinancak tüm dplyr aracılığıyla desteklenen birleşimler .modeargüman, bulanık da içinden desteklenen birleşimler match_fun argüman (o paketin etrafına sarılı fuzzyjoin) ya da örneğin bir formül vererek ~ X("var1") > Y("var2") & X("var3") < Y("var4")etmek byargüman.


0

Ben ortak id sütun ile dataframes bir listesi vardı.
Birçok dfs veri eksik vardı. Null değerler vardı. Veri çerçeveleri tablo fonksiyonu kullanılarak üretilmiştir. Azalt, Birleştir, rbind, rbind.fill ve benzerleri amacım için bana yardımcı olamadı. Amacım, eksik veriler ve ortak kimlik sütunu ile alakasız anlaşılabilir birleştirilmiş bir veri çerçevesi oluşturmaktı.

Bu nedenle, aşağıdaki işlevi yaptım. Belki bu fonksiyon birine yardım edebilir.

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

işlevi takip ediyor

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

}

Örneği çalıştırma

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

rbind_null_df_lists ( list_of_df )

0

Bir dfs listeniz varsa ve bir sütun "ID" içeriyorsa, ancak bazı listelerde bazı kimlikler eksikse, eksik Satır Kimlikleri veya etiketlerinin birden fazla Dfs'sine katılmak için Küçültme / Birleştirme'nin bu sürümünü kullanabilirsiniz:

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

0

İşte bir ikili işlevi çok parametreli işleve dönüştürmek için kullanılabilecek genel bir sarıcı. Bu çözümün yararı, çok genel olması ve herhangi bir ikili fonksiyona uygulanabilmesidir. Sadece bir kez yapmanız gerekir ve daha sonra herhangi bir yere uygulayabilirsiniz.

Fikri demo olarak uygulamak için basit özyineleme kullanıyorum. Elbette, R'nin işlevsel paradigma için iyi desteğinden faydalanan daha zarif bir şekilde uygulanabilir.

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

Daha sonra herhangi bir ikili işlevi onunla sarabilir ve ilk parantezlerde konum parametreleri (genellikle data.frames) ve ikinci parantezlerde ( by =veya gibi suffix =) adlandırılmış parametrelerle çağırabilirsiniz . Adlandırılmış parametre yoksa, ikinci parantezleri boş bırakın.

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)()
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.