web-dev-qa-db-fra.com

Fusionner deux listes en R

J'ai deux listes

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

Je veux fusionner ces deux listes pour que le produit final soit

$a
[1] 1 2

$b
[1] 2 3

$c
[1] 3 4

Y a-t-il une fonction simple pour faire cela?

71
Michael

Si les listes ont toujours la même structure, comme dans l'exemple, une solution plus simple est alors utilisée.

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

Ceci est une adaptation très simple de la fonction modifyList de Sarkar. Comme il est récursif, il gérera des situations plus complexes que mapply et gérera les situations de noms incompatibles en ignorant les éléments de 'seconde' qui ne sont pas en 'premier'.

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
23
42-

Voici deux options, la première:

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

et le second, qui ne fonctionne que s'ils ont la même structure:

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

Les deux donnent le résultat souhaité.

12
Aaron

Voici un code que j'ai fini par écrire, basé sur la réponse de @ Andrei mais sans élégance ni simplicité. L'avantage est qu'il permet une fusion récursive plus complexe et diffère également entre les éléments devant être connectés à rbind et ceux qui viennent d'être connectés à 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)
}

Voici mon exemple:

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)

Cela se traduit par:

$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

Ouais, je sais - ce n'est peut-être pas la fusion la plus logique, mais j'ai une boucle parallèle complexe pour laquelle je devais générer une fonction .combine Plus personnalisée, et j'ai donc écrit ce monstre :-)

4
Max Gordon

list3 <- append(list1, list2) fonctionnera également

4
JKC

En général, on pourrait,

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

Notez que la solution by() renvoie une liste attributed. Elle sera donc imprimée différemment, mais restera une liste. Mais vous pouvez vous débarrasser des attributs avec attr(x,"_attribute.name_")<-NULL. Vous pouvez probablement aussi utiliser aggregate().

1
csta