同时合并列表中的多个数据框


258

我有许多要合并的data.frames的列表。这里的问题是,每个data.frame不同的行数和列数的条款,但他们都有着关键变量(我打过电话"var1",并"var2"在下面的代码)。如果data.frames在列方面是相同的,我只能这样做rbind,而ply​​r的rbind.fill将为此工作,但这些数据并非如此。

由于该merge命令仅适用于2个data.frames,因此我转向Internet寻求想法。我从这里得到了这个,它在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

(顺便说一句,我在其他地方看到了对此错误的其他参考,没有解决方法)。

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

Answers:


182

另一个问题专门询问了如何在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)

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


1)reducepurrr包装中加入:

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

您还可以执行其他联接,例如a 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()

为了便于比较,这是左联接的基本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
full_join变体完美地工作,并且看起来比接受的答案更不可怕。速度差异不大。
bshor

1
@Axeman是正确的,但是您可以使用map_dfr()map_dfc()
DaveRGP

我虽然可以使用基于“ ls(pattern =“ DF_name_contains_this”“)”的模式加入多个DF,但没有。使用了'noquote(paste(())',但我仍在生成字符向量而不是DF列表。我最终键入的名称令人讨厌。–
乔治·威廉·罗素

另一个问题提供了python实现dfs = [df1, df2, df3]然后 列出熊猫数据帧reduce(pandas.merge, dfs)
Paul Rougieux

222

减少使这相当容易:

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.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))中的错误:名称与以前的名称不匹配”。我要匹配的变量都存在于列表中的所有数据框中,因此我无法理解该错误告诉我的内容。
bshor

1
我在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)
bshor

1
奇怪的是,我添加了用于运行的代码。我猜基于您使用的合并参数会发生一些字段重命名?合并的结果必须仍然具有相关的密钥,才能与后续数据帧合并。
查尔斯

我怀疑空白数据框有问题。我尝试了一些这样的示例:empty <- data.frame(x=numeric(0),a=numeric(0); L3 <- c(empty,empty,list.of.data.frames,empty,empty,empty)并发生了一些我还没有想到的奇怪事情。
本·博克

@Charles你在找东西。您的代码对我来说运行良好。而且,当我将其调整为适合我的方法时,它也可以正常运行-只是它忽略了我想要的关键变量而进行了合并。当我尝试添加关键变量而不是将其遗漏时,出现新错误“ is.null(x)中的错误:'x'丢失”。代码行是“ test.reduce <-Reduce(function(...)merge(by = match.by,all = T),my.list)”,其中match.by是我要合并的关键变量名称的向量通过。
bshor

52

您可以merge_allreshape包中使用它。您可以将参数传递给merge使用...参数

reshape::merge_all(list_of_dataframes, ...)

这是有关合并数据帧的不同方法的出色资源


看起来我刚刚复制了merge_recurse =)很高兴知道此功能已经存在。
SFun28 2011年

16
是。每当我有一个主意时,我总是检查@hadley是否已经完成了,并且大多数时候他都拥有:-)
Ramnath

1
我有点困惑;我应该做merge_all还是merge_recurse?在任何情况下,当我尝试向其中一个添加其他参数时,都会出现错误“正式参数“全部”与多个实际参数匹配”。
bshor

2
我想我从reshape2删除了它。减少+合并就这么简单。
哈德利2011年

2
@Ramnath,链接已死,有镜子吗?
爱德华多

4

您可以使用递归来做到这一点。我尚未验证以下内容,但它应该给您正确的想法:

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

2

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

这是使用purrr和的简短解决方案tidyr

library(tidyverse)

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

1

eat我的包safejoin的功能具有这样的功能,如果给它一个data.frames列表作为第二个输入,它将递归地将它们连接到第一个输入。

借用并扩展接受的答案的数据:

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

我们不必占用所有列,我们可以使用tidyselect中的选择帮助进行选择(因为我们保留了.x所有.x列的开始):

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

或删除特定的:

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

如果列表被命名,则名称将用作前缀:

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

如果存在列冲突,则该.conflict参数允许您解决该问题,例如,采用第一个/第二个,添加它们,合并它们或嵌套它们。

保持第一:

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

保持最后:

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

加:

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

合并:

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

巢:

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值可以使用.fill参数替换。

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

缺省情况下它是一个增强left_join但所有dplyr连接被通过所支持的.mode参数,模糊联接也通过支持match_fun 参数(它包裹绕包fuzzyjoin)或给予式如 ~ X("var1") > Y("var2") & X("var3") < Y("var4")by参数。


0

我有一个没有通用ID列的数据框列表。
我在许多df上缺少数据。有空值。数据帧是使用表函数生成的。Reduce,Merge,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 )

0

当您有一个dfs列表并且一列包含“ ID”时,但是在某些列表中,某些ID丢失了,那么您可以使用此版本的Reduce / Merge来连接多个缺少行ID或标签的Df:

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

0

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

为了演示这个想法,我使用简单的递归来实现。当然,可以受益于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)()
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.