Combine/merge lists by elements names
I have two lists, whose elements have partially overlapping names, which I need to merge/combine together into a single list, element by element:
> lst1 <- list(integers=c(1:7), letters=letters[1:5],
words=c("two", "strings"))
> lst2 <- list(letters=letters[1:10], booleans=c(TRUE, TRUE, FALSE, TRUE),
words=c("another", "two"), floats=c(1.2, 2.4, 3.8, 5.6))
> lst1
$integers
[1] 1 2 3 4 5 6 7
$letters
[1] "a" "b" "c" "d" "e"
$words
[1] "two" "strings"
> lst2
$letters
[1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j"
$booleans
[1] TRUE TRUE FALSE TRUE
$words
[1] "another" "two"
$floats
[1] 1.2 2.4 3.8 5.6
I tried using mapply, which basically combines the two lists by index (i.e.: "[["), while I need to combine them by name (i.e.: "$"). In addition, since the lists have different lengths, the recycling rule is applied (with rather unpredictable results).
> mapply(c, lst1, lst2)
$integers
[1] "1" "2" "3" "4" "5" "6" "7" "a" "b" "c" "d" "e" "f" "g" "h" "i" "j"
$letters
[1] "a" "b" "c" "d" "e" "TRUE" "TRUE" "FALSE" "TRUE"
$words
[1] "two" "strings" "another" "two"
$<NA>
[1] 1.0 2.0 3.0 4.0 5.0 6.0 7.0 1.2 2.4 3.8 5.6
Warning message:
In mapply(c, lst1, lst2) :
longer argument not a multiple of length of shorter
As you might imagine, what I'm looking for is:
$integers
[1] 1 2 3 4 5 6 7
$letters
[1] "a" "b" "c" "d" "e" "a" "b" "c" "d" "e" "f" "g" "h" "i" "j"
$words
[1] "two" "strings" "another" "two"
$booleans
[1] TRUE TRUE FALSE TRUE
$floats
[1] 1.2 2.4 3.8 5.6
Is there any way to achieve that? Thank you!
Solution 1:
You can do:
keys <- unique(c(names(lst1), names(lst2)))
setNames(mapply(c, lst1[keys], lst2[keys]), keys)
Generalization to any number of lists would require a mix of do.call
and lapply
:
l <- list(lst1, lst2, lst1)
keys <- unique(unlist(lapply(l, names)))
setNames(do.call(mapply, c(FUN=c, lapply(l, `[`, keys))), keys)
Solution 2:
An update of flodel's answer for tidyverse
users:
list1 <- list(integers=c(1:7), letters=letters[1:5],
words=c("two", "strings"))
list2 <- list(letters=letters[1:10], booleans=c(TRUE, TRUE, FALSE, TRUE),
words=c("another", "two"), floats=c(1.2, 2.4, 3.8, 5.6))
input_list <- list(list1, list2, list1, list2)
We want to replicate the original desired output exactly twice for each element in the output list. Using map2
and reduce
, we can achieve that with a little bit more clarity than the base R
solution involving do.call
, mapply
, and lapply
. First, we declare a function that combines two lists by their named elements using c()
, then we call our function on the input list via reduce
:
library(purrr)
cat_lists <- function(list1, list2) {
keys <- unique(c(names(list1), names(list2)))
map2(list1[keys], list2[keys], c) %>%
set_names(keys)
}
combined_output <- reduce(input_list, cat_lists)
Which gives us what we want:
> combined_output
#> $integers
#> [1] 1 2 3 4 5 6 7 1 2 3 4 5 6 7
#>
#> $letters
#> [1] "a" "b" "c" "d" "e" "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "a" "b"
#> [18] "c" "d" "e" "a" "b" "c" "d" "e" "f" "g" "h" "i" "j"
#>
#> $words
#> [1] "two" "strings" "another" "two" "two" "strings" "another"
#> [8] "two"
#>
#> $booleans
#> [1] TRUE TRUE FALSE TRUE TRUE TRUE FALSE TRUE
#>
#> $floats
#> [1] 1.2 2.4 3.8 5.6 1.2 2.4 3.8 5.6