【问题标题】:Safer purrr::map2 for lists with names out of order对于名称乱序的列表更安全的 purrr::map2
【发布时间】:2019-09-11 21:25:30
【问题描述】:

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

有时我有 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 列表同名的列表,并按位置进行迭代。因此,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 的清洁度结合起来。

【问题讨论】:

  • 也许使用transpose(lst(odds, evens))之类的东西并在顶部使用标准map?如果这是您的用例,或者将您的路径设置为 ggplot 对象的属性。

标签: r purrr


【解决方案1】:

我们可以的

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 名称都是无序的,则遍历list 之一的sorted names,提取两个元素并连接

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

或为order 创建一个标识符,然后在list 中创建order list 元素并与map2 连接

i1 <- order(names(evens)) # not sure if this should be avoided
map2(evens[i1], odds[i1], str_c, sep=" ")

【讨论】:

  • 我喜欢将一个子集到另一个名称的解决方案。如果您使用order,如果一个列表的名称不在另一个列表中,您将得到欺骗性结果
  • @IceCreamToucan 根据示例有一些what ifs 条件。如果示例更改可能会有点复杂
【解决方案2】:

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

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

基本上purrr 中没有可以自动为您执行此操作的原语。当它这么容易做到时,似乎没有什么意义。

【讨论】:

  • 谢谢,我不知道为什么我经常对编写辅助函数持谨慎态度——不想在 R 脚本中丢失它们。但由于我已经为工作编写了 2 个实用程序包,这可能是最安全、最可持续的方法。
【解决方案3】:

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】:

    transpose() 似乎正在这样做(按名称匹配)。虽然没有记录编辑:.names arg 的解释给出了上下文,并且有一些例子),但文档在某些地方似乎不准确(@987654325 @ 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 (v0.2.1) 于 2019 年 4 月 23 日创建

    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 (v0.2.1) 于 2019 年 4 月 23 日创建


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

    【讨论】:

      【解决方案5】:

      如果列表名称仅部分重叠,则可以使用@MrFlick 答案的以下修改。应用的函数必须忽略NULL参数:

      namedmap2 <- function(.x, .y, .f, ...) {
          set <- unique(c(names(.x), names(.y)))
          lst <- map2(.x[set], .y[set], .f, ...)
          names(lst) <- set
          lst
      }
      

      【讨论】:

        猜你喜欢
        • 2021-02-10
        • 2019-04-03
        • 1970-01-01
        • 2020-09-07
        • 2021-12-16
        • 1970-01-01
        • 1970-01-01
        • 2018-11-16
        • 2022-08-09
        相关资源
        最近更新 更多