2016-04-15 4 views
8

La valutazione non standard è molto utile quando utilizza i verbi di dplyr. Ma può essere problematico quando si usano quei verbi con argomenti di funzione. Per esempio diciamo che voglio creare una funzione che mi dà il numero di righe per una determinata specie.Creare una funzione con un argomento passato a dplyr :: filter qual è il modo migliore per aggirare nse?

# Load packages and prepare data 
library(dplyr) 
library(lazyeval) 
# I prefer lowercase column names 
names(iris) <- tolower(names(iris)) 
# Number of rows for all species 
nrow(iris) 
# [1] 150 

Esempio non funzionante

Questa funzione non funziona come previsto perché species viene interpretata nel contesto del frame di dati dell'iride anziché essere interpretato nel contesto dell'argomento funzione:

nrowspecies0 <- function(dtf, species){ 
    dtf %>% 
     filter(species == species) %>% 
     nrow() 
} 
nrowspecies0(iris, species = "versicolor") 
# [1] 150 

3 esempi di attuazione

Per aggirare la valutazione non standard, io di solito aggiungere l'argomento con una sottolineatura:

nrowspecies1 <- function(dtf, species_){ 
    dtf %>% 
     filter(species == species_) %>% 
     nrow() 
} 

nrowspecies1(iris, species_ = "versicolor") 
# [1] 50 
# Because of function name completion the argument 
# species works too 
nrowspecies1(iris, species = "versicolor") 
# [1] 50 

non è del tutto soddisfacente in quanto cambia il nome dell'argomento funzione per qualcosa di meno user friendly. Oppure si basa sul completamento automatico che temo non sia una buona pratica per la programmazione. Per mantenere un bel nome dell'argomento, che potevo fare:

nrowspecies2 <- function(dtf, species){ 
    species_ <- species 
    dtf %>% 
     filter(species == species_) %>% 
     nrow() 
} 
nrowspecies2(iris, species = "versicolor") 
# [1] 50 

Un altro modo per aggirare la valutazione non standard sulla base di this answer. interp() interpreta species nel contesto dell'ambiente funzione:

nrowspecies3 <- function(dtf, species){ 
    dtf %>% 
     filter_(interp(~species == with_species, 
         with_species = species)) %>% 
     nrow() 
} 
nrowspecies3(iris, species = "versicolor") 
# [1] 50 

Considerando la funzione 3 di cui sopra, qual è il preferito - più robusta - modo per implementare questa funzione di filtro? Ci sono altri modi?

risposta

3

Questa domanda non ha assolutamente nulla a che fare con la valutazione non standard. Mi permetta di riscrivere la funzione iniziale a fare questa precisazione:

nrowspecies4 <- function(dtf, boo){ 
    dtf %>% 
     filter(boo == boo) %>% 
     nrow() 
} 
nrowspecies4(iris, boo = "versicolor") 
#150 

L'espressione all'interno del vostro filter restituisce sempre TRUE (quasi sempre - vedi esempio sotto), è per questo che non funziona, non a causa di qualche magia NSE .

Il tuo nrowspecies2 è la strada da percorrere.

FWIW, species nella vostra nrowspecies0 è infatti valutato come una colonna, non come la variabile di ingresso species, e si può verificare che confrontando nrowspecies0(iris, NA)-nrowspecies4(iris, NA).

5

La risposta da @eddi è corretta su cosa sta succedendo qui. Sto scrivendo un'altra risposta che risponde alla richiesta più ampia di come scrivere le funzioni usando i verbi dplyr.Noterai che, in definitiva, utilizza qualcosa come nrowspecies2 per evitare la tautologia species == species.

Per scrivere un verbo funzione incarto dplyr (s) che funzionerà con NSE, scrivere due funzioni:

Prima scrivere una versione che richiede ingressi citati, utilizzando lazyeval e una versione SE del dplyr verbo. Quindi in questo caso, filter_.

nrowspecies_robust_ <- function(data, species){ 
    species_ <- lazyeval::as.lazy(species) 
    condition <- ~ species == species_ # * 
    tmp <- dplyr::filter_(data, condition) # ** 
    nrow(tmp) 
} 
nrowspecies_robust_(iris, ~versicolor) 

Seconda fare una versione che utilizza NSE:

nrowspecies_robust <- function(data, species) { 
    species <- lazyeval::lazy(species) 
    nrowspecies_robust_(data, species) 
} 
nrowspecies_robust(iris, versicolor) 

* = se si vuole fare qualcosa di più complesso, potrebbe essere necessario utilizzare lazyeval::interp qui come nelle punte collegate sotto

** = anche, se è necessario modificare i nomi di output, vedere l'argomento .dots

+0

Grazie, l'e-mail da Hadley lei ha citato mi ha fatto guardare 'vignetta (" lazyeval ")" che spiega che "Ogni funzione che utilizza NSE dovrebbe avere una finestra di escape standard di valutazione (SE) che esegue il calcolo effettivo. Il nome della funzione SE dovrebbe terminare con _." Vorrei una spiegazione di ciò che Hadley intende con "adatto per programmare con" alla fine della vignetta 'lazyeval'. Questo implica che non dovrei usare nse all'interno delle funzioni? –

+0

Sì, o almeno dovresti evitarlo quando possibile. Consulta anche la sezione "Aspetti negativi della valutazione non standard" qui http://adv-r.had.co.nz/Computing-on-the-language.html Il problema di base, come Hadley spiega in quel capitolo, è che l'NSE è molto difficile da ragionare all'interno di un programma perché le funzioni possono essere diverse in diversi contesti_. Cioè, se utilizzato in modo interattivo, una funzione NSE può agire in modo diverso rispetto a quando viene utilizzata in una funzione. – jaimedash

+1

Hadley spiega il concetto di "trasparenza referenziale" in [il suo keynote alla conferenza UseR del 2016] (https://channel9.msdn.com/Events/useR-international-R-User-conference/useR2016/Towards-a-grammar -di-grafica interattiva) (a 38min30s)."La formula mantiene sia il codice che l'ambiente in cui questo codice deve essere valutato, senza effettivamente fare la valutazione." Ho creato un esempio utilizzando una formula e incollato in una nuova risposta. –

0

in his 2016 UseR talk (@ 38min30s), Hadley Wickham spiega il concetto di referential transparency. Utilizzando una formula, la funzione di filtro può essere riformulata come:

nrowspecies5 <- function(dtf, formula){ 
    dtf %>% 
     filter_(formula) %>% 
     nrow() 
} 

Questo ha il vantaggio di beeing più generico

nrowspecies5(iris, ~ species == "versicolor") 
# 50 
nrowspecies5(iris, ~ sepal.length > 6 & species == "virginica") 
# 41 
nrowspecies5(iris, ~ sepal.length > 6 & species == "setosa") 
# 0