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

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

由于merge命令只适用于2 data.frames,我转向互联网寻找思路。我从在这里中得到了这个,它在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

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

有办法解决这个问题吗?

257444 次浏览

你可以使用递归来做到这一点。我还没有验证以下内容,但它应该会给你一个正确的想法:

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

你可以在reshape包中使用merge_all。可以使用...参数将参数传递给merge

reshape::merge_all(list_of_dataframes, ...)

这里有一个关于合并数据帧的不同方法的优秀资源

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中的一个bug。问题是没有检查添加后缀(以处理重叠的不匹配的名称)是否真的使它们惟一。在某一点上,它使用[.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将正常工作。

另一个问题特别问如何执行多个左连接使用dplyr在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)

答案分为三个部分,代表执行合并的三种不同方式。如果你已经在使用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() with base 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

我将重复使用来自@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)
我有一个没有公共id列的数据帧列表 我丢失了许多dfs的数据。有Null值。 数据帧是使用表函数生成的。 还原,合并,rbind, rbind。填满,他们的同类不能帮助我达到我的目标。 我的目标是产生一个可理解的合并数据框架,与缺失的数据和公共id列无关。< / p >

因此,我做了如下函数。也许这个函数可以帮助到某些人。

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

我们可以使用{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

当你有一个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)

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

为了演示这个想法,我使用简单的递归来实现。当然,它可以用更优雅的方式实现,这得益于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)()