2012-10-11 6 views
8

Powiel możliwe:
How to write an R function that evaluates an expression within a data-frameDlaczego leniwa ocena nie działa w tej funkcji R?

Chcę napisać funkcję, która sortuje data.frame - zamiast korzystania kolejność kłopotliwe(). Biorąc pod uwagę coś takiego

> x=data.frame(a=c(5,6,7),b=c(3,5,1)) 
> x 
    a b 
1 5 3 
2 6 5 
3 7 1 

Chcę powiedzieć coś takiego:

sort.df(x,b) 

więc oto moja funkcja:

sort.df <- function(df, ...) { 
    with(df, df[order(...),]) 
} 

byłem naprawdę dumny z tego. Biorąc pod uwagę leniwą ocenę R, doszedłem do wniosku, że parametr ... będzie oceniany tylko wtedy, gdy będzie potrzebny - i do tego czasu będzie w zakresie, ze względu na "z".

Jeśli uruchomię linię "z" bezpośrednio, działa. Ale funkcja nie działa.

> with(x,x[order(b),]) 
    a b 
3 7 1 
1 5 3 
2 6 5 
> sort.df(x,b) 
Error in order(...) : object 'b' not found 

Co jest nie tak i jak to naprawić? Tego rodzaju "magię" widzę często w pakietach takich jak na przykład plyr. Co to za sztuczka?

+0

sort.df (x, x $ b) działa, ale nadal nie mam pojęcia dlaczego sortowania. df (x, b) nie działa – Ali

+0

Zobacz także 'plyr :: arran', które robi dokładnie to. – hadley

+1

Dzięki! Nie wiedziałem o aranżacji mimo używania plyr każdego dnia. Kolejny przykład, że trudno jest znaleźć odpowiednie rozwiązania w świecie R - a tak wiele dobrego programowania R polega na uczeniu się najlepszych praktyk przy użyciu kilku dobrych pakietów. –

Odpowiedz

7

To dlatego, gdy jesteś przejazdem b jesteś rzeczywiście nie przekazując obiekt. Umieść browser w swojej funkcji, a zobaczysz, co mam na myśli. Ukradłem to od jakiegoś robota internetowego gdzieś:

x=data.frame(a=c(5,6,7),b=c(3,5,1)) 

sort.df <- function(df, ..., drop = TRUE){ 
    ord <- eval(substitute(order(...)), envir = df, enclos = parent.frame()) 
    return(df[ord, , drop = drop]) 
} 

sort.df(x, b) 

będzie działać.

Więc jeśli szukasz miły sposób to zrobić w przyłożonym sensie:

library(taRifx) 
sort(x, f=~b) 
+2

+ 1 dla fajnego rozwiązania, a zwłaszcza dla sugerowania zabawy z wywołaniem 'browser()' wewnątrz funkcji. IMHO, to najlepszy sposób na poznanie "..." i otaczającej go dziwności. –

+0

Ktoś mógłby mnie poprawić, ale 'enclos = parent.frame()' jest domyślne w 'eval', więc po prostu' eval (zamiennik (kolejność (...)), envir = df) 'również działa :) – user1665355

9

to zrobi to, co chcesz:

sort.df <- function(df, ...) { 
    dots <- as.list(substitute(list(...)))[-1] 
    ord <- with(df, do.call(order, dots)) 
    df[ord,] 
} 

## Try it out 
x <- data.frame(a=1:10, b=rep(1:2, length=10), c=rep(1:3, length=10)) 
sort.df(x, b, c) 

A więc będzie to:

sort.df2 <- function(df, ...) { 
    cl <- substitute(list(...)) 
    cl[[1]] <- as.symbol("order") 
    df[eval(cl, envir=df),] 
} 
sort.df2(x, b, c) 
+1

Lub 'sort.df <- function (df, ...) df [order (eval (substitute (...), df)),]' –

+0

@JoshuaUlrich - Nie do końca taki sam. Twój będzie sortował tylko przez pierwszy element '...', ponieważ 'substitute (...)' tylko przechwytuje to. (Wstaw 'browser()' wywołanie w 'sort.df()', a następnie porównaj 'substitute (...)' i 'substitute (list (...))', aby zobaczyć co mam na myśli.) –