This is a question for which I've written failsafes in my code before, but I'm wondering if there's something more straightforward that I've missed.
I sometimes have 2 (or more) lists that contain different types of information that need to work together with a function such as map2—think a named list of ggplot objects and a named list of file paths for saving output of each. Is there a way built-in or easily added to a piped workflow to make sure list items are matched by name rather than by position?
Consider a simple example:
library(purrr)
evens <- list(a = 2, b = 4, c = 6, d = 8)
odds <- list(a = 11, d = 9, c = 7, b = 5)
map2 returns a list with the same names as the first list, and iterates by position. So the fact that items b and d are switched in odds isn't addressed, and these two calls come out with different results:
map2(evens, odds, function(l1, l2) {
paste(l1, l2)
})
#> $a
#> [1] "2 11"
#>
#> $b
#> [1] "4 9"
#>
#> $c
#> [1] "6 7"
#>
#> $d
#> [1] "8 5"
map2(odds, evens, function(l1, l2) {
paste(l1, l2)
})
#> $a
#> [1] "11 2"
#>
#> $d
#> [1] "9 4"
#>
#> $c
#> [1] "7 6"
#>
#> $b
#> [1] "5 8"
What I've done in the past is to instead use imap and use the names of the first list to extract the appropriate item in the other list, but that means no longer having that second list in my function arguments:
imap(evens, function(l1, name) {
paste(l1, odds[[name]])
})
#> $a
#> [1] "2 11"
#>
#> $b
#> [1] "4 5"
#>
#> $c
#> [1] "6 7"
#>
#> $d
#> [1] "8 9"
If I want to feel like I'm operating more evenly over both lists, I could order them each by name, but this feels clunky:
map2(
evens[order(names(evens))],
odds[order(names(odds))],
function(l1, l2) paste(l1, l2)
)
# same output as previous
Or clunkier still, make a list of the two lists, order them each in another map, then pipe that into pmap since it takes a list of lists:
list(evens, odds) %>%
map(~.[order(names(.))]) %>%
pmap(function(l1, l2) paste(l1, l2))
# same output as previous
Ideally, I'd like to combine the safety of the imap option with the cleanliness of map2.
Just write a helper function to clean it up
namemap <- function(.x, .y, .f, ...) {
n <- order(unique(names(.x), names(.y)))
map2(.x[n], .y[n], .f, ...)
}
namemap(odds, evens, paste)
Basically there's no primitive in purrr that will do this automatically for you. And when it's this easy to do, there doesn't seem to be much point.
We can do
library(tidyverse)
map2(evens, odds[names(evens)], str_c, sep=' ')
#$a
#[1] "2 11"
#$b
#[1] "4 5"
#$c
#[1] "6 7"
#$d
#[1] "8 9"
If both the list names are unordered, loop through the sorted names of one of the list, extract both the elements and concatenate
map(sort(names(evens)), ~ str_c(evens[[.x]], odds[[.x]], sep= ' '))
Or create an identifier for the order, then order the list elements in both the list and concatenate with map2
i1 <- order(names(evens)) # not sure if this should be avoided
map2(evens[i1], odds[i1], str_c, sep=" ")
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With