50

I am using the map function of the purrr package in R which gives as output a list. Now I would like the output to be a named list based on the input. An example is given below.

input <- c("a", "b", "c")
output <- purrr::map(input, function(x) {paste0("test-", x)})

From this I would like to access elements of the list using:

output$a

Or

output$b
1
  • 2
    This is done by default by base::Map: output2 <- Map(function(x) {paste0("test-", x)}, input) . Commented Jun 5, 2018 at 14:28

3 Answers 3

51
Answer recommended by R Language Collective

The recommended solution:

c("a", "b", "c") %>% 
    purrr::set_names() %>% 
    purrr::map(~paste0('test-', .))
Sign up to request clarification or add additional context in comments.

1 Comment

Brilliant. We set the name in advance. This also works with map_dfc, and avoids the "renaming warnings for the duplicate names" that I get when setting the name after the map.
46

We just need to name the list

names(output) <- input

and then extract the elements based on the name

output$a
#[1] "test-a"

If this needs to be done using tidyverse

library(tidyverse)
output <- map(input, ~paste0('test-', .)) %>% 
                                setNames(input)

2 Comments

FYI purrr imports set_names() from rlang for this reason. Not that there is any significant difference between base::setNames()...
@AndersSwanson There are significant differences, and in particular you can do x %>% set_names() %>% map(fn) which is the recommended solution to this problem.
32

Update

Now in 2020 the answer form @mihagazvoda describes the correct approach: simply set_names before applying map

c("a", "b", "c") %>% 
    purrr::set_names() %>% 
    purrr::map(~paste0('test-', .))

Outdated answer

The accepted solution works, but suffers from a repeated argument (input) which may cause errors and interrupts the flow when using piping with %>%.

An alternative solution would be to use a bit more power of the %>% operator

1:5 %>% { set_names(map(., ~ .x + 3), .) } %>% print # ... or something else

This takes the argument from the pipe but still lacks some beauty. An alternative could be a small helper method such as

map_named = function(x, ...) map(x, ...) %>% set_names(x)

1:5 %>% map_named(~ .x + 1)

This already looks more pretty and elegant. And would be my preferred solution.

Finally, we could even overwrite purrr::map in case the argument is a character or integer vector and produce a named list in such a case.

map = function(x, ...){
    if (is.integer(x) | is.character(x)) {
        purrr::map(x, ...) %>% set_names(x)
    }else {
        purrr::map(x, ...) 
    }
}

1 : 5 %>% map(~ .x + 1)

However, the optimal solution would be if purrr would implement such behaviour out of the box.

4 Comments

That would be awesome! Shouldn't you propose such a pull request? Or have they a good reason not to do this that way?
My gut feeling would be, that the purrr maintainers do not like it because it creates different semantics depending on the input. I'd imagine that they prefer some more explicit approach, like with a parameter, but this would support the usecase as well. Also, it would break backward compatibility in quite a few cases I guess. But sure, why not asking, see github.com/tidyverse/purrr/issues/691
The recommended solution (added a year later by @mihagazvoda) shows there is indeed a purrrring way of doing this.
Indeed. I don't want to delete the answer, but rather cast the upvotes to this answer to @mihagazvoda. Is there a stackoverflow way to do so?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.