2013-07-24 7 views
8

Kürzlich habe ich ein Objekt factor=1 in meinem Arbeitsbereich erstellt, nicht wissend, dass es eine factor Funktion in der base-Paket gibt. Seltsames Umgebungsverhalten in parallel plyr

Was ich tun sollte, war die Variable factor innerhalb einer parallelen Schleife zu verwenden, zum Beispiel

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")) 
    ) 

Dies jedoch führt zu einem Fehler, den ich nahm so Zeit zu verstehen. Wie es scheint, erstellt plyr das Objekt factor in seiner Umgebung exportEnv, aber verwendet base::factor anstelle des vom Benutzer bereitgestellten Objekts. Siehe das folgende Beispiel

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) 

Wenn wir die Ausgabe von llply inspiziert, sehen wir, dass die Linie factor_in_exportenv=get("factor",envir=export_env) nicht 1 zurückkehrt (entsprechend dem vom Benutzer bereitgestellte Objekt), sondern die Funktionsdefinition von base::factor.

Frage 1) Wie kann ich dieses Verhalten verstehen? Ich hätte erwartet, dass die Ausgabe 1 wäre.

Frage 2) Gibt es eine Möglichkeit, eine Warnung von R zu erhalten, wenn ich einem Objekt, das bereits in einem anderen Paket definiert war (z. B. factor), einen neuen Wert zuweise?

Antwort

0

Zuerst sollte ich beachten, dass der Fehler verschwindet, wenn man einen anderen Variablennamen verwendet, der nicht in base verwendet wird - zum Beispiel, wenn wir a anstelle von factor verwenden. Dies zeigt deutlich, dass llplybase::factor (eine Funktion) vor factor (Variable mit Wert 1) entlang seines Suchpfades findet. Ich habe versucht, dieses Problem mit einer vereinfachten Version von llply zu replizieren, das heißt,

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 verwendet eine rekursive Hilfsfunktion (allEnv), die durch alle einschließenden Umgebungen Schleifen. Es gibt einen Vektor mit allen Umwelt Namen

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

Es ist interessant, dass die vereinfachte Funktion tatsächlich wie erwartet funktioniert (dh gibt 1 und 2 als Ergebnisse)

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 

So ist der einzige signifikante Unterschied von llply_simple mit Bezug auf die volle plyr::llply Funktion ist, dass letztere zu einem Paket gehört. Lassen Sie uns versuchen, llply_simple in ein Paket zu verschieben.

package.skeleton(list=c("llply_simple","allEnv"),name="llplyTest") 
unlink("./llplyTest/DESCRIPTION") 
devtools::create_description("./llplyTest", 
          extra=list("devtools.desc.author"='"T <[email protected]>"')) 
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") 

Und nun versuchen llplyTest::llply_simple aus unserem neuen Paket 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" 

Alle von uns den gleichen Fehler von 2013, wie in meiner ursprünglichen Frage kommen plötzlich auszuführen, so die Frage ist eindeutig mit Aufruf die Funktion aus einem Paket. Lassen Sie uns einen Blick auf die Ausgabe von allEnv werfen: Es gibt uns im Grunde die Reihenfolge der Umgebungen, die llpy_simple und llplyTest::llpy_simple verwenden, um nach Variablen zu suchen, die exportiert werden sollen. Eigentlich ist es foreach, dass der ausführende tut und wenn man daran interessiert zu sehen, warum foreach wirklich mit der Umwelt beginnt, die wir llply_simple_body genannt, Blick auf den Quellcode von foreach::%dopar%, foreach:::getDoPar und foreach:::.foreachGlobals$fun und folgen Sie dem Pfad des envir Argument.

Wir können nun deutlich sehen, dass die Nicht-Paketversion eine andere Suchsequenz als llplyTest::llpy_simple hat und dass die Paketversion factor zuerst in base finden wird!

1

Die Illply-Funktion ruft "foreach" unter der Haube auf. Foreach verwendet "parant.frame()", um die zu bewertende Umgebung zu bestimmen. Was ist der parant.frame im Fall von lplly? Es ist die Funktionsumgebung der llply, für die kein Faktor definiert ist.

Anstatt llply zu verwenden, warum nicht foreach direkt verwenden?

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

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

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

Hinweis: Sie benötigen nicht einmal den .export-Parameter, da dies in diesem Fall automatisch geschieht.

+0

Vielen Dank für Ihre Eingabe. Das Problem hier ist nicht so sehr das Umfeld der Bewertung, sondern die Umgebung, in der foreach die Variablen findet, die exportiert werden sollen. Das Problem verschwindet, wenn man einen Variablennamen verwendet, der nicht in "base" verwendet wird, sagen wir "a" statt "faktor". Ich weiß, dass ich 'foreach' direkt verwenden kann (wie du es heute meistens tust) und dass der Fehler dann verschwindet. Aber im Jahr 2013 war ich ein schwerer "plyr" -Benutzer und dieser Fehler verwirrte mich ziemlich. Also wollte ich das aus Neugier lösen. – cryo111