2012-04-05 12 views
12

Używam dużo "do.call" do generowania wywołań funkcji. Np .:do.call w połączeniu z "::"

myfun <- "rnorm"; 
myargs <- list(n=10, mean=5); 
do.call(myfun, myargs); 

Czasami jednak chciałbym wyraźnie wywołać funkcję z określonego pakietu. Podobne do np. stats::rnorm(n=10, mean=5). Czy jest jakiś sposób mogę korzystać do.call lub utworzyć funkcję, która zachowuje się jak do.call aby uzyskać to do pracy:

myfun <- "stats::rnorm"; 
myargs <- list(n=10, mean=5); 
do.call(myfun, myargs); 
+1

jak o 'do.call (stats :: rnorm, myargs)'? – kohske

+1

@kohske - wtedy wygląda na to, że 'stats :: rnorm (n = 10, mean = 5)' jest jeszcze prostsze :-) – Tommy

+1

, ale w takim przypadku nie możesz użyć listy jako argumentu :-( – kohske

Odpowiedz

18

nie ma funkcji o nazwie „statystyki :: rnorm”. Musisz znaleźć funkcję rnorm w przestrzeni nazw „status”:

myfun <- get("rnorm", asNamespace("stats")) 
myargs <- list(n=10, mean=5); 
do.call(myfun, myargs); 

Teraz można oczywiście również przejść z nazwy jak „statystykach :: rnorm” i podzielić go na części przestrzeni nazw i nazwy funkcji:

funname <- "stats::rnorm" 
fn <- strsplit(funname, "::")[[1]] 
myfun <- if (length(fn)==1) fn[[1]] else get(fn[[2]], asNamespace(fn[[1]])) 
myargs <- list(n=10, mean=5); 
do.call(myfun, myargs); 

Aktualizacja chciałem tylko pokazać, że takie podejście jest 2,5x szybciej niż ten z @Jeroen ...

do.call.tommy <- function(what, args, ...) { 
    if(is.character(what)){ 
    fn <- strsplit(what, "::")[[1]] 
    what <- if(length(fn)==1) { 
     get(fn[[1]], envir=parent.frame(), mode="function") 
    } else { 
     get(fn[[2]], envir=asNamespace(fn[[1]]), mode="function") 
    } 
    } 

    do.call(what, as.list(args), ...) 
} 

# Test it 
do.call.tommy(runif, 10) 
f1 <- function(FUN) do.call.tommy(FUN, list(5)) 
f2 <- function() { myfun<-function(x) x; do.call.tommy(myfun, list(5)) } 
f1(runif) 
f1("stats::runif") 
f2() 

# Test the performance...  
system.time(for(i in 1:1e4) do.call.jeroen("stats::runif", list(n=1, max=50))) # 1.07 secs 
system.time(for(i in 1:1e4) do.call.tommy("stats::runif", list(n=1, max=50))) # 0.42 secs 
11

Możesz usunąć cytaty: będzie to sama funkcja, a nie jej nazwa.

myfun <- stats::rnorm 
myargs <- list(n=10, mean=5) 
do.call(myfun, myargs) 
0

Dzięki za odpowiedzi. Myślę, że mam zamiar coś takiego:

do.call.jeroen <- function(what, args, ...){ 
    if(is.function(what)){ 
    what <- deparse(as.list(match.call())$what); 
    } 
    myfuncall <- parse(text=what)[[1]]; 
    mycall <- as.call(c(list(myfuncall), args)); 
    eval(mycall, ...); 
} 

To wydaje się być miłym uogólnienie do.call tak, że mogę jeszcze przejść na ciąg znaków za what argumentu, to jednak starannie emuluje stats::rnorm(n=10, mean=5) połączenia.

myfun1 <- "rnorm"; 
myfun2 <- "stats::rnorm"; 
myargs <- list(n=10, mean=5); 
do.call.jeroen(myfun1, myargs); 
do.call.jeroen(myfun2, myargs); 
do.call.jeroen(rnorm, myargs); 
do.call.jeroen(stats::rnorm, myargs); 

Jedną rzeczą, która jest ładne o to, że jeśli funkcja Wołam zastosowań match.call(), aby zapisać rozmowę gdzieś, to zachować rzeczywistą nazwę funkcji. Np:

do.call.jeroen ("statystyki :: GLM", lista (wzór = prędkość ~ dist, data = as.name ('samochody')))

Call: stats::glm(formula = speed ~ dist, data = cars) 

Coefficients: 
(Intercept)   dist 
    8.2839  0.1656 

Degrees of Freedom: 49 Total (i.e. Null); 48 Residual 
Null Deviance:  1370 
Residual Deviance: 478 AIC: 260.8 
+0

Ja naprawdę nie lubię fakt, że używasz 'oparse',' parsuj' i 'eval', aby rozwiązać ten problem.Na przykład nie obsługuje' f <-funkcji (x) do.call.jeroen (x, list (n = 10))); f (zignorować) ', i to ponad dwa razy wolniej niż wywołanie' do.call' ... Zaktualizowałem moją odpowiedź z alternatywą – Tommy

+0

Dzięki. Zaktualizowałem również moją odpowiedź. o 'do.call.jeroen' jest to, że oryginalna nazwa funkcji pozostaje zachowana, zamiast zastępowania jej anonimową funkcją. – Jeroen

Powiązane problemy