更安静的purrr :: map2用于名称乱序的列表

问题描述 投票:9回答:4

这是一个我之前在我的代码中写过故障的问题,但我想知道是否有一些我错过的更直接的东西。

我有时会有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返回一个与第一个列表名称相同的列表,并按位置迭代。因此bd项目在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的清洁度结合起来。

r purrr
4个回答
3
投票

只需编写一个辅助函数来清理它

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

基本上purrr中没有原语会自动为你做这件事。当这很容易做到时,似乎没有多大意义。


4
投票

我们可以做的

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名称都是无序的,则循环通过其中一个sortnamesed 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=" ")

4
投票

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"

2
投票

transpose()似乎正在这样做(按名称匹配)。虽然 它没有记录 (编辑:.names arg的解释给出了上下文,并且有例子),并且文档在某些地方似乎不准确(purrr v.0.3.1)。

它被称为转置,因为x[[1]][[2]]相当于transpose(x)[[2]][[1]]

^似乎是不准确的,因为在这个例子中,list(evens, odds)[[2]][[4]]5transpose(list(evens, odds))[[4]][[2]]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

reprex package创建于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"

reprex package创建于2019-04-23(v0.2.1)


注意:我没有检查是否存在关于此行为的Github问题,也不知道这是否有可能会发生变化,或者获得额外的参数以进行更精细的控制。

© www.soinside.com 2019 - 2024. All rights reserved.