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

24 浏览
0 Comments

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

我有一个包含许多数据框的列表,我希望将它们合并。问题在于,每个数据框在行和列方面都不同,但它们都共享关键变量(在下面的代码中我称其为\"var1\"\"var2\")。如果数据框在列方面相同,那么我可以使用rbind,其中plyr的rbind.fill可以完成任务,但这些数据并非如此。

由于merge命令只适用于2个数据框,因此我求助于互联网。我从这里得到了这个想法,它可以在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

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

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

admin 更改状态以发布 2023年5月21日
0
0 Comments

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   
#2     ALVES   100 RI      019       S         NA      NA   NA   NA         NA   
#3    BADEAU   100 RI      032       S         NA      NA   NA   NA         NA   


注意:这似乎是merge中的一个错误。问题是没有检查添加后缀(以处理重叠的非匹配名称)是否真的使它们唯一。在某个点上,它使用[.data.frame来使名称唯一,导致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然后将正常工作。

0
0 Comments

另一个问题具体询问如何在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包,则可能希望使用purrr方法。为了比较,您将在下面找到使用相同示例数据集的基本R版本。


1)使用purrr包的缩减方法join它们:

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

library(tidyverse)
list(x, y, z) %>% reduce(left_join, by = "i")
#  A tibble: 3 x 4
#  i       j     k     l
#     
# 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
#    
# 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
#    
# 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

0