【问题标题】:Add multiple output variables using purrr and a predefined function使用 purrr 和预定义函数添加多个输出变量
【发布时间】:2019-01-29 09:08:32
【问题描述】:

拿这个简单的数据集和函数(代表更复杂的问题):

x <- data.frame(a = 1:3, b = 2:4)
mult <- function(a,b,n) (a + b) * n

使用基本 R 的 Map 我可以这样做以矢量化方式添加 2 个新列:

ns <- 1:2
x[paste0("new",seq_along(ns))] <- Map(mult, x["a"], x["b"], n=ns)
x
#  a b new1 new2
#1 1 2    3    6
#2 2 3    5   10
#3 3 4    7   14

purrr尝试通过pmap得到一个列表输出:

library(purrr)
library(dplyr)
x %>% select(a,b) %>% pmap(mult, n=1:2)
#[[1]]
#[1] 3 6
#
#[[2]]
#[1]  5 10
#
#[[3]]
#[1]  7 14

我从这里尝试使用 pmap_dfr 等似乎都在尝试将其映射回新列时出错。

我如何最终制作 2 个与我当前的 "new1"/"new2" 匹配的变量?我确定有一个简单的咒语,但我显然忽略了它或使用了错误的*map* 函数。

这里有一些有用的讨论 - How to use map from purrr with dplyr::mutate to create multiple new columns based on column pairs - 但对于我想象的一个简单问题来说,它似乎过于老套和不灵活。

【问题讨论】:

  • 这很难看,但我不认为你在找这个。 x %&gt;% select(a,b) %&gt;% pmap(mult, n=1:2) %&gt;% bind_cols() %&gt;% t()
  • 这个呢:x[paste0("new",seq_along(ns))] &lt;- pmap(list(x['a'], x['b'], ns), mult)?
  • @mt1022 - 这是一个非常好的观点,pmap/Map 直接类似。唯一的问题是我试图将它融入其他人的管道代码。有没有办法像(非工作)x %&gt;% pmap(list(a,b,ns), mult) 一样将x 输入到pmap - 我在这个阶段仍然迷路。
  • 我想不出更好的方法来将abx 提供给pmap。我得到的最接近的是x %&gt;% {list(.['a'], .['b'], ns)} %&gt;% pmap(mult) %&gt;% setNames(paste0('new', seq_along(ns))) %&gt;% cbind(x)
  • @mt1022 - 这还不错。为了完成,可能值得添加作为正式答案。

标签: r function purrr


【解决方案1】:

我发现的最佳方法(仍然不是非常优雅)是输入bind_cols。要让pmap_dfr 正常工作,函数应该返回一个命名列表(可能是也可能不是数据框):

library(tidyverse)

x <- data.frame(a = 1:3, b = 2:4)
mult <- function(a,b,n) as.list(set_names((a + b) * n, paste0('new', n)))

x %>% bind_cols(pmap_dfr(., mult, n = 1:2))
#>   a b new1 new2
#> 1 1 2    3    6
#> 2 2 3    5   10
#> 3 3 4    7   14

为避免更改mult 的定义,可以将其包装在匿名函数中:

mult <- function(a,b,n) (a + b) * n

x %>% bind_cols(pmap_dfr(
    ., 
    ~as.list(set_names(
        mult(...), 
        paste0('new', 1:2)
    )), 
    n = 1:2
))
#>   a b new1 new2
#> 1 1 2    3    6
#> 2 2 3    5   10
#> 3 3 4    7   14

在这种特殊情况下,实际上并不需要遍历行,因为您可以对来自 x 的输入进行矢量化处理,而是遍历 n。优点是通常 n > p,因此迭代次数会[可能]低得多。需要明确的是,这种方法是否可行取决于函数可以接受向量参数的哪些参数。

mult 仍然需要在x 的变量上调用。最简单的方法是显式传递它们:

x %>% bind_cols(map_dfc(1:2, ~mult(x$a, x$b, .x)))
#>   a b V1 V2
#> 1 1 2  3  6
#> 2 2 3  5 10
#> 3 3 4  7 14

...但这失去了pmap 的好处,即命名变量将自动传递给正确的参数。您可以使用purrr::lift 来取回它,这是一个副词,它改变了函数的域,因此它通过将列表包装在do.call 中来接受列表。可以在 x 上调用返回的函数,并且该迭代的 n 的值:

x %>% bind_cols(map_dfc(1:2, ~lift(mult)(x, n = .x)))

这相当于

x %>% bind_cols(map_dfc(1:2, ~invoke(mult, x, n = .x)))

但前者的优点是它返回一个可以partially 应用于x 的函数,因此它只剩下一个n 参数,因此不需要显式引用x 等等管道更好:

x %>% bind_cols(map_dfc(1:2, partial(lift(mult), .)))

所有返回相同的东西。如果您愿意,可以在事后使用%&gt;% set_names(~sub('^V(\\d+)$', 'new\\1', .x)) 修改名称。

【讨论】:

  • 谢谢。很高兴知道我没有忽略一些非常基本的东西。我试着用set_names 捏造,但运气不佳。有点遗憾,mult 函数需要事先编辑。
  • 您可以将 mult 包装在一个匿名函数中:x %&gt;% bind_cols(pmap_dfr(., ~as.list(set_names(mult(...), paste0('new', 1:2))), n = 1:2)),但在某些时候它只是非常不透明的代码。
  • purrrlyrby_rowinvoke_rows 曾经提供执行此类任务的替代方法:x %&gt;% purrrlyr::invoke_rows(mult, ., n = 1:2, .collate = 'cols'),但现在已被弃用。不过,它们确实是很有趣的功能。
  • 我一直在考虑这个问题,如果实际函数足够相似,那么对行进行向量化并迭代n 会更有效,这通常会导致 [可能much] 更少的迭代次数,这里是 2 而不是 3:x %&gt;% bind_cols(map_dfc(1:2, partial(lift(mult), .))) 不过,它确实取决于可以跨行参数向量化的函数。
  • 您想在答案中添加最后一条评论吗?我觉得很有用。
【解决方案2】:

这是一种可能性。

library(purrr)
library(dplyr)
n <- 1:2
x %>%
    mutate(val = pmap(., mult, n = n)) %>%
    unnest() %>%
    mutate(var = rep(paste0("new", n), nrow(.) / length(n))) %>%
    spread(var, val)
#  a b new1 new2
#1 1 2    3    6
#2 2 3    5   10
#3 3 4    7   14

不漂亮,所以我也很想看到替代品。 unnesting list 列和 spreading 到新列中产生了很多过剩。

这是使用pmap_dfc 加上丑陋的as.data.frame(t(...)) 调用的另一种可能性

bind_cols(x, as.data.frame(t(pmap_dfc(x, mult, n = n))))
#  a b V1 V2
#1 1 2  3  6
#2 2 3  5 10
#3 3 4  7 14

样本数据

x <- data.frame(a = 1:3, b = 2:4)
mult <- function(a,b,n) (a + b) * n

【讨论】:

  • 有趣。至少这个答案可以按原样使用mult 函数。我会推迟,看看是否有其他人有一个超级简单的解决方案。
  • @thelatemail 我也觉得必须有一种更简单、更优雅的方式。我玩过两次map 电话(见更新);仍然不优雅(丑陋的as.data.frame(t(...)) 并且名称仍然需要修复)。
  • @thelatemail [更新] 好吧,双重map 调用是不必要的,仍然有丑陋的as.data.frame(t(...))...
【解决方案3】:

为了模仿Map 的输入格式,我们可以这样从purrr 调用pmap

x[paste0("new",seq_along(ns))] <- pmap(list(x['a'], x['b'], ns), mult)

要将其装入管道:

x %>%
    {list(.['a'], .['b'], ns)} %>%
    pmap(mult) %>%
    setNames(paste0('new', seq_along(ns))) %>%
    cbind(x)

#   new1 new2 a b
# 1    3    6 1 2
# 2    5   10 2 3
# 3    7   14 3 4

显然,与简洁的基本 R 代码相比,这看起来很难看。但我想不出更好的办法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-01-04
    • 1970-01-01
    • 2021-05-04
    • 1970-01-01
    • 1970-01-01
    • 2021-11-11
    • 1970-01-01
    • 2013-12-02
    相关资源
    最近更新 更多