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 sort
ed 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