在R中合并两个列表

81

我有两个列表。

first = list(a = 1, b = 2, c = 3)
second = list(a = 2, b = 3, c = 4)

我想合并这两个列表,以便最终产物为

$a
[1] 1 2

$b
[1] 2 3

$c
[1] 3 4

有没有一个简单的函数可以做到这一点?


请查看:https://dev59.com/9FvUa4cB1Zd3GeqPpxO2 - Hansi
请查看以下链接以了解如何根据对象索引合并两个列表:https://dev59.com/krjoa4cB1Zd3GeqPHvfA - Ferroao
9个回答

124

如果列表始终具有相同的结构,就像示例中一样,那么更简单的解决方案是:

mapply(c, first, second, SIMPLIFY=FALSE)

33
如果有人关心的话,这相当于Map(c, first, second) - Masterfool
2
我正在学习R语言,为什么Map(和mapply)的第一个参数是“c”?难道不应该直接传入两个列表作为参数吗? - user391339
3
“c” 是一个原始函数的名称,用于创建列表。在R中键入不带尾括号的“c”会显示“function(...,recursive = FALSE).Primitive(“c”)”。因此,这个惯用语是将“c”函数映射到第一个和第二个内容上。 - Chris Warth
2
@Masterfool,mapply()更有效率一些,因为Map()包含mapply() - Comfort Eagle
我们需要多么认真地关注以下mapply警告:“较长的参数不是较短的长度的倍数”? - 3pitt
列表 <- mapply(c, first, second, SIMPLIFY=FALSE)。创建的列表每个列表元素有两个元素。在给定的示例中,如何使列表$a = 12而不是list$a = 1 2等?这样length(list$a) = 1而不是2。 - DerDressing

26

这是对Sarkar的modifyList函数的非常简单的改编。因为它是递归的,所以它可以处理比mapply更复杂的情况,并且可以通过忽略在“第二个”中不在“第一个”中的项目来处理名称不匹配的情况。

appendList <- function (x, val) 
{
    stopifnot(is.list(x), is.list(val))
    xnames <- names(x)
    for (v in names(val)) {
        x[[v]] <- if (v %in% xnames && is.list(x[[v]]) && is.list(val[[v]])) 
            appendList(x[[v]], val[[v]])
        else c(x[[v]], val[[v]])
    }
    x
}

> appendList(first,second)
$a
[1] 1 2

$b
[1] 2 3

$c
[1] 3 4

这是帮助我处理更复杂列表的方法。其他选项似乎无法处理其他元素下面的元素。 - Steven Ouellette
是的。如果我没记错,这就是我欣赏Sarkar原作的美妙之处,并且我希望给予适当的认可。 - IRTFM

12

这里有两种选择,第一种:

both <- list(first, second)
n <- unique(unlist(lapply(both, names)))
names(n) <- n
lapply(n, function(ni) unlist(lapply(both, `[[`, ni)))

第二个仅在它们具有相同结构的情况下起作用:

apply(cbind(first, second),1,function(x) unname(unlist(x)))

两种方法都能得到期望的结果。


我认为你的第二个程序不正确,因为我得到了一个矩阵设计而不是向量列表。 - Tyler Rinker
你是正确的; 如果可以,apply会简化它。但是如果像first$c <- c(4,5)这样无法简化时,它也能正常工作。 - Aaron left Stack Overflow
第一个给出了长度为0的列表。names应该被定义为某个值吗? - 3pitt
你的列表有名称吗? - Aaron left Stack Overflow

4
这是一些我根据@Andrei的回答编写的代码,但没有那么优美/简单。其优点是允许更复杂的递归合并,并区分应使用rbind连接的元素和只使用c连接的元素。
# Decided to move this outside the mapply, not sure this is 
# that important for speed but I imagine redefining the function
# might be somewhat time-consuming
mergeLists_internal <- function(o_element, n_element){
  if (is.list(n_element)){
    # Fill in non-existant element with NA elements
    if (length(n_element) != length(o_element)){
      n_unique <- names(n_element)[! names(n_element) %in% names(o_element)]
      if (length(n_unique) > 0){
        for (n in n_unique){
          if (is.matrix(n_element[[n]])){
            o_element[[n]] <- matrix(NA, 
                                     nrow=nrow(n_element[[n]]), 
                                     ncol=ncol(n_element[[n]]))
          }else{
            o_element[[n]] <- rep(NA, 
                                  times=length(n_element[[n]]))
          }
        }
      }

      o_unique <- names(o_element)[! names(o_element) %in% names(n_element)]
      if (length(o_unique) > 0){
        for (n in o_unique){
          if (is.matrix(n_element[[n]])){
            n_element[[n]] <- matrix(NA, 
                                     nrow=nrow(o_element[[n]]), 
                                     ncol=ncol(o_element[[n]]))
          }else{
            n_element[[n]] <- rep(NA, 
                                  times=length(o_element[[n]]))
          }
        }
      }
    }  

    # Now merge the two lists
    return(mergeLists(o_element, 
                      n_element))

  }
  if(length(n_element)>1){
    new_cols <- ifelse(is.matrix(n_element), ncol(n_element), length(n_element))
    old_cols <- ifelse(is.matrix(o_element), ncol(o_element), length(o_element))
    if (new_cols != old_cols)
      stop("Your length doesn't match on the elements,",
           " new element (", new_cols , ") !=",
           " old element (", old_cols , ")")
  }

  return(rbind(o_element, 
               n_element, 
               deparse.level=0))
  return(c(o_element, 
           n_element))
}
mergeLists <- function(old, new){
  if (is.null(old))
    return (new)

  m <- mapply(mergeLists_internal, old, new, SIMPLIFY=FALSE)
  return(m)
}

以下是我的示例:

v1 <- list("a"=c(1,2), b="test 1", sublist=list(one=20:21, two=21:22))
v2 <- list("a"=c(3,4), b="test 2", sublist=list(one=10:11, two=11:12, three=1:2))
mergeLists(v1, v2)

这会导致:
$a
     [,1] [,2]
[1,]    1    2
[2,]    3    4

$b
[1] "test 1" "test 2"

$sublist
$sublist$one
     [,1] [,2]
[1,]   20   21
[2,]   10   11

$sublist$two
     [,1] [,2]
[1,]   21   22
[2,]   11   12

$sublist$three
     [,1] [,2]
[1,]   NA   NA
[2,]    1    2

是的,我知道——也许这不是最合适的合并方法,但我有一个复杂的并行循环,必须生成一个更定制的.combine函数,因此我写了这个庞然大物 :-)


3
merged = map(names(first), ~c(first[[.x]], second[[.x]])
merged = set_names(merged, names(first))

使用 purrr。它还解决了列表无序的问题。


2
我们可以使用c()进行lapply,并使用setNames将原始名称分配给输出。
setNames(lapply(1:length(first), function(x) c(first[[x]], second[[x]])), names(first))

$a
[1] 1 2

$b
[1] 2 3

$c
[1] 3 4

1
一般而言,可以这样做:

merge_list <- function(...) by(v<-unlist(c(...)),names(v),base::c)

请注意,by()解决方案返回一个带有attribute的列表,因此它会以不同的方式打印,但仍然是一个列表。但是您可以使用 attr(x,"_attribute.name_")<-NULL 来去除属性。您还可以使用aggregate()

0

在 @Aaron 离开 Stack Overflow 和 @Theo 的回答之后,合并的列表元素以向量 c 的形式呈现。 但如果你想要绑定行和列,请使用 rbindcbind

merged = map(names(first), ~rbind(first[[.x]], second[[.x]])
merged = set_names(merged, names(first))

0
使用 dplyr,我发现这行代码适用于具有相同名称的命名列表:
as.list(bind_rows(first, second))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接