2013-07-24 11 views
8

Recentemente, ho creato un oggetto factor=1 nel mio spazio di lavoro, non sapendo che esiste una funzione factor nel pacchetto base.Strano comportamento ambientale in plyr parallelo

Quello che ho intenzione di fare era di utilizzare la variabile factor all'interno di un ciclo parallelo, per esempio,

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

Questo, tuttavia, genera un errore che mi ha portato così il tempo per capire. Come sembra, plyr crea l'oggetto factor nel suo ambiente exportEnv, ma utilizza base::factor invece dell'oggetto fornito dall'utente. Vedere il seguente esempio

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) 

Se si controlla l'uscita del llply, vediamo che la linea factor_in_exportenv=get("factor",envir=export_env) non restituisce 1 (corrispondente all'oggetto fornito dall'utente), ma la definizione della funzione di base::factor.

Domanda 1) Come posso capire questo comportamento? Mi sarei aspettato che l'uscita fosse 1.

Domanda 2) C'è un modo per ottenere un avviso da R se assegno un nuovo valore a un oggetto che era già definito in un altro pacchetto (come nel mio caso factor)?

risposta

0

Innanzitutto, dovrei notare che l'errore scompare se si utilizza un altro nome di variabile che non viene utilizzato in base - ad esempio, se si utilizza a anziché factor. Ciò indica chiaramente che llply trova base::factor (una funzione) prima di factor (variabile con valore 1) lungo il suo percorso di ricerca. Ho cercato di replicare questo problema con una versione semplificata di llply, cioè,

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 utilizza una funzione di supporto ricorsiva (allEnv) che scorre tutti gli ambienti racchiudono. Esso restituisce un vettore con tutti i nomi ambiente

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

E 'interessante il fatto che la funzione semplificata in realtà funziona come previsto (cioè, dà 1 e 2 come risultati)

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 

Così l'unica differenza significativa di llply_simple con rispetto alla funzione completa plyr::llply è che quest'ultimo appartiene a un pacchetto. Proviamo a spostare llply_simple in un pacchetto.

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

E ora tenta di eseguire llplyTest::llply_simple dal nostro nuovo pacchetto 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" 

Tutto ad un tratto si ottiene lo stesso errore come nella mia domanda iniziale dal 2013. Quindi il problema è chiaramente collegato a chiamare la funzione da un pacchetto. Diamo un'occhiata all'output di allEnv: fondamentalmente ci dà la sequenza di ambienti che llpy_simple e llplyTest::llpy_simple usano per cercare le variabili che dovrebbero essere esportate. In realtà è foreach che fa l'esportazione e se uno è interessato a vedere il motivo per cui foreach inizia davvero con l'ambiente che abbiamo chiamato llply_simple_body, guardare il codice sorgente del foreach::%dopar%, foreach:::getDoPar e foreach:::.foreachGlobals$fun e seguire il percorso dell'argomento envir.

Ora possiamo vedere chiaramente che la versione non-package ha una sequenza di ricerca diversa da llplyTest::llpy_simple e che la versione del pacchetto troverà prima factor in base!

1

La funzione llply chiama "foreach" sotto il cofano. Foreach utilizza "parant.frame()" per determinare l'ambiente da valutare. Qual è il parant.frame nel caso di llply? È l'ambiente funzionale di llply, che non ha un fattore definito.

Invece di usare llply, perché non utilizzare foreach direttamente?

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

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

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

Nota, non è nemmeno necessario il parametro .export, poiché in questo caso lo fa automaticamente.

+0

Grazie per l'input. Il problema qui non è tanto l'ambiente di valutazione, quanto l'ambiente 'foreach' utilizza per trovare le variabili che dovrebbero essere esportate. Il problema scompare se si usa un nome di variabile che non è usato in 'base', diciamo' a' invece di 'factor'. So che posso usare direttamente 'foreach' (come hai fatto tu e lo faccio quasi sempre al giorno d'oggi) e che l'errore poi scompare. Ma nel 2013 ero un utente 'plyr' e questo errore mi ha lasciato perplesso. Quindi, volevo risolvere questo per curiosità. – cryo111