同时合并列表中的多个 data.frames

Simultaneously merge multiple data.frames in a list

提问人:bshor 提问时间:11/11/2011 最后编辑:Communitybshor 更新时间:12/1/2022 访问量:277518

问:

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

由于该命令仅适用于 2 个 data.frames,因此我转向 Internet 寻求想法。我从这里得到了这个,它在 R 2.7.2 中完美运行,这就是我当时所拥有的:merge

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

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

有什么办法可以解决这个问题吗?

列表 合并 数据帧 R-常见问题

评论


答:

6赞 Suraj 11/11/2011 #1

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

MergeListOfDf = function( data , ... )
{
    if ( length( data ) == 2 ) 
    {
        return( merge( data[[ 1 ]] , data[[ 2 ]] , ... ) )
    }    
    return( merge( MergeListOfDf( data[ -1 ] , ... ) , data[[ 1 ]] , ... ) )
}
60赞 Ramnath 11/11/2011 #2

您可以在包中使用。您可以使用参数将参数传递给merge_allreshapemerge...

reshape::merge_all(list_of_dataframes, ...)

这是关于合并数据框的不同方法的绝佳资源

评论

0赞 Suraj 11/11/2011
看起来我刚刚复制了 merge_recurse =) 很高兴知道这个函数已经存在。
17赞 Ramnath 11/11/2011
是的。每当我有想法时,我总是检查@hadley是否已经做到了,而且大多数时候他已经做到了:-)
1赞 bshor 11/12/2011
我有点困惑;我应该做merge_all还是merge_recurse?无论如何,当我尝试将我的附加参数添加到其中任何一个时,我都会收到错误“形式参数”所有“与多个实际参数匹配”。
3赞 hadley 11/12/2011
我想我从 reshape2 中删除了它。减少 + 合并也同样简单。
2赞 Eduardo 10/22/2014
@Ramnath,林克死了,有镜子吗?
242赞 Charles 11/12/2011 #3

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

下面是一个使用这些数据进行复制的示例: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.framemake.uniquerbind

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

然后 / 将正常工作。mergeReduce

评论

0赞 bshor 11/12/2011
谢谢!我也在 Ramnath 的链接上看到了这个解决方案。看起来很简单。但是我收到以下错误:“match.names(clabs,names(习))中的错误:名称与以前的名称不匹配”。我匹配的变量都存在于列表中的所有数据帧中,所以我没有捕捉到这个错误告诉我什么。
1赞 bshor 11/15/2011
我在 R2.7.2 上测试了这个解决方案,但出现相同的 match.names 错误。因此,这个解决方案和我的数据存在一些更根本的问题。我使用了代码:Reduce(function(x, y) merge(x, y, all=T,by.x=match.by, by.y=match.by), my.list, accumulate=F)
1赞 Charles 11/15/2011
奇怪的是,我添加了我测试它的代码,它运行良好。我想根据您使用的合并参数发生了一些字段重命名?合并的结果必须仍具有相关键,才能与后续数据框合并。
0赞 Ben Bolker 11/15/2011
我怀疑空数据帧发生了一些事情。我尝试了一些这样的例子:并发生了一些我还没有弄清楚的奇怪事情。empty <- data.frame(x=numeric(0),a=numeric(0); L3 <- c(empty,empty,list.of.data.frames,empty,empty,empty)
0赞 bshor 11/16/2011
@Charles 你正在做某事。您的代码在上面对我来说运行良好。当我根据我的调整它时,它也运行良好——除了它做了一个合并,忽略了我想要的关键变量。当我尝试添加关键变量而不是将它们排除在外时,我收到一个新错误“错误在 is.null(x) 中:'x' 丢失”。代码行是“test.reduce <- Reduce(function(...)merge(by=match.by, all=T), my.list)“,其中 match.by 是我想要合并的关键变量名称的向量。
319赞 Paul Rougieux 12/21/2015 #4

另一个问题专门询问了如何在 R 中使用 dplyr 执行多个左连接。该问题被标记为与此问题重复,因此我在这里使用以下 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)

答案分为三个部分,代表执行合并的三种不同方式。如果您已经在使用 tidyverse 包,您可能希望使用这种方式。为了在下面的比较目的,你将找到使用相同示例数据集的基本 R 版本。purrr


1) 用 purrr 包中的 reduce 加入它们:

该软件包提供了一个具有简洁语法的函数:purrrreduce

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) Base R merge() 与 Base 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

评论

1赞 bshor 12/21/2016
full_join变体运行良好,看起来比公认的答案要可怕得多。不过,速度差异不大。
1赞 DaveRGP 6/30/2017
@Axeman是对的,但您可以通过使用 或map_dfr()map_dfc()
0赞 aae 3/8/2019
我虽然可以使用'ls(pattern = “DF_name_contains_this” )'基于模式加入许多DF,但没有。使用了'noquote( paste((())',但我仍在生成一个字符向量而不是 DF 列表。我最终输入了名字,这很令人讨厌。
0赞 Paul Rougieux 3/18/2019
另一个问题提供了一个python实现:然后列出pandas数据帧。dfs = [df1, df2, df3]reduce(pandas.merge, dfs)
0赞 jgarces 11/20/2020
如何添加后缀以避免自动附加“.y”或“.x”?
5赞 dmi3kno 7/28/2017 #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)
1赞 Estatistics 10/17/2018 #6

我有一个没有通用 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赞 moodymudskipper 3/3/2019 #7

我们可以使用 {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
0赞 Estatistics 9/12/2019 #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)
1赞 englealuze 5/13/2020 #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)()