subset and flatten nested list (purrr style)

Hi folks, I would appreciate help about subsetting and flattening lists using purrr. Bellow is an example including a for loop which does the job and my attempt to do the same using purrr.

library(tidyverse)
library(reprex)
l <-
  list(
    name1 = list(
      mat = matrix(1:9, 3),
      chr = "some text"
    ),
    name2 = list(
      mat = matrix(11:19, 3),
      chr = "some othet text"
    )
  )
l
#> $name1
#> $name1$mat
#>      [,1] [,2] [,3]
#> [1,]    1    4    7
#> [2,]    2    5    8
#> [3,]    3    6    9
#> 
#> $name1$chr
#> [1] "some text"
#> 
#> 
#> $name2
#> $name2$mat
#>      [,1] [,2] [,3]
#> [1,]   11   14   17
#> [2,]   12   15   18
#> [3,]   13   16   19
#> 
#> $name2$chr
#> [1] "some othet text"

# here a for loop which produces desired output, that is subset only matrix and flatten it while keeping the top level names:
desired_l <- list()
for (i in names(l)) {
  desired_l[[i]] <- l[[i]][["mat"]]
}
desired_l
#> $name1
#>      [,1] [,2] [,3]
#> [1,]    1    4    7
#> [2,]    2    5    8
#> [3,]    3    6    9
#> 
#> $name2
#>      [,1] [,2] [,3]
#> [1,]   11   14   17
#> [2,]   12   15   18
#> [3,]   13   16   19


# here my attempt to achieve the same using purrr
## function for subsetting the list (didnt know how to use purrr::keep())
SubList <- function(l, keep_name) {
  l[keep_name]
}
sub_l <- map(l, SubList, keep_name = "mat")
flatt_l <- flatten(sub_l)
flatt_l
#> $mat
#>      [,1] [,2] [,3]
#> [1,]    1    4    7
#> [2,]    2    5    8
#> [3,]    3    6    9
#> 
#> $mat
#>      [,1] [,2] [,3]
#> [1,]   11   14   17
#> [2,]   12   15   18
#> [3,]   13   16   19

# It is pretty close but I dont know how to go about the names. flatten() pulls the names from the level bellow, but I would like to keep top-level names as in output from for loop. 

Thank you a lot for help!
Regards,
Amel

Created on 2023-01-16 by the reprex package (v2.0.1)

Hello @AmelZulji ,

looking at the docs

?purrr::flatten

I saw that the flatten function is depreciated for list_flatten. If I understand correctly you could do:

library(purrr)

l <-
  list(
    name1 = list(
    mat = matrix(1:9, 3),
    chr = "some text"
    ),
    name2 = list(
    mat = matrix(11:19, 3),
    chr = "some othet text"
    )
  )

SubList <- function(l, keep_name) {
l[keep_name]
}

sub_l <- map(l, SubList, keep_name = "mat")

list_flatten(sub_l,name_spec = "{outer}")
#> $name1
#>      [,1] [,2] [,3]
#> [1,]    1    4    7
#> [2,]    2    5    8
#> [3,]    3    6    9
#> 
#> $name2
#>      [,1] [,2] [,3]
#> [1,]   11   14   17
#> [2,]   12   15   18
#> [3,]   13   16   19

sessionInfo()
#> R version 4.2.2 (2022-10-31 ucrt)
#> Platform: x86_64-w64-mingw32/x64 (64-bit)
#> Running under: Windows 10 x64 (build 19045)
#> 
#> Matrix products: default
#> 
#> locale:
#> [1] LC_COLLATE=English_United States.utf8 
#> [2] LC_CTYPE=English_United States.utf8   
#> [3] LC_MONETARY=English_United States.utf8
#> [4] LC_NUMERIC=C                          
#> [5] LC_TIME=English_United States.utf8    
#> 
#> attached base packages:
#> [1] stats     graphics  grDevices utils     datasets  methods   base     
#> 
#> other attached packages:
#> [1] purrr_1.0.1
#> 
#> loaded via a namespace (and not attached):
#>  [1] rstudioapi_0.14   knitr_1.41        magrittr_2.0.3    R.cache_0.16.0   
#>  [5] rlang_1.0.6       fastmap_1.1.0     stringr_1.5.0     styler_1.8.1     
#>  [9] highr_0.10        tools_4.2.2       xfun_0.36         R.oo_1.25.0      
#> [13] cli_3.5.0         withr_2.5.0       htmltools_0.5.4   yaml_2.3.6       
#> [17] digest_0.6.31     lifecycle_1.0.3   vctrs_0.5.1       R.utils_2.12.0   
#> [21] fs_1.5.2          glue_1.6.2        evaluate_0.19     rmarkdown_2.19   
#> [25] reprex_2.0.2      stringi_1.7.8     compiler_4.2.2    R.methodsS3_1.8.2
Created on 2023-01-16 with reprex v2.0.2
2 Likes

Thank you, @HanOostdijk

You are right, flatten() is deprecated and list_flatten(sub_l,name_spec = "{outer}") does the job. I had pretty outdated purrr version.

However, I also figured out that slight modification of SubList function solves the problem as well:

SubList <- function(l, keep_name) {
  l[[keep_name]]
}
sub_l <- map(l, SubList, keep_name = "mat")
sub_l
#> $name1
#>      [,1] [,2] [,3]
#> [1,]    1    4    7
#> [2,]    2    5    8
#> [3,]    3    6    9
#> 
#> $name2
#>      [,1] [,2] [,3]
#> [1,]   11   14   17
#> [2,]   12   15   18
#> [3,]   13   16   19

Note double square brackets in SubList. It extracts single element of the list as compared to single square bracket which subset the list.

1 Like

Great solution. Just adding that, for more complex cases (e.g. if "mat" does not always exist), there are functions pluck() and chuck() that do the same thing with some more safety.

for ex:

map(l, pluck, "mat")
map(l, chuck, "mat")
3 Likes

with rlist package, you can get all the matrix'es out of a nested list like this:

list.flatten(l,
             classes = "matrix")
2 Likes

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.

If you have a query related to it or one of the replies, start a new topic and refer back with a link.