Безопаснее purrr :: map2 для списков с именами не по порядку - PullRequest
9 голосов
/ 22 апреля 2019

Это вопрос, для которого я уже писал в моем коде ошибки, но мне интересно, есть ли что-то более простое, что я пропустил.

Иногда у меня есть 2 (или более) списка, которые содержат информацию разных типов, которая должна работать вместе с такой функцией, как map2 - продумайте именованный список ggplot объектов и именованный список путей к файлам для сохранения. выход каждого. Есть ли способ встроенного или легко добавляемого в рабочий процесс, чтобы убедиться, что элементы списка сопоставляются по имени, а не по позиции?

Рассмотрим простой пример:

library(purrr)

evens <- list(a = 2, b = 4, c = 6, d = 8)
odds <- list(a = 11, d = 9, c = 7, b = 5)

map2 возвращает список с теми же именами, что и список first , и выполняет итерацию по позиции. Поэтому тот факт, что элементы b и d включены в odds, не рассматривается, и эти два вызова дают разные результаты:

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"

В прошлом я вместо этого использовал imap и использовал имена первого списка для извлечения соответствующего элемента из другого списка, но это означает, что у меня больше нет второго списка в аргументах моей функции:

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"

Если я хочу почувствовать, что я работаю более равномерно в обоих списках, я мог бы упорядочить их по именам, но это кажется неуклюжим:

map2(
  evens[order(names(evens))],
  odds[order(names(odds))],
  function(l1, l2) paste(l1, l2)
)
# same output as previous

Или еще более грубый, составьте список из двух списков, упорядочите их каждый в другой map, затем перенаправьте его в pmap, так как он принимает список списков:

list(evens, odds) %>%
  map(~.[order(names(.))]) %>%
  pmap(function(l1, l2) paste(l1, l2))
# same output as previous

В идеале я бы хотел совместить безопасность опции imap с чистотой map2.

Ответы [ 4 ]

4 голосов
/ 22 апреля 2019

bind_rows соответствует именам, так что вы можете bind_rows, а затем map (хотя это накладывает дополнительные ограничения на то, что в списках)

library(tidyverse)

bind_rows(evens, odds) %>% 
  map(paste, collapse = ' ')

# $`a`
# [1] "2 11"
# 
# $b
# [1] "4 5"
# 
# $c
# [1] "6 7"
# 
# $d
# [1] "8 9"
4 голосов
/ 22 апреля 2019

Мы можем сделать

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"

Если оба list имени неупорядочены, переберите sort ed names одного из list, извлеките оба элемента и объедините

map(sort(names(evens)), ~ str_c(evens[[.x]], odds[[.x]], sep= ' '))

Или создайте идентификатор для order, затем order list элементов в обоих list и объедините с map2

i1 <- order(names(evens)) # not sure if this should be avoided
map2(evens[i1], odds[i1], str_c, sep=" ")
3 голосов
/ 22 апреля 2019

Просто напишите вспомогательную функцию, чтобы очистить ее

namemap <- function(.x, .y, .f, ...) {
  n <- order(unique(names(.x), names(.y)))
  map2(.x[n], .y[n], .f, ...)
}
namemap(odds, evens, paste)

По сути, в purrr нет примитива, который сделает это автоматически для вас. И когда это так легко сделать, кажется, нет особого смысла.

2 голосов
/ 23 апреля 2019

transpose(), кажется, делает это (соответствие по имени).Хотя это не задокументировано ( edit: объяснение аргумента .names дает контекст, и есть примеры), и документация местами кажется неточной (purrr v. 0.3.1).

Это называется транспонированием, поскольку x[[1]][[2]] эквивалентно transpose(x)[[2]][[1]].

^ представляется неточным, поскольку в этом случае list(evens, odds)[[2]][[4]] is 5 и transpose(list(evens, odds))[[4]][[2]] is 9.

Также

Обратите внимание, что transpose () является своей собственной инверсией, очень похожей на операцию транспонирования в матрице.Вы можете получить исходный ввод, перенеся его дважды.

не совсем точен, но мы можем использовать это в наших интересах:

list(evens, odds) %>% 
  transpose() %>% 
  transpose()
#> [[1]]
#> [[1]]$a
#> [1] 2
#> 
#> [[1]]$b
#> [1] 4
#> 
#> [[1]]$c
#> [1] 6
#> 
#> [[1]]$d
#> [1] 8
#> 
#> 
#> [[2]]
#> [[2]]$a
#> [1] 11
#> 
#> [[2]]$b
#> [1] 5
#> 
#> [[2]]$c
#> [1] 7
#> 
#> [[2]]$d
#> [1] 9

Создано в 2019 году-04-23 с помощью Представить пакет (v0.2.1)

Первый пример OP ( "думать именованный список объектов ggplot и именованный список путей к файламдля сохранения выходных данных каждого. ") может выглядеть так:

  list(paths, plots) # or list(filename = paths, plot = plots) to match args of ggsave
  transpose() %>%
  walk(lift(ggsave))

Второй пример OP может быть:

list(evens = evens, odds = odds) %>% # or tibble::lst(evens, odds) but lst() is in the questioning stage
  transpose() %>% 
  map(lift(paste)) # or map(paste, collapse = " ") 
#> $a
#> [1] "2 11"
#> 
#> $b
#> [1] "4 5"
#> 
#> $c
#> [1] "6 7"
#> 
#> $d
#> [1] "8 9"

Создано в 2019-04-23 по Представить пакет (v0.2.1)


примечание: Я не проверял, существует ли проблема Github об этом поведении, низнаю, есть ли вероятность, что это изменится, или получу дополнительный аргумент для более точного контроля.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...