"vectorize" 具有不同参数长度的函数的最快方法
Fastest way to "vectorize" a function with differing argument lengths
采用以下简单函数:
fun <- function(a, b, c, d, e) {
stopifnot("Input you provide must be equal length." = length(a) == length(b) && length(b) == length(c) && length(c) == length(d) && length(d) == length(e))
result <- (a + b / c + d) / sqrt(e)
result2 <- a/result
return(data.frame(result = result, result2 = result2, a = a, b = b, c = c, d = d, e = e))
}
现在,如果我想映射所有输入值组合的查找 table,我可以执行以下操作,例如,使用 purrr
泛函:
library(purrr)
df <- expand.grid(a = 1:1000, b = c(1, 2, 3, 4, 5), c = 7, d = 3, e = 5)
out <- pmap_df(d, fun)
然而,即使对于一个较大向量和一个较小向量的相对简单的情况(在我的应用案例中,这将是最常见的情况),这也非常慢。
Unit: seconds
min lq mean median uq max neval
2.235245 2.235245 2.235245 2.235245 2.235245 2.235245 1
如何加快速度,尤其是对于上面描述的简单案例?当然,随着 df
越来越大,事情会变慢。
我不能说我的解决方案是最快的,但确实更快。您可以试试下面的代码
do.call(fun, df)
和基准测试
df <- expand.grid(a = 1:1000, b = c(1, 2, 3, 4, 5), c = 7, d = 3, e = 5)
f_Rob <- function() pmap_df(df, function(a, b, c, d, e) fun(a = a, b = b, c = c, d = d, e = e))
f_TIC <- function() do.call(fun, df)
microbenchmark(
f_Rob(),
f_TIC(),
unit = "relative",
check = "equivalent",
times = 10
)
你会看到
Unit: relative
expr min lq mean median uq max neval
f_Rob() 1074.886 1049.034 441.6319 854.2739 620.4029 92.29739 10
f_TIC() 1.000 1.000 1.0000 1.0000 1.0000 1.00000 10
我认为最直接的 tidyverse
等同于使用 rlang
中的 exec()
。
这并不比 do.call()
快,除了高级案例之外我没有看到明显的优势,但它就是。
library(rlang)
df <- expand.grid(a = 1:1000, b = c(1, 2, 3, 4, 5), c = 7, d = 3, e = 5)
f_TIC <- function() do.call(fun, df)
f_rlang <- function() exec(fun, !!!df)
microbenchmark::microbenchmark(
f_rlang(),
f_TIC(),
unit = "relative",
check = "equivalent",
times = 100
)
大约慢 15%。
Unit: relative
expr min lq mean median uq max neval
f_rlang() 1.158271 1.149351 1.156371 1.145274 1.143179 1.229871 100
f_TIC() 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 100
采用以下简单函数:
fun <- function(a, b, c, d, e) {
stopifnot("Input you provide must be equal length." = length(a) == length(b) && length(b) == length(c) && length(c) == length(d) && length(d) == length(e))
result <- (a + b / c + d) / sqrt(e)
result2 <- a/result
return(data.frame(result = result, result2 = result2, a = a, b = b, c = c, d = d, e = e))
}
现在,如果我想映射所有输入值组合的查找 table,我可以执行以下操作,例如,使用 purrr
泛函:
library(purrr)
df <- expand.grid(a = 1:1000, b = c(1, 2, 3, 4, 5), c = 7, d = 3, e = 5)
out <- pmap_df(d, fun)
然而,即使对于一个较大向量和一个较小向量的相对简单的情况(在我的应用案例中,这将是最常见的情况),这也非常慢。
Unit: seconds
min lq mean median uq max neval
2.235245 2.235245 2.235245 2.235245 2.235245 2.235245 1
如何加快速度,尤其是对于上面描述的简单案例?当然,随着 df
越来越大,事情会变慢。
我不能说我的解决方案是最快的,但确实更快。您可以试试下面的代码
do.call(fun, df)
和基准测试
df <- expand.grid(a = 1:1000, b = c(1, 2, 3, 4, 5), c = 7, d = 3, e = 5)
f_Rob <- function() pmap_df(df, function(a, b, c, d, e) fun(a = a, b = b, c = c, d = d, e = e))
f_TIC <- function() do.call(fun, df)
microbenchmark(
f_Rob(),
f_TIC(),
unit = "relative",
check = "equivalent",
times = 10
)
你会看到
Unit: relative
expr min lq mean median uq max neval
f_Rob() 1074.886 1049.034 441.6319 854.2739 620.4029 92.29739 10
f_TIC() 1.000 1.000 1.0000 1.0000 1.0000 1.00000 10
我认为最直接的 tidyverse
等同于使用 rlang
中的 exec()
。
这并不比 do.call()
快,除了高级案例之外我没有看到明显的优势,但它就是。
library(rlang)
df <- expand.grid(a = 1:1000, b = c(1, 2, 3, 4, 5), c = 7, d = 3, e = 5)
f_TIC <- function() do.call(fun, df)
f_rlang <- function() exec(fun, !!!df)
microbenchmark::microbenchmark(
f_rlang(),
f_TIC(),
unit = "relative",
check = "equivalent",
times = 100
)
大约慢 15%。
Unit: relative
expr min lq mean median uq max neval
f_rlang() 1.158271 1.149351 1.156371 1.145274 1.143179 1.229871 100
f_TIC() 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 100