【问题标题】:do.call in combination with "::"do.call 结合“::”
【发布时间】:2023-04-03 05:41:01
【问题描述】:

我大量使用“do.call”来生成函数调用。例如:

myfun <- "rnorm";
myargs <- list(n=10, mean=5);
do.call(myfun, myargs);

但是,有时我想从某个包中显式调用一个函数。类似于例如stats::rnorm(n=10, mean=5)。有什么方法可以使用 do.call,或者创建一个行为类似于 do.call 的函数来让它工作:

myfun <- "stats::rnorm";
myargs <- list(n=10, mean=5);
do.call(myfun, myargs);

【问题讨论】:

  • do.call(stats::rnorm, myargs) 怎么样?
  • @kohske - 那么stats::rnorm(n=10, mean=5) 似乎更简单:-)
  • 但在这种情况下你不能使用 list 作为它的参数 :-(

标签: r


【解决方案1】:

没有名为“stats::rnorm”的函数。您必须在“stats”命名空间中找到rnorm 函数:

myfun <- get("rnorm", asNamespace("stats"))
myargs <- list(n=10, mean=5);
do.call(myfun, myargs);

现在您当然也可以从“stats::rnorm”之类的名称出发,并将其拆分为命名空间部分和函数名称:

funname <- "stats::rnorm"
fn <- strsplit(funname, "::")[[1]]
myfun <- if (length(fn)==1) fn[[1]] else get(fn[[2]], asNamespace(fn[[1]]))
myargs <- list(n=10, mean=5);
do.call(myfun, myargs);

更新我只是想证明这种方法比 @Jeroen 的方法快 2.5 倍...

do.call.tommy <- function(what, args, ...) {
  if(is.character(what)){
    fn <- strsplit(what, "::")[[1]]
    what <- if(length(fn)==1) {
        get(fn[[1]], envir=parent.frame(), mode="function")
    } else {
        get(fn[[2]], envir=asNamespace(fn[[1]]), mode="function")
    }
  }

  do.call(what, as.list(args), ...)
}

# Test it
do.call.tommy(runif, 10)
f1 <- function(FUN) do.call.tommy(FUN, list(5))
f2 <- function() { myfun<-function(x) x; do.call.tommy(myfun, list(5)) }
f1(runif)
f1("stats::runif")
f2()

# Test the performance...    
system.time(for(i in 1:1e4) do.call.jeroen("stats::runif", list(n=1, max=50))) # 1.07 secs
system.time(for(i in 1:1e4) do.call.tommy("stats::runif", list(n=1, max=50)))  # 0.42 secs

【讨论】:

    【解决方案2】:

    你可以去掉引号:那将是函数本身,而不是它的名字。

    myfun <- stats::rnorm
    myargs <- list(n=10, mean=5)
    do.call(myfun, myargs)
    

    【讨论】:

      【解决方案3】:

      感谢您的回复。我想我会这样做:

      do.call.jeroen <- function(what, args, ...){
        if(is.function(what)){
          what <- deparse(as.list(match.call())$what);
        }
        myfuncall <- parse(text=what)[[1]];
        mycall <- as.call(c(list(myfuncall), args));
        eval(mycall, ...);
      }
      

      这似乎是对do.call 的一个很好的概括,因此我仍然可以为what 参数传递一个字符串,但它巧妙地模拟了stats::rnorm(n=10, mean=5) 调用。

      myfun1 <- "rnorm";
      myfun2 <- "stats::rnorm";
      myargs <- list(n=10, mean=5);
      do.call.jeroen(myfun1, myargs);
      do.call.jeroen(myfun2, myargs);
      do.call.jeroen(rnorm, myargs);
      do.call.jeroen(stats::rnorm, myargs);
      

      这样做的好处是,如果我正在调用的函数使用 match.call() 将调用存储在某处,它将保留实际的函数名称。例如:

      do.call.jeroen("stats::glm", list(formula=speed~dist, data=as.name('cars')))

      Call:  stats::glm(formula = speed ~ dist, data = cars)
      
      Coefficients:
      (Intercept)         dist  
           8.2839       0.1656  
      
      Degrees of Freedom: 49 Total (i.e. Null);  48 Residual
      Null Deviance:      1370 
      Residual Deviance: 478  AIC: 260.8 
      

      【讨论】:

      • 我不太喜欢你求助于deparseparseeval 来解决这个问题。一方面,它不能处理f&lt;-function(x) do.call.jeroen(x, list(n=10)); f(runif),而且它的速度是调用do.call 的两倍多……我用另一种方法更新了我的答案。
      • 谢谢。我也稍微更新了我的回复。我喜欢do.call.jeroen 的一件事是保留了原始函数名称,而不是用匿名函数替换它。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-11-15
      • 2023-01-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多