filtering lists nested in a list by element-name

I have a list of lists like the following:

testlist <- list(
  list(aaa_x = 1, aaa_y = 2, aaa_z = 5, bbb_a = 333, bbb_b = 222),
  list(aaa_x = 7, aaa_y = 5, aaa_z = 6, bbb_a = 3939, bbb_b = 5635)
)

I want to filter the lists contained in the list (actually, I want to split the list) on condition of name prefixes, so I thought of doing it this way:

testlist %>% map(keep(grepl(pattern="aaa", x=names(.))))

However, I end up with

Error in probe(.x, .p, ...) : argument ".p" is missing, with no default

while the following works (but is of course not sufficient):

testlist[[1]] %>% keep(grepl(pattern="aaa", x=names(.)))

There's probably a very nice, simple, tidy way to do it...

Thanks in advance!

Do you want the result to retain the current structure i.e. should it return a list of lists with only the elements named "aaa"?

yes, that's the goal. Sorry for not directly providing a desired result. I want it to look like this:

filteredlist <- list(
  list(aaa_x = 1, aaa_y = 2, aaa_z = 5),
  list(aaa_x = 7, aaa_y = 5, aaa_z = 6)
)

This ought to do the trick. You can substitute grepl() for str_detect() if you wish. I just like stringr. :grin:

library(tidyverse)

testlist <- list(
  list(aaa_x = 1, aaa_y = 2, aaa_z = 5, bbb_a = 333, bbb_b = 222),
  list(aaa_x = 7, aaa_y = 5, aaa_z = 6, bbb_a = 3939, bbb_b = 5635)
)

map(testlist, ~ keep(.x, .p = str_detect(names(.x), "aaa")))
#> [[1]]
#> [[1]]$aaa_x
#> [1] 1
#> 
#> [[1]]$aaa_y
#> [1] 2
#> 
#> [[1]]$aaa_z
#> [1] 5
#> 
#> 
#> [[2]]
#> [[2]]$aaa_x
#> [1] 7
#> 
#> [[2]]$aaa_y
#> [1] 5
#> 
#> [[2]]$aaa_z
#> [1] 6

Created on 2020-05-22 by the reprex package (v0.3.0)

2 Likes

Works like a charm, thank you!

If you have the time to expand a bit, I'd like to understand what I did wrong with my example above.

So this one works

map(testlist, ~ keep(.x, .p = str_detect(names(.x), "aaa")))

while this one doesn't

map(testlist, keep(str_detect(names(.x), "aaa")))

So I need to turn it into a function/formula? I thought the whole point of map was to supply the individual elements of the (first) argument to the (second) function?

I think it's more accurate to think of it the other way around i.e. map() applies the function in .f to each element of .x.

# mean() is applied to each vector in the list.
purrr::map(.x = list(c(1:3), c(4:6)), .f = mean)
#> [[1]]
#> [1] 2
#> 
#> [[2]]
#> [1] 5

Created on 2020-05-22 by the reprex package (v0.3.0)

However, for the problem at hand, we want to test whether each element meets a specified condition. In essence, we want to use the element as an argument in a function call. In order to do that, we need to define an anonymous function.

purrr simply provides ~ as a shorthand to save typing. For example, the two calls below are equivalent.

library(tidyverse)

testlist <- list(
  list(aaa_x = 1, aaa_y = 2, aaa_z = 5, bbb_a = 333, bbb_b = 222),
  list(aaa_x = 7, aaa_y = 5, aaa_z = 6, bbb_a = 3939, bbb_b = 5635)
)

result_1 <- map(testlist, function(x) keep(x, .p = str_detect(names(x), "aaa")))

result_2 <- map(testlist, ~ keep(.x, .p = str_detect(names(.x), "aaa")))

identical(result_1, result_2)
#> [1] TRUE

Created on 2020-05-22 by the reprex package (v0.3.0)

1 Like

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