并行plyr中环境行为异常

8

最近,我在我的工作区创建了一个对象factor=1,并不知道base包中有一个factor函数。

我的本意是在并行循环中使用变量factor,例如:

library(plyr)
library(foreach)
library(doParallel)

workers <- makeCluster(2)
registerDoParallel(workers,cores=2)

factor=1

llply(
  as.list(1:2),
  function(x) factor*x,
  .parallel = TRUE,
  .paropts=list(.export=c("factor"))
     )

然而,这会导致一个错误,花费了我很多时间才理解。看起来,plyr 在其环境exportEnv中创建了对象factor,但是使用了用户提供的对象之外的base::factor。请参考以下示例。
llply(
  as.list(1:2),
  function(x) {
    function_env=environment();
    global_env=parent.env(function_env);
    export_env=parent.env(global_env);
    list(
      function_env=function_env,
      global_env=global_env,
      export_env=export_env,
      objects_in_exportenv=unlist(ls(envir=export_env)),
      factor_found_in_envs=find("factor"),
      factor_in_exportenv=get("factor",envir=export_env)
      )
    },
  .parallel = TRUE,
  .paropts=list(.export=c("factor"))
  )

stopCluster(workers)

如果我们检查llply的输出,我们会发现这行代码factor_in_exportenv=get("factor",envir=export_env)并没有返回用户提供对象所对应的1,而是base::factor函数定义。
问题1)我该如何理解这种行为?我本来期望输出结果是1
问题2)如果我给已经在另一个包中定义的对象(例如我的情况中的factor)赋新值,是否有一种方法可以让R发出警告?
2个回答

1

llply函数在内部调用"foreach"。Foreach使用"parent.frame()"来确定要评估的环境。在llply的情况下,parant.frame是什么?它是llply的函数环境,其中没有定义因子。

为什么不直接使用foreach而不是llply?

library(plyr)
library(foreach)
library(doParallel)

workers <- makeCluster(2)
registerDoParallel(workers,cores=2)

factor=1
foreach(x=1:2) %dopar% {factor*x}

注意,在这种情况下,您甚至不需要使用.export参数,因为它会自动执行。

感谢您的输入。这里的问题不是评估环境,而是foreach用于查找应该导出的变量的环境。如果使用一个在base中没有使用过的变量名,比如说a而不是factor,问题就会消失。我知道我可以直接使用foreach(就像你做的那样,而我现在也大多数时候都这么做),然后错误就会消失。但是在2013年,我是一个重度的plyr用户,这个错误让我非常困惑。所以,出于好奇心,我想解决这个问题。 - cryo111

0

首先,我应该指出,如果使用另一个在`base`中未使用的变量名称(例如,如果我们使用`a`而不是`factor`),错误将消失。这表明`llply`沿其搜索路径找到了`base::factor`(一个函数)先于具有值1的变量`factor`。我已尝试使用`llply`的简化版本来复制此问题,即:

library(plyr)
library(foreach)
library(doParallel)

workers <- makeCluster(2)
registerDoParallel(workers,cores=2)

factor=1

llply_simple=function(.x,.fun,.paropts) {
  #give current environment a name
  tmpEnv=environment()
  attr(tmpEnv,"name")="llply_simple_body"
  #print all enclosing envirs of llply_simple_body (see def of allEnv below)
  print(allEnv(tmpEnv))
  cat("------\nResults:\n")
  do.ply=function(i) {
    .fun(i)
  }
  fe_call <- as.call(c(list(quote(foreach::foreach), i = .x), .paropts))
  fe <- eval(fe_call)
  foreach::`%dopar%`(fe, do.ply(i))
}

llply_simple 使用一个递归辅助函数 (allEnv),循环遍历所有封闭环境。它返回一个包含所有环境名称的向量。

allEnv=function(x) {
  if (environmentName(x)=="R_EmptyEnv") {
    return(environmentName(x))
  } else {
    c(environmentName(x),allEnv(parent.env(x)))
  }
}

有趣的是,简化的函数实际上按预期工作(即返回12作为结果)

llply_simple(1:2,function(x) x*factor,list(.export="factor"))
#[1] "llply_simple_body"  "R_GlobalEnv"        "package:doParallel" "package:parallel"  
#[5] "package:iterators"  "package:foreach"    "package:plyr"       "tools:rstudio"     
#[9] "package:stats"      "package:graphics"   "package:grDevices"  "package:utils"     
#[13] "package:datasets"   "package:methods"    "Autoloads"          "base"              
#[17] "R_EmptyEnv"
#--------
#Results:        
#[[1]]
#[1] 1
#
#[[2]]
#[1] 2

因此,llply_simple 与完整的 plyr::llply 函数唯一的显著区别是后者属于一个包。让我们尝试将 llply_simple 移入一个包中。

package.skeleton(list=c("llply_simple","allEnv"),name="llplyTest")
unlink("./llplyTest/DESCRIPTION")
devtools::create_description("./llplyTest",
                             extra=list("devtools.desc.author"='"T <t@t.com>"'))
tmp=readLines("./llplyTest/man/llply_simple.Rd")
tmp[which(grepl("\\\\title",tmp))+1]="Test1"
writeLines(tmp,"./llplyTest/man/llply_simple.Rd")
tmp=readLines("./llplyTest/man/allEnv.Rd")
tmp[which(grepl("\\\\title",tmp))+1]="Test2"
writeLines(tmp,"./llplyTest/man/allEnv.Rd")
devtools::install("./llplyTest")

现在尝试从我们的新包llplyTest执行llplyTest::llply_simple

library(llplyTest)
llplyTest::llply_simple(1:2,function(x) x*factor,list(.export="factor"))
#[1] "llply_simple_body"  "llplyTest"          "imports:llplyTest"  "base"              
#[5] "R_GlobalEnv"        "package:doParallel" "package:parallel"   "package:iterators" 
#[9] "package:foreach"    "package:plyr"       "tools:rstudio"      "package:stats"     
#[13] "package:graphics"   "package:grDevices"  "package:utils"      "package:datasets"  
#[17] "package:methods"    "Autoloads"          "base"               "R_EmptyEnv"
#------
#Results:
#Error in do.ply(i) : 
#  task 1 failed - "non-numeric argument to binary operator"

突然间,我们遇到了与我2013年提出的原始问题相同的错误。因此,这个问题显然与从包中调用函数有关。让我们来看看allEnv的输出:它基本上给出了llpy_simplellplyTest::llpy_simple用于查找应该被导出的变量的环境序列。实际上,是foreach进行导出,如果有人想知道为什么foreach真正从我们命名为llply_simple_body的环境开始,可以查看foreach::%dopar%foreach:::getDoParforeach:::.foreachGlobals$fun的源代码,并跟随envir参数的路径。
现在我们可以清楚地看到非包版本与llplyTest::llpy_simple具有不同的搜索顺序,而包版本将首先在base中找到factor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接