rpurrrnested-lists

From a two-level list with the same structure for all items, bind together all like-named items from that second level


I've got a list with a uniform structure: each item is itself a list of commonly named data.frames. I want to find all the items with the same name in the 2nd level and make them a single data.frame. Here's an example list like this:

MyList <- list("A" = list("apples" = data.frame(X = 1:5, 
                                            Source = "A"), 
                      "mangoes" = data.frame(X = 1:5, 
                                             Source = "A")), 
           "B" = list("apples" = data.frame(X = 1:5, 
                                            Source = "B"), 
                      "mangoes" = data.frame(X = 1:5, 
                                             Source = "B")), 
           "C" = list("apples" = data.frame(X = 1:5, 
                                            Source = "C"), 
                      "mangoes" = data.frame(X = 1:5, 
                                             Source = "C")))

I'd like to create a new list with a single level instead of two levels, and I need all the "apples" to be a single data.frame and all the "mangoes" to be a single data.frame. It would look like this:

MyNewList <- list("apples" = bind_rows(data.frame(X = 1:5, 
                                              Source = "A"), 
                                   data.frame(X = 1:5, 
                                              Source = "B"), 
                                   data.frame(X = 1:5, 
                                              Source = "C")), 
              "mangoes" = bind_rows(data.frame(X = 1:5, 
                                               Source = "A"), 
                                    data.frame(X = 1:5, 
                                               Source = "B"), 
                                    data.frame(X = 1:5, 
                                               Source = "C")))

This seems like a job for map or pluck from purrr, but I've always found the syntax for those to be confusing. Here's what I've tried:

Trying to get all the "apples" as a place to start:

 pluck(MyList, 1:3, "apples") # nope; index can't have length > 1

Maybe I can lapply over the names of the items?

 lapply(c("apples", "mangoes"), 
        FUN = function(x) pluck(MyList, x)) # all items are NULL

Maybe map_depth does this?

 map_depth(.x = MyList, 
      .depth = 2, 
      .f = bind_rows) # not at all what I want

Solution

  • You can iterate over each fruit to bind it into a data frame:

    fruits  <- c("apples", "mangoes")
    out  <- lapply(fruits, \(fruit)
        dplyr::bind_rows(lapply(MyList, \(x) x[[fruit]]))
    )  |> setNames(fruits)
    
    identical(out, desired)
    # [1] TRUE