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

5

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.

Captive answered 30/8, 2022 at 20:9 Comment(1)
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. CheersConcessive
I
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"
Incised answered 30/8, 2022 at 20:19 Comment(5)
Just came here to post keep(my_list, ~vec_depth(.) == 1). I actually think that @nycrefugee is already using purrr, as indicated by their last code block.Lantz
Yes but he used keep in map_depth which is not necessary in this case. I tried to just modify his own solution.Incised
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.Lantz
vec_depth seems like an interesting function! got to know it thanks to this question. Haven't been using R for quite some time.Incised
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.Lantz
T
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"
Teacake answered 30/8, 2022 at 20:16 Comment(3)
I amended the example above to be another level deep.Captive
@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 keepTeacake
@AllanCameron At that point, why not just fix @nycrefugee's original code in purrr: keep(my_list, ~vec_depth(.) == 1))?Lantz
C
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"
Concessive answered 30/8, 2022 at 20:53 Comment(0)
I
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"
Incurvate answered 31/8, 2022 at 7:56 Comment(0)
B
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"
Bronez answered 31/8, 2022 at 7:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.