How to keep first level list elements in a multi level nested list of lists

Viewed 89

I have a list with many levels. I want to only keep elements at the 1st level.

Example list:

 my_list <-
   list(
    a.1 = "some text",
    b.1 = NA,
    c.1 = integer(0),
    d.1 = "some text",
    e.1 = list(a.2 = "some text", b.2 = "a2"),
    f.1 = list(c.2 = "some text", d.2 = integer(10), e.2 = list(a.3 = "some deep text"))
   )

... and I'd like to end up with:

 my_list2 <-
   list(
     a.1 = "some text",
     b.1 = NA,
     c.1 = integer(0),
     d.1 = "some text"
   )

Given the real list is messy and many levels deep I'd like to be able to use something like purrr::keep to simply remove further nested items.

I have tried using keep but the predicate functions throw back errors:

 map_depth(my_list, 1, ~ keep(.x, vec_depth(.x) > 1))

Error in probe(.x, .p, ...) : length(.p) == length(.x) is not TRUE

Thanks.

5 Answers

Or maybe this solution in tidyverse:

library(purrr)

my_list %>% 
  keep(~ vec_depth(.x) == 1)

$a.1
[1] "some text"

$b.1
[1] NA

$c.1
integer(0)

$d.1
[1] "some text"

You could subset your list to only include items that are not themselves lists using:

my_list[!sapply(my_list, is.list)]
#> $a.1
#> [1] "some text"
#>
#> $b.1
#> [1] NA
#>
#> $c.1
#> integer(0)
#>
#> $d.1
#> [1] "some text"

Using collapse::atomic_elem to "extract [...] the atomic [...] elements at the top-level of the list tree"

collapse::atomic_elem(my_list)
# $a.1
# [1] "some text"
#
# $b.1
# [1] NA
#
# $c.1
# integer(0)
# 
# $d.1
# [1] "some text"

You can try Filter + is.list like below

> Filter(Negate(is.list),my_list)
$a.1
[1] "some text"

$b.1
[1] NA

$c.1
integer(0)

$d.1
[1] "some text"

Another approach using rrapply::rrapply() (extended version of base rapply):

library(rrapply)

rrapply(my_list, condition = \(x, .xpos) length(.xpos) == 1, how = "prune") |>
  str()
#> List of 4
#>  $ a.1: chr "some text"
#>  $ b.1: logi NA
#>  $ c.1: int(0) 
#>  $ d.1: chr "some text"

This may be useful as it is easily modified to handle other filter conditions as well. For instance,

rrapply(my_list, condition = \(x) x == "some text", how = "prune") |>
  str()
#> List of 4
#>  $ a.1: chr "some text"
#>  $ d.1: chr "some text"
#>  $ e.1:List of 1
#>   ..$ a.2: chr "some text"
#>  $ f.1:List of 1
#>   ..$ c.2: chr "some text"

rrapply(my_list, condition = \(x, .xname) grepl("a", .xname), how = "prune") |>
  str()
#> List of 3
#>  $ a.1: chr "some text"
#>  $ e.1:List of 1
#>   ..$ a.2: chr "some text"
#>  $ f.1:List of 1
#>   ..$ e.2:List of 1
#>   .. ..$ a.3: chr "some deep text"
Related