2014-09-26 10 views
5

Mam niektóre kodu R, który wygląda tak:kod Refactor R gdy funkcje biblioteczne używać niestandardowych ocena

library(dplyr) 
library(datasets) 

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

Dawanie:

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 

według gatunków Te grupy, a każda grupa trzyma tylko dwa z najkrótszym Petal.Length. Mam trochę duplikacji w moim kodzie, ponieważ robię to kilka razy dla różnych kolumn i liczb. Np .:

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

Chcę to wydobyć z funkcji. Naiwne podejście nie działa:

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 

Jak rozumiem, wyrażenie rank(Petal.Length, ties.method = 'random') <= 2 jest oceniany w innym kontekście, wprowadzonej przez funkcję filter, który zapewnia znaczenia dla wyrażenia Petal.Length. Nie mogę po prostu zamienić zmiennej na Petal.Length, ponieważ będzie ona oceniana w niewłaściwym kontekście. Próbowałem używać różnych kombinacji substitute i eval, po przeczytaniu tej strony: Non-standard evaluation. Nie mogę wymyślić odpowiedniej kombinacji. Myślę, że problem może polegać na tym, że nie chcę po prostu przechodzić przez wyrażenie od dzwoniącego (Petal.Length) do filter, aby to ocenić - chcę skonstruować nowe, większe wyrażenie (rank(Petal.Length, ties.method = 'random') <= 2), a następnie przekazać to całe wyrażenie przez do filter, aby ocenić.

  1. Jak mogę refactor tego wyrażenia do funkcji?
  2. Bardziej ogólnie, w jaki sposób powinienem uzyskać wyodrębnienie wyrażenia R do funkcji?
  3. Jeszcze bardziej ogólnie, czy podchodzę do tego ze złym nastawieniem? W bardziej popularnych językach, które znam (np. Python, C++, C#), jest to stosunkowo prosta operacja, którą chcę wykonywać cały czas, aby usunąć duplikację w moim kodzie. W R wydaje mi się (przynajmniej dla mnie), że niestandardowa ocena może uczynić z niej bardzo nieoczywistą operację. Czy powinienem robić coś zupełnie innego?
+0

http: // ADV-R. had.co.nz/Computing-on-the-language.html – James

+1

Wierzę, że hadley pracuje nad tym z pakietem lazyeval, który zapewniłby ogólne ramy do implementacji standardowych wersji funkcji NSE w innych pakietach. – baptiste

Odpowiedz

6

dplyr wersja 0.3 zaczyna się rozwiązać ten przy użyciu pakietu lazyeval, jak @baptiste wspomniano, a nowa rodzina funkcji, które używają standardowej oceny (tak samo jak nazwy funkcji wersji NSE, ale kończące się _). Tutaj jest winieta: https://github.com/hadley/dplyr/blob/master/vignettes/nse.Rmd

Wszystko, co jest powiedziane, nie znam najlepszych praktyk dla tego, co próbujesz zrobić (chociaż próbuję zrobić to samo). Mam coś działającego, ale jak powiedziałem, nie wiem, czy to najlepszy sposób na zrobienie tego.Zwróć uwagę na użycie filter_() zamiast filter() i przechodząc w argumencie jako cytowanego ciągu znaków:

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) 

Aktualizacja oparta na komentarz @ 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

Zrobiłbym' wyrażenie <- lazyeval :: lazy (expr) '(więc nie musisz tego cytować) i utwórz formułę poza 'filter _()' wywołanie. – hadley

+0

Ah, bardzo ładne - dzięki! Dodałem aktualizację do mojej odpowiedzi z tym, co myślę, że dostajesz. – andyteucher

+1

Perfect :) Dokładnie tak zrobię. – hadley

4

Jak o

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() 
} 

Wydaje się, aby wszystkie oświadczenia uruchomić bez błędu

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) 

Chodzi o to, że używamy match.call() uchwycić unevaluated wyrażeń przekazane do funkcji. Następnie używamy bquote() do budowania filtra jako obiektu wywołania.

+0

Świetnie! Trochę mi smutno, wydaje mi się to takie tajemnicze. Nie sądzę, bym mógł to rozgryźć bez większego doświadczenia R. Jeśli nie ma bardziej szczegółowych odpowiedzi w ciągu jednego dnia, wrócę i przyjmuję to. – Weeble

+0

:) Nauczyłem się dwóch naprawdę przydatnych funkcji. – Elin

+0

Polecam trzymanie się z dala od 'match.call()' i używanie zamiast tego bardziej specyficznych funkcji. (A w tym przypadku bezpieczniej byłoby ocenić 'n') – hadley

Powiązane problemy