【问题标题】:Simultaneously merge multiple data.frames in a list同时合并列表中的多个 data.frames
【发布时间】:2011-12-26 20:07:33
【问题描述】:

我有许多要合并的 data.frames 的列表。这里的问题是每个 data.frame 在行数和列数方面都不同,但它们都共享关键变量(我在下面的代码中称为 "var1""var2")。如果 data.frames 在列方面相同,我只能rbind,plyr 的rbind.fill 可以完成这项工作,但这些数据并非如此。

因为merge 命令仅适用于 2 个 data.frames,所以我转向互联网寻求灵感。我从here 得到了这个,它在 R 2.7.2 中完美运行,这就是我当时所拥有的:

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

我会这样调用函数:

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

但是在 2.7.2 之后的任何 R 版本中,包括 2.11 和 2.12,此代码都会失败并出现以下错误:

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

(顺便说一句,我看到其他引用此错误elsewhere 没有解决方案)。

有没有办法解决这个问题?

【问题讨论】:

    标签: r list merge dataframe r-faq


    【解决方案1】:

    另一个问题专门问how to perform multiple left joins using dplyr in R 。这个问题被标记为这个问题的重复,所以我在这里回答,使用下面的 3 个示例数据框:

    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)
    

    2018 年 6 月更新:我将答案分为三个部分,代表执行合并的三种不同方式。如果您已经在使用 tidyverse 软件包,您可能想要使用 purrr 方式。为了在下面进行比较,您将找到使用相同示例数据集的基本 R 版本。


    1) 通过purrr 包中的reduce 加入他们:

    purrr 包提供了一个reduce 函数,该函数语法简洁:

    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
    

    您还可以执行其他联接,例如 full_joininner_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 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() 与基 R Reduce()

    出于比较目的,这里是基于 Charles 的回答的左连接的基本 R 版本。

     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
    

    【讨论】:

    • full_join 变体完美运行,并且看起来没有公认的答案那么可怕。不过,速度差别不大。
    • @Axeman 是对的,但您可以通过使用map_dfr()map_dfc() 完全避免(明显地)返回数据帧列表
    • 我虽然可以使用 'ls(pattern = "DF_name_contains_this" )' 加入多个基于模式的 DF,但不能。使用了 'noquote( paste(())',但我仍在生成字符向量而不是 DF 列表。我最终输入了名称,这令人讨厌。
    • 另一个问题提供了python implementation:熊猫数据框列表dfs = [df1, df2, df3] 然后reduce(pandas.merge, dfs)
    • 如何添加后缀以避免自动附加“.y”或“.x”?
    【解决方案2】:

    Reduce 使这变得相当容易:

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

    这是一个使用一些模拟数据的完整示例:

    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
    

    这是一个使用these data 复制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>
    

    注意:看起来这可能是merge 中的一个错误。问题是没有检查添加后缀(以处理重叠的不匹配名称)实际上使它们唯一。在某个时刻,它使用[.data.frame确实 make.unique 的名称,导致rbind 失败。

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

    最简单的修复方法是不要将重复字段(这里有很多)的字段重命名为 merge。例如:

    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 然后就可以正常工作了。

    【讨论】:

    • 谢谢!我也在 Ramnath 的链接上看到了这个解决方案。看起来很容易。但我收到以下错误:“match.names(clabs, names(xi)) 中的错误:名称与以前的名称不匹配”。我匹配的变量都存在于列表中的所有数据框中,所以我没有理解这个错误告诉我的内容。
    • 我在 R2.7.2 上测试了这个解决方案,我得到了同样的 match.names 错误。所以这个解决方案和我的数据存在一些更根本的问题。我使用的代码:Reduce(function(x, y) merge(x, y, all=T,by.x=match.by, by.y=match.by), my.list, accumle=F)跨度>
    • 奇怪,我添加了我测试它运行良好的代码。我想根据您使用的合并参数会发生一些字段重命名?合并后的结果必须仍然有相关的键,才能与后续的数据框合并。
    • 我怀疑空数据框发生了一些事情。我尝试了一些这样的例子:empty &lt;- data.frame(x=numeric(0),a=numeric(0); L3 &lt;- c(empty,empty,list.of.data.frames,empty,empty,empty) 并发生了一些我还没有弄清楚的奇怪事情。
    • @Charles 你有点意思。你的代码对我来说运行良好。当我将它调整为我的时,它也运行良好——除了它忽略了我想要的关键变量进行合并。当我尝试添加关键变量而不是将它们排除在外时,我收到一个新错误“is.null(x) 中的错误:'x' 丢失”。代码行是“test.reduce
    【解决方案3】:

    您可以使用reshape 包中的merge_all 来实现。您可以使用... 参数将参数传递给merge

    reshape::merge_all(list_of_dataframes, ...)
    

    Here is an excellent resource on different methods to merge data frames.

    【讨论】:

    • 看起来我只是复制了 merge_recurse =) 很高兴知道这个函数已经存在。
    • 是的。每当我有想法时,我都会检查@hadley 是否已经完成了,而且大多数时候他都有:-)
    • 我有点困惑;我应该做merge_all 还是merge_recurse?在任何情况下,当我尝试向其中任何一个中添加其他参数时,都会收到错误“formal argument "all"matched by multiple actual arguments”。
    • 我想我从 reshape2 中删除了这个。减少 + 合并同样简单。
    • @Ramnath,链接失效了,有镜像吗?
    【解决方案4】:

    您可以使用递归来执行此操作。我还没有验证以下内容,但它应该给你正确的想法:

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

    【讨论】:

      【解决方案5】:

      我将重用来自@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)
      

      这是一个使用purrrtidyr 的简短而甜蜜的解决方案

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

      【讨论】:

        【解决方案6】:

        我们可以使用 {powerjoin}。

        从接受的答案中借用样本数据:

        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)
        
        library(powerjoin)
        power_full_join(list(x,y,z), by = "i")
        #>   i  j  k  l
        #> 1 a  1 NA  9
        #> 2 b  2  4 NA
        #> 3 c  3  5  7
        #> 4 d NA  6  8
        
        power_left_join(list(x,y,z), by = "i")
        #>   i j  k  l
        #> 1 a 1 NA  9
        #> 2 b 2  4 NA
        #> 3 c 3  5  7
        

        您也可以从数据框开始并加入数据框列表,以获得相同的结果

        
        power_full_join(x, list(y,z), by = "i")
        #>   i  j  k  l
        #> 1 a  1 NA  9
        #> 2 b  2  4 NA
        #> 3 c  3  5  7
        #> 4 d NA  6  8
        

        【讨论】:

          【解决方案7】:

          我有一个没有通用 id 列的数据框列表。
          我丢失了许多 dfs 的数据。有 Null 值。 数据帧是使用表函数生成的。 Reduce、Merging、rbind、rbind.fill 等无法帮助我实现目标。 我的目标是生成一个可以理解的合并数据框,与丢失的数据和常见的 id 列无关。

          因此,我做了以下功能。也许这个功能可以帮助某人。

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

          它遵循函数

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

          运行示例

          ##########################################################
          ####             Running the example                 #####
          ##########################################################
          
          rbind_null_df_lists ( list_of_df )
          

          【讨论】:

            【解决方案8】:

            当您有一个dfs列表,并且一列包含“ID”,但在某些列表中,缺少一些ID,那么您可以使用此版本的Reduce / Merge来加入多个缺少行ID的Dfs或标签:

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

            【讨论】:

              【解决方案9】:

              这是一个通用包装器,可用于将二进制函数转换为多参数函数。这个解决方案的好处是它非常通用,可以应用于任何二进制函数。你只需要做一次,然后你就可以在任何地方应用它。

              为了演示这个想法,我使用简单的递归来实现。它当然可以用更优雅的方式来实现,这得益于 R 对函数范式的良好支持。

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

              然后您可以简单地用它包装任何二进制函数,并在第一个括号中使用位置参数(通常是 data.frames)调用,在第二个括号中使用命名参数(例如by =suffix =)。如果没有命名参数,则将第二个括号留空。

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

              【讨论】:

                猜你喜欢
                • 2019-03-02
                • 1970-01-01
                • 2011-09-02
                • 1970-01-01
                • 2016-05-02
                相关资源
                最近更新 更多