filtering a list with purrr::keep

Consider this simple list

list('a' = list(1,2),'b'=list(list(3,4), list(5,6)), 'c' = 'hello')
$a
$a[[1]]
[1] 1

$a[[2]]
[1] 2


$b
$b[[1]]
$b[[1]][[1]]
[1] 3

$b[[1]][[2]]
[1] 4


$b[[2]]
$b[[2]][[1]]
[1] 5

$b[[2]][[2]]
[1] 6



$c
[1] "hello"

Let's say I want to keep the element a and b from it. I do not understand why this works

list('a' = list(1,2),'b'=list(list(3,4), list(5,6)), 'c' = 'hello') %>% 
  purrr::keep(names(.) == 'a' | names(.) == 'b')

while using the following does not work

> list('a' = list(1,2),'b'=list(list(3,4), list(5,6)), 'c' = 'hello') %>% 
+   purrr::keep(~names(.x) %in% c('a','b'))
Error: Predicate functions must return a single `TRUE` or `FALSE`, not a logical vector of length 0
Call `rlang::last_error()` to see a backtrace

what is wrong with the second syntax?
Thanks!

1 Like

In the first example that does work, . is part of the pipe syntax, so it refers to the list that you piped into purrr::keep().

In the second example, ~ names(.x) %in% c("a", "b") is shorthand for

f <- function(.x) names(.x) %in% c("a", "b")

but when a function is applied to each element of a list, the name of the list element isn't available.

library(purrr)

ex_list <- list('a' = list(1,2),'b'=list(list(3,4), list(5,6)), 'c' = 'hello')

el_name <- function(x) names(x)

map(ex_list, el_name)
#> $a
#> NULL
#> 
#> $b
#> NULL
#> 
#> $c
#> NULL

The result is NULL for each element because the elements passed to el_name() are nameless.

I believe the easiest way to subset a list by name is to use base R list filtering syntax, i.e. [ ] single bracket subsetting.

ex_list[c("a", "b")]
#> $a
#> $a[[1]]
#> [1] 1
#> 
#> $a[[2]]
#> [1] 2
#> 
#> 
#> $b
#> $b[[1]]
#> $b[[1]][[1]]
#> [1] 3
#> 
#> $b[[1]][[2]]
#> [1] 4
#> 
#> 
#> $b[[2]]
#> $b[[2]][[1]]
#> [1] 5
#> 
#> $b[[2]][[2]]
#> [1] 6

Created on 2019-02-26 by the reprex package (v0.2.1)

5 Likes

thanks! but how can I use [ in a pipe then?

You can write your own "keep" function that does what you want, or you can use dot notation.

library(purrr)

ex_list <- list('a' = list(1,2),'b'=list(list(3,4), list(5,6)), 'c' = 'hello')

keep_by_name <- function(l, keep_names) l[keep_names]

ex_list %>% keep_by_name(c("a", "c"))
#> $a
#> $a[[1]]
#> [1] 1
#> 
#> $a[[2]]
#> [1] 2
#> 
#> 
#> $c
#> [1] "hello"

ex_list %>% .[c("a", "c")]
#> $a
#> $a[[1]]
#> [1] 1
#> 
#> $a[[2]]
#> [1] 2
#> 
#> 
#> $c
#> [1] "hello"

Created on 2019-02-26 by the reprex package (v0.2.1)

This same issue always gets me too -- I always think keep() should keep top level elements, but it works at the second level of a list rather than the first.

magrittr have some useful function for pipe operation

library(purrr)
#> Warning: le package 'purrr' a été compilé avec la version R 3.5.2

ex_list <- list('a' = list(1,2),'b'=list(list(3,4), list(5,6)), 'c' = 'hello')

# use magrittr function
ex_list %>%
  magrittr::extract(c("a", "c"))
#> $a
#> $a[[1]]
#> [1] 1
#> 
#> $a[[2]]
#> [1] 2
#> 
#> 
#> $c
#> [1] "hello"

Created on 2019-02-26 by the reprex package (v0.2.1)

4 Likes

thanks but what is the advantage of extract when I can use .[] as above? its even shorter :kissing_smiling_eyes::kissing_smiling_eyes:

Nothing. :wink: Just to let you know it exists in magrittr and that this :package: contains other functions pipe friendly that could help some time :blush:

1 Like

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.