首先,我应该指出,如果使用另一个在`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) {
tmpEnv=environment()
attr(tmpEnv,"name")="llply_simple_body"
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
执行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_simple
和
llplyTest::llpy_simple
用于查找应该被导出的变量的环境序列。实际上,是
foreach
进行导出,如果有人想知道为什么
foreach
真正从我们命名为
llply_simple_body
的环境开始,可以查看
foreach::%dopar%
、
foreach:::getDoPar
和
foreach:::.foreachGlobals$fun
的源代码,并跟随
envir
参数的路径。
现在我们可以清楚地看到非包版本与
llplyTest::llpy_simple
具有不同的搜索顺序,而包版本将首先在
base
中找到
factor
!
foreach
用于查找应该导出的变量的环境。如果使用一个在base
中没有使用过的变量名,比如说a
而不是factor
,问题就会消失。我知道我可以直接使用foreach
(就像你做的那样,而我现在也大多数时候都这么做),然后错误就会消失。但是在2013年,我是一个重度的plyr
用户,这个错误让我非常困惑。所以,出于好奇心,我想解决这个问题。 - cryo111