R 中的调用环境计算量大吗?

Is calling environments in R computationally expensive?

我有一个包含许多输入的主用户定义函数。 master 函数调用一个用户定义的函数,该函数调用另一个函数,后者又调用另一个函数,依此类推,每次都使用较小的输入子集。我想出了两种将输入传递给较低级别​​函数的方法:

  1. 手动,
  2. 让每个较低级别的函数从主函数获取输入。

解决方案 1) 需要大量输入,我怀疑更有经验的程序员不会这样做。解决方案 2) 看起来更整洁,但 运行 需要更长的时间。所以我有两个问题:A)为什么解决方案 2)需要更多时间? B) 是否有比这两个更好的解决方案来减少程序员的手动工作并且计算效率高?这种编程场景在我的生物学研究中以及在编写统计方法时遇到过,所以我认为这是其他人已经解决的常见问题。

我在下面提供了两个解决方案的简单示例(添加 5 个数字)以及时间安排。

# Solution 1)
f0 <- function(a0,a1,a2,a3,a4){
  val <- a0 + f1(a1=a1,a2=a2,a3=a3,a4=a4)
  return(val)
}

f1 <- function(a1,a2,a3,a4){
  val <- a1 + f2(a2=a2,a3=a3,a4=a4)
  return(val)
}

f2 <- function(a2,a3,a4){
  val <- a2 + f3(a3=a3,a4=a4)
  return(val)
}

f3 <- function(a3,a4){
  val <- a3 + f4(a4=a4)
  return(val)
}

f4 <- function(a4){
  val <- a4
  return(val)
}

# Solution 2)

g0 <- function(a0,a1,a2,a3,a4){
  vars <- list('a0','a1','a2','a3','a4')
  env <<- environment()
  val <- a0 + g1()
  return(val)
}

g1 <- function(){
  for (i in get('vars',env)){assign(i,get(i,env),environment())}
  val <- a1 + g2()
  return(val)
}

g2 <- function(){
  for (i in get('vars',env)){assign(i,get(i,env),environment())}
  val <- a2 + g3()
  return(val)
}

g3 <- function(){
  for (i in get('vars',env)){assign(i,get(i,env),environment())}
  val <- a3 + g4()
  return(val)
}

g4 <- function(){
  for (i in get('vars',env)){assign(i,get(i,env),environment())}
  val <- a4
  return(val)
}

# Timing
t0 <- Sys.time()
replicate(1e4, f0(1,2,3,4,5))
t1 <- Sys.time()

tt0 <- Sys.time()
replicate(1e4, g0(1,2,3,4,5))
tt1 <- Sys.time()

# Time: Solution 1)
> t1-t0
Time difference of 0.2921922 secs

# Time: Solution 2)
> tt1-tt0
Time difference of 0.953675 secs

一般来说,解决方案 1 可能更容易维护,因为每个函数都清楚它应该做什么。以后理解你的代码所花的时间会更少。

通用的"best"解决方案很困难;在这种情况下,它只是简单的 a1 + a2 + a3 + a4 + a5,尽管真正的功能是什么以及它们如何相互作用将极大地影响您的解决方案。

至于为什么解决方案 2 需要这么长时间,它不仅仅是在环境中查找变量,您还进行了大量的获取和分配。

我也不认为函数正在做你认为它正在做的事情,因为 vars 没有存储在环境 env.

您可以考虑像这样将变量存储在全局环境中:

a1 <- 1
a2 <- 2
a3 <- 4
a4 <- 8


h1 <- function(){
  a1 + h2()
}

h2 <- function(){
  a2 + h3()
}

h3 <- function(){
  a3 + h4()
}

h4 <- function(){
  a4
}

h1()

您可以传递命名的 list(),甚至可以根据列表创建您自己的 class。这或多或少是大多数模型在 R 中的工作方式:lm 对象是一个大列表,并且有很多函数(predictsummarycoefAICplot 等)使用他们需要的对象的任何部分。

# Solution 4)
h0 <- function(arg_list){
 arg_list$a0 + h1(arg_list)
}

h1 <- function(arg_list){
  arg_list$a1 + h2(arg_list)
}

h2 <- function(arg_list){
  arg_list$a2 + h3(arg_list)
}

h3 <- function(arg_list) {
  arg_list$a3 + h4(arg_list)
}

h4 <- function(arg_list) {
  arg_list$a4
}


h0(list(a0 = 1, a1 = 2, a2 = 3, a3 = 4, a4 = 5))
# [1] 15

这样做的好处是您不必太担心确切的依赖关系。如果 h2 调用 h3 并且你编辑 h3 使用列表的另一部分,你不必也编辑 h2 来传递正确的参数,因为你是传递整个对象。

想象一下,如果您必须调用 summary.lm 时只使用摘要使用的模型片段而不是 summary(my_model),那将是多么烦人 summary(rank = my_model$rank, resid = my_model$residuals, df_resid = my_model$df.residuals, w = my_mod$weights, ...) 等等模型的一半或更多元素!

使用...向后续函数传递参数:

f0 <- function(a0, ...){
  val <- a0 + f1(...)
  return(val)
}

f1 <- function(a1, ...){
  val <- a1 + f2(...)
  return(val)
}

f2 <- function(a2, ...){
  val <- a2 + f3(...)
  return(val)
}

f3 <- function(a3, ...){
  val <- a3 + f4(...)
  return(val)
}

f4 <- function(a4){
  val <- a4
  return(val)
}

f0(1,2,3,4,5)
#[1] 15

关于A):每个函数调用都需要时间。而且我认为 assign 特别不是很快。