我有一个我要合并的许多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没有解决方案)。
有什么方法可以解决这个问题吗?
答案 0 :(得分:211)
减少使这相当容易:
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
可以正常使用。
答案 1 :(得分:129)
另一个问题是how to perform multiple left joins using dplyr in R 。这个问题被标记为这个问题的副本,所以我在这里回答,使用下面的3个样本数据框:
library(dplyr)
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)
更新2018年6月:我将答案分为三个部分,分别代表三种不同的合并方式。如果您已经使用 tidyverse 包,则可能需要使用purrr
方式。为了进行比较,您将使用相同的样本数据集找到基本R版本。
reduce
包purrr
加入他们
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_join
或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
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
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
答案 2 :(得分:48)
您可以使用merge_all
包中的reshape
来执行此操作。您可以使用merge
参数
...
reshape::merge_all(list_of_dataframes, ...)
Here is an excellent resource on different methods to merge data frames
答案 3 :(得分:4)
您可以使用递归来执行此操作。我没有验证以下内容,但它应该给你正确的想法:
MergeListOfDf = function( data , ... )
{
if ( length( data ) == 2 )
{
return( merge( data[[ 1 ]] , data[[ 2 ]] , ... ) )
}
return( merge( MergeListOfDf( data[ -1 ] , ... ) , data[[ 1 ]] , ... ) )
}
答案 4 :(得分: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)
答案 5 :(得分:1)
我的软件包safejoin中的函数eat
具有这种功能,如果您提供
它是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
coalesce:
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
参数。
答案 6 :(得分: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 )
答案 7 :(得分:0)
当您有一个dfs列表并且一列包含“ ID”时,但是在某些列表中,某些ID丢失了,那么您可以使用此版本的Reduce / Merge来加入多个缺少行ID的Dfs或标签:
cap <img>
答案 8 :(得分: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)()