我想在函数中使用plyr
包的并行功能。
我原以为导出在函数体内创建的对象的正确方法(在本例中,对象是df_2
)如下所示
# rm(list=ls())
library(plyr)
library(doParallel)
workers=makeCluster(2)
registerDoParallel(workers,core=2)
plyr_test=function() {
df_1=data.frame(type=c("a","b"),x=1:2)
df_2=data.frame(type=c("a","b"),x=3:4)
#export df_2 via .paropts
ddply(df_1,"type",.parallel=TRUE,.paropts=list(.export="df_2"),.fun=function(y) {
merge(y,df_2,all=FALSE,by="type")
})
}
plyr_test()
stopCluster(workers)
然而,这会引发错误
Error in e$fun(obj, substitute(ex), parent.frame(), e$data) :
unable to find variable "df_2"
所以我做了一些研究,发现如果我手动导出df_2
workers=makeCluster(2)
registerDoParallel(workers,core=2)
plyr_test_2=function() {
df_1=data.frame(type=c("a","b"),x=1:2)
df_2=data.frame(type=c("a","b"),x=3:4)
#manually export df_2
clusterExport(cl=workers,varlist=list("df_2"),envir=environment())
ddply(df_1,"type",.parallel=TRUE,.fun=function(y) {
merge(y,df_2,all=FALSE,by="type")
})
}
plyr_test_2()
stopCluster(workers)
它给出了正确的结果
type x.x x.y
1 a 1 3
2 b 2 4
但我也发现以下代码有效
workers=makeCluster(2)
registerDoParallel(workers,core=2)
plyr_test_3=function() {
df_1=data.frame(type=c("a","b"),x=1:2)
df_2=data.frame(type=c("a","b"),x=3:4)
#no export at all!
ddply(df_1,"type",.parallel=TRUE,.fun=function(y) {
merge(y,df_2,all=FALSE,by="type")
})
}
plyr_test_3()
stopCluster(workers)
plyr_test_3()
也会给出正确的结果,但我不明白为什么。我原以为我必须导出df_2
...
我的问题是:在函数中处理并行*ply
的正确方法是什么?显然,plyr_test()
是不正确的。我不知何故感觉plyr_test_2()
中的手动导出是无用的。但我也认为plyr_test_3()
是一种糟糕的编码风格。有人可以详细说明吗?谢谢你们!
答案 0 :(得分:1)
看起来像是一个范围问题。
这是我的测试套件"这允许我.export不同的变量或避免在函数内创建df_2。我在函数外部添加和删除虚拟df_2和df_3并进行比较。
library(plyr)
library(doParallel)
workers=makeCluster(2)
registerDoParallel(workers,core=2)
plyr_test=function(exportvar,makedf_2) {
df_1=data.frame(type=c("a","b"),x=1:2)
if(makedf_2){
df_2=data.frame(type=c("a","b"),x=3:4)
}
print(ls())
ddply(df_1,"type",.parallel=TRUE,.paropts=list(.export=exportvar,.verbose = TRUE),.fun=function(y) {
z <- merge(y,df_2,all=FALSE,by="type")
})
}
ls()
rm(df_2,df_3)
plyr_test("df_2",T)
plyr_test("df_2",F)
plyr_test("df_3",T)
plyr_test("df_3",F)
plyr_test(NULL,T) #ok
plyr_test(NULL,F)
df_2='hi'
ls()
plyr_test("df_2",T) #ok
plyr_test("df_2",F)
plyr_test("df_3",T)
plyr_test("df_3",F)
plyr_test(NULL,T) #ok
plyr_test(NULL,F)
df_3 = 'hi'
ls()
plyr_test("df_2",T) #ok
plyr_test("df_2",F)
plyr_test("df_3",T) #ok
plyr_test("df_3",F)
plyr_test(NULL,T) #ok
plyr_test(NULL,F)
rm(df_2)
ls()
plyr_test("df_2",T)
plyr_test("df_2",F)
plyr_test("df_3",T) #ok
plyr_test("df_3",F)
plyr_test(NULL,T) #ok
plyr_test(NULL,F)
我不知道为什么,但.export在函数外部的全局环境中查找df_2,(我在代码中看到了parent.env(),这可能是&#34;更正确&#34 ;而不是全局环境),而计算要求变量与ddply在同一环境中并自动导出它。
在函数外部使用df_2的虚拟变量允许.export工作,而计算使用内部的df_2。
当.export无法在函数外找到变量时,它会输出:
Error in e$fun(obj, substitute(ex), parent.frame(), e$data) :
unable to find variable "df_2"
在函数外部使用df_2虚拟变量,但没有内部函数,.export很好,但ddply输出:
Error in do.ply(i) : task 1 failed - "object 'df_2' not found"
可能因为这是一个小例子或者可能不可并行化,它实际上在一个核心上运行并且无需导出任何东西。如果没有.export,一个更大的例子可能会失败,但其他人可以试试。
答案 1 :(得分:1)
plyr_test
的问题是df_2
中定义的plyr_test
无法从doParallel
包中访问,因此在尝试导出时失败{ {1}}。这是一个范围问题。 df_2
可以避免此问题,因为它不会尝试使用plyr_test2
选项,但正如您所猜测的那样,不需要调用.export
。
clusterExport
和plyr_test2
成功的原因是plyr_test3
与通过{{1}传递给df_2
函数的匿名函数一起序列化参数。实际上,ddply
和.fun
都与匿名函数一起序列化,因为该函数是在df_1
和df_2
中定义的。在这种情况下包含plyr_test2
会很有帮助,但包含plyr_test3
是不必要的,可能会影响您的表现。
只要在匿名函数的环境中捕获df_2
,就不会使用df_1
的其他值,无论您导出什么。除非您可以阻止它被捕获,否则使用df_2
或df_2
导出它是没有意义的,因为将使用捕获的值。您只能通过尝试将其导出给工作人员来解决问题(正如您对.export
所做的那样)。
请注意,在这种情况下,foreach不会自动导出clusterExport
,因为它无法分析匿名函数的主体以查看引用了哪些符号。如果您在不使用匿名函数的情况下直接调用foreach,它将会看到引用并自动导出它,因此无需使用.export
显式导出它。
您可以通过在将df_2
传递给.export
之前修改它的环境来阻止plyr_test
的环境与匿名函数一起序列化:
ddply
plyr_test=function() {
df_1=data.frame(type=c("a","b"),x=1:2)
df_2=data.frame(type=c("a","b"),x=3:4)
clusterExport(cl=workers,varlist=list("df_2"),envir=environment())
fun=function(y) merge(y, df_2, all=FALSE, by="type")
environment(fun)=globalenv()
ddply(df_1,"type",.parallel=TRUE,.fun=fun)
}
包的一个优点是它不鼓励你在另一个可能意外捕获一堆变量的函数内部创建一个函数。
此问题告诉我foreach
应该包含一个名为foreach
的选项,类似于.exportenv
clusterExport
选项。这对envir
非常有帮助,因为它允许使用plyr
正确导出df_2
。但是,除非从.export
函数中删除包含df_2
的环境,否则仍不会使用该导出值。
答案 2 :(得分:0)
感谢@ARobertson的帮助!
当在函数体之外定义虚拟对象plyr_test("df_2",T)
时df_2
工作非常有趣。
看起来ddply
最终会调用llply
,而foreach(...) %dopar% {...}
会调用foreach
。
我还尝试使用foreach
重现问题,但library(plyr)
library(doParallel)
workers=makeCluster(2)
registerDoParallel(workers,core=2)
foreach_test=function() {
df_1=data.frame(type=c("a","b"),x=1:2)
df_2=data.frame(type=c("a","b"),x=3:4)
foreach(y=split(df_1,df_1$type),.combine="rbind",.export="df_2") %dopar% {
#also print process ID to be sure that we really use different R script processes
cbind(merge(y,df_2,all=FALSE,by="type"),Sys.getpid())
}
}
foreach_test()
stopCluster(workers)
工作正常。
Warning message:
In e$fun(obj, substitute(ex), parent.frame(), e$data) :
already exporting variable(s): df_2
它会抛出警告
type x.x x.y Sys.getpid()
1 a 1 3 216
2 b 2 4 1336
但它返回正确的结果
foreach
因此,df_2
似乎会自动导出foreach
。实际上,.export="df_2"
vignette表示
...%dopar%函数注意到那些变量被引用,并且 它们是在当前环境中定义的。在这种情况下 %dopar%会自动将它们导出到并行执行 工人一次,并将它们用于所有的表达式评估 那个预先执行......
因此我们可以省略library(plyr)
library(doParallel)
workers=makeCluster(2)
registerDoParallel(workers,core=2)
foreach_test_2=function() {
df_1=data.frame(type=c("a","b"),x=1:2)
df_2=data.frame(type=c("a","b"),x=3:4)
foreach(y=split(df_1,df_1$type),.combine="rbind") %dopar% {
#also print process ID to be sure that we really use different R script processes
cbind(merge(y,df_2,all=FALSE,by="type"),Sys.getpid())
}
}
foreach_test_2()
stopCluster(workers)
并使用
foreach
代替。这会在没有警告的情况下进行评估。
ARobertson的虚拟变量示例以及plyr_test_3()
正常工作的事实让我现在认为* ply如何处理环境存在问题。
我的结论是:
函数foreach_test_2()
和df_2
(未明确导出ddply
)运行时没有错误并给出相同的结果。因此,parallel=TRUE
与df_2
基本相同。但是使用更加“冗长”的编码样式(即明确导出plyr_test()
),例如在foreach(...) %dopar% {...}
中会引发错误,而{{1}}只会发出警告。