2012-10-19 11 views
33

Hadley Wickham vor kurzem eine interessante Frage auf der r-devel mailing Liste gestellt, und eine vorhandene Frage zu dem Thema auf StackOverflow nicht finden konnte, dachte ich, dass es nützlich sein könnte, denn es existiert auch hier.Wie programmgesteuert eine R-Funktion erstellen?

In Anlehnung an:

Eine R-Funktion besteht aus drei Elementen: eine Argumentenliste, einen Körper und eine Umgebung. Können wir eine Funktion programmatisch aus diesen drei Elementen konstruieren?

(Eine ziemlich umfassende Antwort wird am Ende des Threads in der obigen r-devel Verbindung erreicht. Ich werde dies für andere offen lassen, um das Benchmarking der verschiedenen Lösungen selbst neu zu erstellen und es als Antwort zu liefern, aber sei sicher, dass du Hadley zitierst, wenn du es tust. Wenn niemand in ein paar Stunden aufsteht, mache ich es selbst.)

+0

Ich denke, das ist, warum sie Lisps erfunden! – Justin

+0

seltsam, wie es klingen mag, wir haben einen Fall gesehen, in dem man [string] eine Zeichenfolge (http://stackoverflow.com/q/9345373/471093) haben möchte, um etwas Ähnliches (knitr) zu erreichen. – baptiste

Antwort

43

Dies ist eine Erweiterung der Diskussion here.

Unsere drei Teile müssen eine Argumentliste, ein Körper und eine Umgebung sein.

Für die Umwelt werden wir einfach env = parent.frame() standardmäßig verwenden.

wir nicht wirklich eine regelmäßige alte Liste für die Argumente wollen, so verwenden Sie stattdessen wir alist , die etwas anderes Verhalten hat:

“... Werte nicht bewertet werden, und getaggt Argumente ohne Wert

args <- alist(a = 1, b = 2) 

Für den Körper ist „

erlaubt, wir quote unseren Ausdruck ein call zu bekommen:

body <- quote(a + b) 

Eine Option ist args zu einem pairlist zu konvertieren und dann rufen Sie einfach die Funktion function eval mit:

make_function1 <- function(args, body, env = parent.frame()) { 
     args <- as.pairlist(args) 
     eval(call("function", args, body), env) 
} 

Eine weitere Option eine leere Funktion, ist zu erstellen und es dann mit den gewünschten Werten füllen:

make_function2 <- function(args, body, env = parent.frame()) { 
     f <- function() {} 
     formals(f) <- args 
     body(f) <- body 
     environment(f) <- env 

     f 
} 

Eine dritte Möglichkeit ist einfach zu verwenden as.function:

make_function3 <- function(args, body, env = parent.frame()) { 
     as.function(c(args, body), env) 
} 

Und schließlich scheint dies sehr ähnlich das erste Verfahren zu mir, außer wir ein etwas anderes Idiom werden mit dem Funktionsaufruf zu erstellen, mit substitute statt call:

make_function4 <- function(args, body, env = parent.frame()) { 
     subs <- list(args = as.pairlist(args), body = body) 
     eval(substitute(`function`(args, body), subs), env) 
} 


library(microbenchmark) 
microbenchmark(
     make_function1(args, body), 
     make_function2(args, body), 
     make_function3(args, body), 
     make_function4(args, body), 
     function(a = 1, b = 2) a + b 
    ) 

Unit: nanoseconds 
          expr min  lq median  uq max 
1 function(a = 1, b = 2) a + b 187 273.5 309.0 363.0 673 
2 make_function1(args, body) 4123 4729.5 5236.0 5864.0 13449 
3 make_function2(args, body) 50695 52296.0 53423.0 54782.5 147062 
4 make_function3(args, body) 8427 8992.0 9618.5 9957.0 14857 
5 make_function4(args, body) 5339 6089.5 6867.5 7301.5 55137 
+0

joran, tolle zusammenfassung danke. Würden Sie das Benchmarking bitte kommentieren? Es sieht von Ihrem Ergebnis aus wie Funktion1 ist marginal schneller als die anderen Funktionen. Aber mit der albernen Alternative: 'Funktion (a = 1, b = 2, c = 3, d = 4, e = 5, f = 6, g = 7, h = 8, i = 9, j = 10) { exp (a + b) * (c + d)^e/f - ln (g) + h^i^j } Ich bekomme diese Funktion4 erscheint viel schneller als die anderen. Irgendwelche Gedanken? – PatrickT

2

Es gibt auch das Problem, alist Objekte programmgesteuert zu erstellen, was beim Erstellen von Funktionen nützlich sein kann, wenn die Anzahl der Argumente variabel ist.

Eine alist ist einfach eine benannte Liste leerer Symbole. Diese leeren Symbole können mit substitute() erstellt werden.Also:

make_alist <- function(args) { 
    res <- replicate(length(args), substitute()) 
    names(res) <- args 
    res 
} 

identical(make_alist(letters[1:2]), alist(a=, b=)) 
## [1] TRUE 
Verwandte Themen