最近,我在工作区中创建了一个对象factor=1
,不知道factor
包中有一个函数base
。
我打算在并行循环中使用变量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
在其environemt factor
中创建了对象exportEnv
,但使用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
(对应于用户提供的对象),但是{{1}的函数定义}}
问题1)我如何理解这种行为?我原以为输出为base::factor
。
问题2)如果我为一个已在另一个包中定义的对象(例如我的1
中)定义了一个新值,是否有办法从R
发出警告?
答案 0 :(得分:1)
llply函数在引擎盖下调用“foreach”。 Foreach使用“parant.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参数,因为在这种情况下它会自动执行。
答案 1 :(得分: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)))
}
}
有趣的是,简化函数实际上按预期工作(即,1
和2
作为结果)
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::llply_simple
llplyTest
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_simple
和llplyTest::llpy_simple
用来查找应该导出的变量的环境序列。实际上它是foreach
进行导出的,如果有人想知道为什么foreach
真正从我们命名为llply_simple_body
的环境开始,请查看{{{{}}的源代码。 1}},foreach::%dopar%
和foreach:::getDoPar
并遵循foreach:::.foreachGlobals$fun
参数的路径。
我们现在可以清楚地看到非包版本的搜索顺序与envir
不同,并且包版本会先在llplyTest::llpy_simple
中找到factor
!