2016-02-04 22 views
9

Sto riscontrando problemi nel refactoring di dplyr in modo da preservare la valutazione non standard. Diciamo che voglio creare una funzione che seleziona e rinomina sempre.Programmazione con dplyr e lazyeval

library(lazyeval) 
library(dplyr) 

df <- data.frame(a = c(1,2,3), f = c(4,5,6), lm = c(7, 8 , 9)) 

select_happy<- function(df, col){ 
    col <- lazy(col) 
    fo <- interp(~x, x=col) 
    select_(df, happy=fo) 
} 

f <- function(){ 
    print('foo') 
} 

select_happy() è scritto in base alla risposta a questo post Refactor R code when library functions use non-standard evaluation. select_happy() funziona su nomi di colonna non definiti o definiti nell'ambiente globale. Tuttavia, si imbatte in problemi quando il nome di una colonna è anche il nome di una funzione in un altro spazio dei nomi.

select_happy(df, a) 
# happy 
# 1  1 
# 2  2 
# 3  3 

select_happy(df, f) 
# happy 
# 1  4 
# 2  5 
# 3  6 

select_happy(df, lm) 
# Error in eval(expr, envir, enclos) (from #4) : object 'datafile' not found 

environment(f) 
# <environment: R_GlobalEnv> 

environment(lm) 
# <environment: namespace:stats> 

Calling lazy() sul fe lm mostra una differenza nell'oggetto pigri, in cui la definizione di funzione per lm sta comparendo nell'oggetto pigri, e per f è proprio il nome della funzione.

lazy(f) 
# <lazy> 
# expr: f 
# env: <environment: R_GlobalEnv> 

lazy(lm) 
# <lazy> 
# expr: function (formula, data, subset, weights, na.action, method = "qr", ... 
# env: <environment: R_GlobalEnv> 

substitute sembra funzionare con lm.

select_happy<- function(df, col){ 
    col <- substitute(col) # <- substitute() instead of lazy() 
    fo <- interp(~x, x=col) 
    select_(df, happy=fo) 
} 

select_happy(df, lm) 
# happy 
# 1  7 
# 2  8 
# 3  9 

Tuttavia, dopo aver letto the vignette on lazyeval sembra che lazy dovrebbe servire come un sostituto superiore per substitute. Inoltre, la normale funzione select funziona perfettamente.

select(df, happy=lm) 
# happy 
# 1  7 
# 2  8 
# 3  9 

mia domanda è come posso scrivere select_happy() in modo che funzioni in tutti i modi che select() fa? Sto attraversando un periodo difficile che gira la testa sulla valutazione dell'ambito e non standard. Più in generale, quale sarebbe una solida strategia per programmare con dplyr che potrebbe evitare questi e altri problemi?

Modifica

Ho provato la soluzione docendo di discimus e ha funzionato grande, ma mi piacerebbe sapere se c'è un modo per utilizzare argomenti, piuttosto che i punti, per la funzione. Penso che sia anche importante poter usare interp() perché potresti voler inserire l'input in una formula più complicata, come nel post che ho inserito in precedenza. Penso che il nocciolo del problema sia dovuto al fatto che lazy_dots() sta acquisendo l'espressione in modo diverso da lazy(). Vorrei capire perché si comportano in modo diverso e come utilizzare lazy() per ottenere la stessa funzionalità di lazy_dots().

g <- function(...){ 
    lazy_dots(...) 
} 

h <- function(x){ 
    lazy(x) 
} 

g(lm)[[1]] 
# <lazy> 
# expr: lm 
# env: <environment: R_GlobalEnv> 
h(lm) 
# <lazy> 
# expr: function (formula, data, subset, weights, na.action, method = "qr", ... 
# env: <environment: R_GlobalEnv> 

anche cambiando .follow__symbols-FALSE per lazy() in modo che sia lo stesso che lazy_dots() non funziona.

lazy 
# function (expr, env = parent.frame(), .follow_symbols = TRUE) 
# { 
#  .Call(make_lazy, quote(expr), environment(), .follow_symbols) 
# } 
# <environment: namespace:lazyeval> 

lazy_dots 
# function (..., .follow_symbols = FALSE) 
# { 
#  if (nargs() == 0) 
#   return(structure(list(), class = "lazy_dots")) 
#  .Call(make_lazy_dots, environment(), .follow_symbols) 
# } 
# <environment: namespace:lazyeval> 


h2 <- function(x){ 
    lazy(x, .follow_symbols=FALSE) 
} 

h2(lm) 
# <lazy> 
# expr: x 
# env: <environment: 0xe4a42a8> 

Mi sento davvero un po 'bloccato su cosa fare.

+0

@Henrik questo è quello che volevo dire! Emette comunque un errore, e nel complesso il problema è lo stesso. Ho aggiornato la domanda per riflettere la correzione. –

risposta

2

Una possibilità potrebbe essere quella di far scrivere select_happy quasi allo stesso modo come il select funzione standard:

select_happy<- function(df, ...){ 
    select_(df, .dots = setNames(lazy_dots(...), "happy")) 
} 

f <- function(){ 
    print('foo') 
} 

> select_happy(df, a) 
    happy 
1  1 
2  2 
3  3 
> 
> select_happy(df, f) 
    happy 
1  4 
2  5 
3  6 
> 
> select_happy(df, lm) 
    happy 
1  7 
2  8 
3  9 

Si noti che la definizione di funzione della funzione standard select è:

> select 
function (.data, ...) 
{ 
    select_(.data, .dots = lazyeval::lazy_dots(...)) 
} 
<environment: namespace:dplyr> 

noti inoltre che con questa definizione, select_happy accetta più colonne da selezionare, ma assegnerà il nome a eventuali colonne aggiuntive "NA":

> select_happy(df, lm, a) 
    happy NA 
1  7 1 
2  8 2 
3  9 3 

Naturalmente si potrebbe apportare alcune modifiche per tali casi, per esempio:

select_happy<- function(df, ...){ 
    dots <- lazy_dots(...) 
    n <- length(dots) 
    if(n == 1) newnames <- "happy" else newnames <- paste0("happy", seq_len(n)) 
    select_(df, .dots = setNames(dots, newnames)) 
} 

> select_happy(df, f) 
    happy 
1  4 
2  5 
3  6 

> select_happy(df, lm, a) 
    happy1 happy2 
1  7  1 
2  8  2 
3  9  3 
+0

Questa soluzione funziona, ma non mi piace che imponga l'uso dei puntini di sospensione. La vignetta fa sembrare che questo dovrebbe essere un uso valido di 'lazy()'. Per le funzioni che devono utilizzare i puntini di sospensione, anche se questa sembra una buona soluzione. –