80

I have two lists

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

I want to merge these two lists so the final product is

$a
[1] 1 2

$b
[1] 2 3

$c
[1] 3 4

Is there a simple function to do this?

Ferdi
  • 540
  • 3
  • 12
  • 23
Michael
  • 7,087
  • 21
  • 52
  • 81
  • Check: http://stackoverflow.com/questions/7349608/merge-contents-within-list-of-list-by-duplicate-name – Hansi Mar 01 '12 at 16:10
  • Check https://stackoverflow.com/questions/59992657/how-to-merge-two-lists-based-on-object-indices – Ferroao Jan 31 '20 at 00:35

9 Answers9

122

If lists always have the same structure, as in the example, then a simpler solution is

mapply(c, first, second, SIMPLIFY=FALSE)
Andrei
  • 2,585
  • 1
  • 14
  • 17
  • 32
    This is equivalent to `Map(c, first, second)`, if anyone cares. – Masterfool Jul 17 '15 at 21:56
  • 2
    I'm just learning R, why does Map (and mapply) have 'c' as the first parameter? Shouldn't the parameters passed in simply be the two lists? – user391339 Jun 01 '16 at 15:59
  • 3
    'c' is the name of a primitive function that creates lists. Typing c in R without the trailing parens shows 'function (..., recursive = FALSE) .Primitive("c")' So this cliche is mapping the 'c' function over the contents of first and second. – Chris Warth Jun 28 '16 at 16:29
  • 2
    @Masterfool mapply() is a tick more efficient, since `Map()` contains `mapply()` – Comfort Eagle Jul 27 '17 at 22:14
  • how seriously do we need to worry about the following mapply warning: 'longer argument not a multiple of length of shorter' – 3pitt Sep 26 '17 at 19:42
  • list <- mapply(c, first, second, SIMPLIFY=FALSE) The created list has two elements in each list element. How do I get in the given example list$a = 12 instead of list$a = 1 2 etc.? So that length(list$a) = 1 and not 2. – DerDressing Nov 01 '19 at 12:03
25

This is a very simple adaptation of the modifyList function by Sarkar. Because it is recursive, it will handle more complex situations than mapply would, and it will handle mismatched name situations by ignoring the items in 'second' that are not in 'first'.

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
IRTFM
  • 258,963
  • 21
  • 364
  • 487
  • This is the one that helped me with a more complicated list. The other options didn't seem to handle elements under other elements. – Steven Ouellette Dec 16 '21 at 21:04
  • Yes. IIRC that was the beauty of Sarkar's original that I appreciated and sought to give proper credit. – IRTFM Dec 17 '21 at 03:12
12

Here are two options, the first:

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

and the second, which works only if they have the same structure:

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

Both give the desired result.

Aaron left Stack Overflow
  • 36,704
  • 7
  • 77
  • 142
4

Here's some code that I ended up writing, based upon @Andrei's answer but without the elegancy/simplicity. The advantage is that it allows a more complex recursive merge and also differs between elements that should be connected with rbind and those that are just connected with 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)
}

Here's my example:

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)

This results in:

$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

Yeah, I know - perhaps not the most logical merge but I have a complex parallel loop that I had to generate a more customized .combine function for, and therefore I wrote this monster :-)

Max Gordon
  • 5,367
  • 2
  • 44
  • 70
3
merged = map(names(first), ~c(first[[.x]], second[[.x]])
merged = set_names(merged, names(first))

Using purrr. Also solves the problem of your lists not being in order.

Theo
  • 31
  • 1
1

In general one could,

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

Note that the by() solution returns an attributed list, so it will print differently, but will still be a list. But you can get rid of the attributes with attr(x,"_attribute.name_")<-NULL. You can probably also use aggregate().

csta
  • 2,423
  • 5
  • 26
  • 34
1

We can do a lapply with c(), and use setNames to assign the original name to the output.

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
benson23
  • 16,369
  • 9
  • 19
  • 38
0

Following @Aaron left Stack Overflow and @Theo answer, the merged list's elements are in form of vector c. But if you want to bind rows and columns use rbind and cbind.

merged = map(names(first), ~rbind(first[[.x]], second[[.x]])
merged = set_names(merged, names(first))
Nad Pat
  • 3,129
  • 3
  • 10
  • 20
0

Using dplyr, I found that this line works for named lists using the same names:

as.list(bind_rows(first, second))
buddemat
  • 4,552
  • 14
  • 29
  • 49