使用 purrr 和预定义函数添加多个输出变量
Add multiple output variables using purrr and a predefined function
拿这个简单的数据集和函数(代表更复杂的问题):
x <- data.frame(a = 1:3, b = 2:4)
mult <- function(a,b,n) (a + b) * n
使用 base 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*
函数。
这里有一些有用的讨论 - - 但对于我想象的一个简单问题来说,它似乎过于老套和不灵活。
这是一种可能。
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
不太好,所以我也很想看看替代方案。 unnest
ing list
列和 spread
ing 到新列中产生了很多过剩。
这是另一种可能性,使用 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
我发现的最佳方法(仍然不是很优雅)是通过管道输入 bind_cols
。为了让 pmap_dfr
正常工作,该函数应该 return 一个命名列表(可能是也可能不是数据框):
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
上调用 returned 函数,该迭代的 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)))
但前者的优点是它return是一个可以partial
应用在x
上的函数,所以它只剩下一个n
参数,因此不需要对 x
的显式引用,因此管道更好:
x %>% bind_cols(map_dfc(1:2, partial(lift(mult), .)))
都是return一样的东西。如果您愿意,可以在事后使用 %>% set_names(~sub('^V(\d+)$', 'new\1', .x))
修复名称。
为了模仿 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 代码相比,这看起来很难看。但是我想不出更好的办法。
拿这个简单的数据集和函数(代表更复杂的问题):
x <- data.frame(a = 1:3, b = 2:4)
mult <- function(a,b,n) (a + b) * n
使用 base 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*
函数。
这里有一些有用的讨论 -
这是一种可能。
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
不太好,所以我也很想看看替代方案。 unnest
ing list
列和 spread
ing 到新列中产生了很多过剩。
这是另一种可能性,使用 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
我发现的最佳方法(仍然不是很优雅)是通过管道输入 bind_cols
。为了让 pmap_dfr
正常工作,该函数应该 return 一个命名列表(可能是也可能不是数据框):
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
上调用 returned 函数,该迭代的 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)))
但前者的优点是它return是一个可以partial
应用在x
上的函数,所以它只剩下一个n
参数,因此不需要对 x
的显式引用,因此管道更好:
x %>% bind_cols(map_dfc(1:2, partial(lift(mult), .)))
都是return一样的东西。如果您愿意,可以在事后使用 %>% set_names(~sub('^V(\d+)$', 'new\1', .x))
修复名称。
为了模仿 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 代码相比,这看起来很难看。但是我想不出更好的办法。