Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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
like image 782
Triamus Avatar asked Mar 13 '18 09:03

Triamus


Video Answer


1 Answers

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
like image 100
Jack Brookes Avatar answered Oct 13 '22 09:10

Jack Brookes