R (purrr) flatten list of named lists to list and keep names
Asked Answered
H

2

6

Maybe I'm missing something obvious but trying to flatten a list of named lists of named lists in R (may even be more nested) into eventually one flat list. purrr and rlist seem to have tools for that. How can I achieve that names of sublists become name precrypts of flattened result list, e.g. list1.blist.a in purrr? My actual list is more deeply nested with varying number of levels and repeating names on different levels. In the end I perform purrr::map_df(final_list, bind_rows) and this seems to drop all duplicate names (and even if it didn't I don't know from which branch the original duplicate name comes from). I can do it with rlist but I had hoped for a tidyverse solution (nothing against fantastic rlist but many already have tidyverse installed).

EDIT:

Also note that rlist::list.flatten() will always remove all levels but the top while purrr::flatten() drops levels one at a time which may sometimes be what you need. You could achieve the same by nesting purrr::map(.x, .f = rlist::list.flatten) as often as you require but it's cumbersome and not beautiful/readable.

alist <- list(list1 = list(a = 1, b = 2, blist = list(a = 3, b = 4)),
              list2 = list(a = 1, b = 2, blist = list(a = 3, b = 4)))
str(alist)

List of 2
 $ list1:List of 3
  ..$ a    : num 1
  ..$ b    : num 2
  ..$ blist:List of 2
  .. ..$ a: num 3
  .. ..$ b: num 4
 $ list2:List of 3
  ..$ a    : num 1
  ..$ b    : num 2
  ..$ blist:List of 2
  .. ..$ a: num 3
  .. ..$ b: num 4

alist_flat <- purrr::map(alist, purrr::flatten)
str(alist_flat)

List of 2
 $ list1:List of 4
  ..$ a: num 1
  ..$ b: num 2
  ..$ a: num 3
  ..$ b: num 4
 $ list2:List of 4
  ..$ a: num 1
  ..$ b: num 2
  ..$ a: num 3
  ..$ b: num 4

alist_flattest <- purrr::flatten(alist_flat)
str(alist_flattest)

List of 8
 $ a: num 1
 $ b: num 2
 $ a: num 3
 $ b: num 4
 $ a: num 1
 $ b: num 2
 $ a: num 3
 $ b: num 4

# works with rlist
alist_flat_names <- map(alist, rlist::list.flatten, use.names = TRUE)
str(alist_flat_names)

List of 2
 $ list1:List of 4
  ..$ a      : num 1
  ..$ b      : num 2
  ..$ blist.a: num 3
  ..$ blist.b: num 4
 $ list2:List of 4
  ..$ a      : num 1
  ..$ b      : num 2
  ..$ blist.a: num 3
  ..$ blist.b: num 4

alist_flattest_names <- rlist::list.flatten(alist_flat_names, use.names = TRUE)
str(alist_flattest_names)

List of 8
 $ list1.a      : num 1
 $ list1.b      : num 2
 $ list1.blist.a: num 3
 $ list1.blist.b: num 4
 $ list2.a      : num 1
 $ list2.b      : num 2
 $ list2.blist.a: num 3
 $ list2.blist.b: num 4
Hypothyroidism answered 13/3, 2018 at 9:22 Comment(0)
T
3

I looked at the source for rlist::list.flatten() and copied the source into a new function to avoid that dependency.

my_flatten <- function (x, use.names = TRUE, classes = "ANY") 
{
  #' Source taken from rlist::list.flatten
  len <- sum(rapply(x, function(x) 1L, classes = classes))
  y <- vector("list", len)
  i <- 0L
  items <- rapply(x, function(x) {
    i <<- i + 1L
    y[[i]] <<- x
    TRUE
  }, classes = classes)
  if (use.names && !is.null(nm <- names(items))) 
    names(y) <- nm
  y
}

alist <- list(list1 = list(a = 1, b = 2, blist = list(a = 3, b = 4)),
              list2 = list(a = 1, b = 2, blist = list(a = 3, b = 4)))


flat_list <- my_flatten(alist)

str(flat_list)

Result:

List of 8
 $ list1.a      : num 1
 $ list1.b      : num 2
 $ list1.blist.a: num 3
 $ list1.blist.b: num 4
 $ list2.a      : num 1
 $ list2.b      : num 2
 $ list2.blist.a: num 3
 $ list2.blist.b: num 4
Trisyllable answered 13/3, 2018 at 9:39 Comment(1)
that's of course one way but note the differences in rlist::list.flatten() vs. purrr::flatten(). See my edit. Otherwise of course a perfectly great way.Hypothyroidism
T
2

unlist fully flattens a list into a named vector with the names you seem to want:

alist <- list(list1 = list(a = 1, b = 2, blist = list(a = 3, b = 4)),
              list2 = list(a = 1, b = 2, blist = list(a = 3, b = 4)))

> unlist(alist)
      list1.a       list1.b list1.blist.a list1.blist.b       list2.a 
            1             2             3             4             1 
      list2.b list2.blist.a list2.blist.b 
            2             3             4 

Then its one step to build that back to a named list. So my function would be:

splat_to_list = function(x){
  as.list(unlist(x))
}

Then:

> str(splat_to_list(alist))
List of 8
 $ list1.a      : num 1
 $ list1.b      : num 2
 $ list1.blist.a: num 3
 $ list1.blist.b: num 4
 $ list2.a      : num 1
 $ list2.b      : num 2
 $ list2.blist.a: num 3
 $ list2.blist.b: num 4

is identical to my_flatten in a previous answer.

Note it uses only base R functions, and there have been changes to purrr recently such as deprecating development of flatten, so old code is going to break.

Takeo answered 6/1, 2023 at 11:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.