7

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.

nycrefugee
  • 1,629
  • 1
  • 10
  • 23
  • Hi @nycrefugee! The title suggests a rather general problem. However, in the question body, you have a very specific requirement: "_I want to only keep elements at the 1st level_". You may consider to edit the title to agree with your actual issue, to make it easier to find the question for future visitors with the same problem. Cheers – Henrik Aug 31 '22 at 08:42

5 Answers5

6

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"
Anoushiravan R
  • 21,622
  • 3
  • 18
  • 41
  • 1
    Just came here to post `keep(my_list, ~vec_depth(.) == 1)`. I actually think that [@nycrefugee](https://stackoverflow.com/u/7871804) is already using [**`purrr`**](https://purrr.tidyverse.org/), as indicated by their last code block. – Greg Aug 30 '22 at 20:21
  • Yes but he used `keep` in `map_depth` which is not necessary in this case. I tried to just modify his own solution. – Anoushiravan R Aug 30 '22 at 20:22
  • 1
    Correct. I was simply confirming that your solution is ideal, since it simply fixes the existing attempt in **`purrr`**, which is already in the **`tidyverse`**. No paradigm shift is necessary. – Greg Aug 30 '22 at 20:29
  • `vec_depth` seems like an interesting function! got to know it thanks to this question. Haven't been using R for quite some time. – Anoushiravan R Aug 30 '22 at 20:32
  • 1
    `vec_depth()` might be interesting, but it can have really infuriating implications for `map_depth()`. Since `vec_depth()` throws an error whenever it encounters an object other than a vector or list, I've had to alter the source code as a developer, in order to `map_*()` the penultimate level of a ragged tree containing a diversity of objects. I'll probably post a question to SO in the near future, in search of a more elegant fix. – Greg Aug 30 '22 at 20:37
3

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"
Allan Cameron
  • 147,086
  • 7
  • 49
  • 87
  • I amended the example above to be another level deep. – nycrefugee Aug 30 '22 at 20:23
  • @nycrefugee you can use `my_list[!sapply(my_list, function(x) purrr::vec_depth(x) > 1)]`, then just increase the 1 to whatever depth you want to keep – Allan Cameron Aug 30 '22 at 20:29
  • @AllanCameron At that point, why not just fix [@nycrefugee](https://stackoverflow.com/u/7871804)'s original code in [**`purrr`**](https://purrr.tidyverse.org): `keep(my_list, ~vec_depth(.) == 1))`? – Greg Aug 30 '22 at 21:01
2

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"
Henrik
  • 65,555
  • 14
  • 143
  • 159
1

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"
ThomasIsCoding
  • 96,636
  • 9
  • 24
  • 81
0

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"
Joris C.
  • 5,721
  • 3
  • 12
  • 27