2014-09-26 4 views
5

ho qualche codice R che assomiglia a questo:codice Refactor R quando le funzioni di libreria uso non standard di valutazione

library(dplyr) 
library(datasets) 

iris %.% group_by(Species) %.% filter(rank(Petal.Length, ties.method = 'random')<=2) %.% ungroup() 

Dare:

Source: local data frame [6 x 5] 

    Sepal.Length Sepal.Width Petal.Length Petal.Width Species 
1   4.3   3.0   1.1   0.1  setosa 
2   4.6   3.6   1.0   0.2  setosa 
3   5.0   2.3   3.3   1.0 versicolor 
4   5.1   2.5   3.0   1.1 versicolor 
5   4.9   2.5   4.5   1.7 virginica 
6   6.0   3.0   4.8   1.8 virginica 

Questa gruppi di specie, e per ogni gruppo mantiene solo i due con il Petal.Length più corto. Ho alcune duplicazioni nel mio codice, perché lo faccio più volte per colonne e numeri diversi. Es .:

iris %.% group_by(Species) %.% filter(rank(Petal.Length, ties.method = 'random')<=2) %.% ungroup() 
iris %.% group_by(Species) %.% filter(rank(-Petal.Length, ties.method = 'random')<=2) %.% ungroup() 
iris %.% group_by(Species) %.% filter(rank(Petal.Width, ties.method = 'random')<=3) %.% ungroup() 
iris %.% group_by(Species) %.% filter(rank(-Petal.Width, ties.method = 'random')<=3) %.% ungroup() 

Voglio estrarre questo in una funzione. L'approccio naive non funziona:

keep_min_n_by_species <- function(expr, n) { 
    iris %.% group_by(Species) %.% filter(rank(expr, ties.method = 'random') <= n) %.% ungroup() 
} 

keep_min_n_by_species(Petal.Width, 2) 

Error in filter_impl(.data, dots(...), environment()) : 
    object 'Petal.Width' not found 

quanto mi risulta, l'espressione rank(Petal.Length, ties.method = 'random') <= 2 viene valutata in un contesto diverso, introdotto dalla funzione filter, che fornisce un significato per il Petal.Length espressione. Non posso semplicemente scambiare una variabile per Petal.Length, perché verrà valutata nel contesto sbagliato. Ho provato a utilizzare diverse combinazioni di substitute e eval, dopo aver letto questa pagina: Non-standard evaluation. Non riesco a capire una combinazione appropriata. Penso che il problema potrebbe essere che non voglio solo passare attraverso un'espressione dal chiamante (Petal.Length) a filter per valutarlo - Voglio costruire una nuova espressione più grande (rank(Petal.Length, ties.method = 'random') <= 2) e poi passare l'intera espressione attraverso a filter da valutare.

  1. Come posso refactoring questo espressione in una funzione?
  2. Più in generale, come devo fare per estrarre un'espressione R in una funzione?
  3. Ancora più in generale, mi sto avvicinando a questo con la mentalità sbagliata? In più lingue mainstream con cui ho familiarità (ad es. Python, C++, C#), questa è un'operazione relativamente semplice che voglio fare sempre per rimuovere la duplicazione nel mio codice. In R sembra (almeno per me) che la valutazione non standard possa renderla un'operazione non ovvia. Dovrei fare qualcos'altro interamente?
+0

http: // adv-r. had.co.nz/Computing-on-the-language.html – James

+1

Credo che hadley stia lavorando a questo con il pacchetto lazyeval, che fornirebbe la struttura generale per implementare versioni standard delle funzioni NSE in altri pacchetti. – baptiste

risposta

6

dplyr versione 0.3 sta iniziando ad affrontare questo usando il pacchetto lazyeval, come @baptiste menzionato, e una nuova famiglia di funzioni che utilizzano standard di valutazione (nomi delle funzioni stesse delle versioni NSE, ma che termina in _). C'è una vignetta qui: https://github.com/hadley/dplyr/blob/master/vignettes/nse.Rmd

Tutto ciò detto, non conosco le migliori pratiche per quello che stai cercando di fare (anche se sto cercando di fare la stessa cosa). Ho qualcosa che funziona, ma come ho detto, non so se è il modo migliore per farlo.Si noti l'uso di filter_() invece di filter(), e passando l'argomento come una stringa di caratteri citato:

devtools::install_github("hadley/dplyr") 
devtools::install_github("hadley/lazyeval") 

library(dplyr) 
library(lazyeval) 

keep_min_n_by_species <- function(expr, n, rev = FALSE) { 
    iris %>% 
    group_by(Species) %>% 
    filter_(interp(~rank(if (rev) -x else x, ties.method = 'random') <= y, # filter_, not filter 
        x = as.name(expr), y = n)) %>% 
    ungroup() 
} 

keep_min_n_by_species("Petal.Width", 3) # "Petal.Width" as character string 
keep_min_n_by_species("Petal.Width", 3, rev = TRUE) 

aggiornamento sulla base di commento @ di Hadley:

keep_min_n_by_species <- function(expr, n) { 
    expr <- lazy(expr) 

    formula <- interp(~rank(x, ties.method = 'random') <= y, 
        x = expr, y = n) 

    iris %>% 
    group_by(Species) %>% 
    filter_(formula) %>% 
    ungroup() 
} 

keep_min_n_by_species(Petal.Width, 3) 
keep_min_n_by_species(-Petal.Width, 3) 
+1

Farei' expr <- lazyeval :: lazy (expr) '(quindi non è necessario citarla) e creare la formula al di fuori del chiamata 'filter _()'. – hadley

+0

Ah, molto bello - grazie! Ho aggiunto un aggiornamento alla mia risposta con quello che penso stia ottenendo. – andyteucher

+1

Perfetto :) Questo è esattamente come lo farei io. – hadley

4

Come su

keep_min_n_by_species <- function(expr, n) { 
    mc <- match.call() 
    fx <- bquote(rank(.(mc$expr), ties.method = 'random') <= .(mc$n)) 
    iris %.% group_by(Species) %.% filter(fx) %.% ungroup() 
} 

che sembra per consentire a tutti le istruzioni da eseguire senza errori

keep_min_n_by_species(Petal.Width, 2) 
keep_min_n_by_species(-Petal.Width, 2) 
keep_min_n_by_species(Petal.Width, 3) 
keep_min_n_by_species(-Petal.Width, 3) 

L'idea è che usiamo match.call() di catturare le espressioni non valutate passati alla funzione. Quindi utilizziamo bquote() per creare il filtro come oggetto di chiamata.

+0

Ottimo! Sono un po 'triste, sembra così arcano. Non penso che avrei potuto capirlo senza molta più esperienza di R. Se non ci sono risposte più dettagliate nell'arco di un giorno o giù di lì tornerò e accetto questo. – Weeble

+0

:) Ho imparato due funzioni davvero utili. – Elin

+0

Consiglierei di stare lontano da 'match.call()' e utilizzare invece funzioni più specifiche. (E in questo caso, sarebbe più sicuro valutare 'n') – hadley