I recently (very) started looking into creating S3 functions in R. I am working in a function where I foresee having common operations between different methods. Not sure how this should be done. For example:
myfun <- function (x) {
UseMethod("myfun")
}
myfun.numeric <- function(x) {
a<-x+5
b<-a^2
c<-b+4
d<-c-3
d
}
myfun.character <- function(x) {
a<-as.numeric(x)+9
b<-a^2
c<-b+4
d<-c-3
d
}
myfun("3")
myfun(3)
The function at this time is not too long. I guess technically I can have a function that perform the part represented by letter "a" then have a common function that perform steps "b", "c", and "d". In some cases the functions could be quite short and having an additional function seems not to be the best practice. What is usually done in cases like this?
Here are two possibilities. There is some danger in using the default method since it might get invoked in unanticipated situations so the common function seems more reliable but either would work in the example you show.
1) default method Put the common code in the default method and use NextMethod().
myfun <- function (x) UseMethod("myfun")
myfun.numeric <- function(x) {
x<-x+5
NextMethod()
}
myfun.character <- function(x) {
x <-as.numeric(x)+9
NextMethod()
}
myfun.default <- function(x) {
b<-x^2
c<-b+4
d<-c-3
d
}
myfun("3")
myfun(3)
2) common function Alternately just put the common code in a separate function called common, say, and call it.
myfun <- function (x) UseMethod("myfun")
myfun.numeric <- function(x) {
y <-x+5
common(y)
}
myfun.character <- function(x) {
y <-as.numeric(x)+9
common(y)
}
common <- function(x) {
b<-x^2
c<-b+4
d<-c-3
d
}
myfun("3")
myfun(3)
Related
I have a function myfun which among other arguments has one that is a user supplied function, say f. This function may have any number of arguments, including maybe none. Here is a simple example:
myfun = function(f, ...) { f()}
Now calls to myfun might be
myfun( f=function() rnorm(10) )
myfun( f=function(m) rnorm(10, m) )
I don't want to use the ellipse argument ... inside of f, so my question is whether there is any other way to determine inside of myfun how many arguments the function f has? If f has no arguments it is then passed to the Rcpp routine doA.cpp, but if it has one or more arguments it is passed to doB.cpp. So I need to know inside myfun which it is.
Here is a toy example which hopefully makes it clearer what I am after:
myfun = function(f) {
numarg = number.of.arguments(f)
if(numarg==0) return(doA.cpp(f))
else return(doB.cpp(f))
}
so I need a "function" number.of.arguments, that is some way to determine numarg.
Based on your specific use-case, your best bet might be to query the formal arguments of f. However, note that there are several caveats with this method, which I note below.
f = function (f) {
if (length(formals(f)) == 0L) {
doA.cpp(f)
} else {
doB.cpp(f)
}
}
The caveats are that formals does not work for primitive functions: formals(mean) works, but formals(sum) returns NULL. Furthermore, formals counts ... as a single argument. So if you want to handle ... differently you'll have to do this manually:
if ('...' %in% names(formals(f))) {
# `...` is present
} else {
# `...` is not present
}
A more robust method when the user supplies the arguments is to find the length of the ... args via ...length().
You could then pass the ... arguments to doB.cpp inside a list, for instance:
myfun = function(f, ...) {
if (...length() == 0L) {
doA.cpp(f)
} else {
doB.cpp(f, list(...))
}
}
Konrad's is just what I need! Also, I learned about the formals function, which I had not seen before. Its limitations as discussed by Konrad won't matter for my case because the functions are ones the users have to write anyway, and I won't use ... . So, thanks!
Wolfgang
This is what I may code in R.
myfun = function(f, ...) { f(...)}
myfun( f=function(m) rnorm(10, m), m )
After getting comment from #Wolfgang Rolke, the question becomes better understood. Here is my second attempt.
myfun = function(f, ...) {
argg <- c(as.list(environment()), list(...))
numarg=length(argg)
if (numarg==1) { return( f()) }
if (numarg==2) { return(f(argg[[2]])) }
}
myfun( f=function() rnorm(10) )
myfun( f=function(m=2) rnorm(10, m) )
To easily verify the result, one may do the following:
myfun( f=function() mean(rnorm(10))) # it returns something like 0.07599287
myfun( f=function(m=10) mean(rnorm(10, m))) # it returns 9.49364
Is there a way to use a function-call to set up a collection of variables with new names?
What I'd like is something like the following:
helper <- function (x) {
y <<- x + 1
NULL
}
main <- function (x) {
helper(x)
return(y)
}
However, there are two problems with this:
the code means that y is defined in the global environment, which I don't want;
I'm also aware that the <<- operator is not kosher as far as CRAN is concerned.
Essentially I'd like to make my function main cleaner by passing a lot of the work it does to helper. Is there any legitimate way to do this for a package that I eventually want to be on CRAN?
I don't think your approach is in any way really sensible (why not use a List), but if you really want to do that, you can use assign to assign variables in arbitrary environments, e.g. the parent frame:
helper <- function(x) {
assign('y', x + 1, envir=parent.frame())
NULL
}
main <- function(x) {
helper(x)
return(y)
}
main(1)
# [1] 2
You can use the strategy to have helper returning a list with the calculated variables and then use them:
helper <- function (x) {
y <- x + 1
list(y = y)
}
main <- function (x) {
vars <- helper(x)
return(vars$y)
}
If you are going to use y often and don't want to always type var$s, you could assign it locally:
main <- function (x) {
vars <- helper(x)
y <- vars$y
return(y)
}
In contrast to assigning variables in arbitrary environments, this makes it way easier to reason what your code does.
I am currently dealing with a problem. I am working on a package for some specific distributions where among other things I would like to create a function that will fit an mixture to some data. For this I would like to use for example the fitdistr function. The problem is that I don't know from what distributions and weights and number of components the mixture will be composed of. Hence I need a function that will dynamically create an density function of some specified mixture so the fitdistr function can use it. For example if the user will call:
fitmix(data,dist=c(norm,chisq),params=list(c(mean=0,sd=3),df=2),wights=c(0.5,0.5))
to use ML method the code needs to create an density function
function(x,mean,sd,df) 0.5*dnorm(x,mean,sd)+0.5*dchisq(x,df)
so it can call optim or fitdistr.
An obvious solution is to use a lot of paste+eval+parse but I don't think this is the most elegant solution. A nice solution is probably hiding somewhere in non-standard evaluation and expression manipulation, but I have not enough skills in this problematic.
P.S. the params can be used as starting values for the optimizer.
Building expressions is relatively straight forward in R with functions like as.call and bquote and the fact that functions are first class objects in R. Building functions with dynamic signatures is a bit trickier. Here's a pass at some function that might help
to_params <- function(l) {
z <- as.list(l)
setNames(lapply(names(z), function(x) bquote(args[[.(x)]])), names(z))
}
add_exprs <- function(...) {
x <- list(...)
Reduce(function(a,b) bquote(.(a) + .(b)), x)
}
get_densities <- function(f) {
lapply(paste0("d", f), as.name)
}
weight_expr <- function(w, e) {
bquote(.(w) * .(e))
}
add_params <- function(x, p) {
as.call(c(as.list(x), p))
}
call_with_x <- function(fn) {
as.call(list(fn, quote(x)))
}
fitmix <- function(data, dist, params, weights) {
fb <- Reduce( add_exprs, Map(function(d, p, w) {
weight_expr(w, add_params(call_with_x(d), to_params(p)))
}, get_densities(dist), params, weights))
f <- function(x, args) {}
body(f) <- fb
f
}
Note that I changed the types of some of your parameters. The distributions should be strings. The parameters should be a list of named vectors. It would work with a call like this
ff <- fitmix(data, dist=c("norm","chisq"), params=list(c(mean=0,sd=3),c(df=2)),
weights=c(0.5,0.5))
It returns a function that takes an x and a list of named arguments. You could call it like
ff(0, list(mean=3, sd=2, df=2))
# [1] 0.2823794
which returns the same value as
x <- 0
0.5 * dnorm(x, mean = 3, sd = 2) + 0.5 * dchisq(x, df = 2)
# [1] 0.2823794
I am working on a function that would behave similar to Reduce where you pass in a function and dispatch it over the arguments. Here is simple example to demonstrate what I am working on.
fun <- function(f){
switch(f,
`+` = "addition",
stop("undefined")
)
}
fun(`+`)
Now this clearly won't work as it stands because switch requires a character or numeric EXPR. What I don't know how to do is convert the function f that is passed to fun to a string.
One approach is to capture the input and deparse the call.
fun <- function(f){
switch(deparse(substitute(f)),
`+` = "addition",
stop("undefined")
)
}
fun(`+`)
#[1] "addition"
Going off of Pierre's comment above, one can use identical to test whether two functions are the same. This doesn't work well with switch, but an if/else tree is still relatively simple:
fun <- function(f) {
if (identical(f, `+`)) {
return('addition')
} else if (identical(f, mean)) {
return('mean')
} else {
return('undefined')
}
}
I have a question with importing functions.
Say I have a R script named "functions" which looks like this:
mult <- function(x,y){
return(x*y)
}
divide <- function(x,y){
return(x/y)
}
Currently I am importing all functions in the script:
source(file="C:\\functions.R",echo=FALSE)
The problem is that the (actual) R script is getting very large.
Is there a way to import the "mult" function only?
I was looking at evalSource/insertSource but my code was not working:
insertSource("C:\\functions.R", functions="mult")
It looks like your code will work with a slight change: define an empty object for the function you want to load first, then use insertSource.
mult <- function(x) {0}
insertSource("C:\\functions.R", functions="mult")
mult
Which gives:
Object of class "functionWithTrace", from source
function (x, y)
{
return(x * y)
}
## (to see original from package, look at object#original)
The mult object has some additional information that I suppose is related to the original application for insertSource, but you could get rid of them with mult <- mult#.Data, which will set mult to the actual function body only.
Also, you might be interested in the modules project on github, which is trying to implement a lightweight version of R's package system to facilitate code reuse. Seems like that might be relevant, although I think you would have to split your functions into separate files in different subdirectories.
I ended up creating functions to do what you recommended.
This first group allows for multiple functions in one call:
LoadFunction <- function(file,...) {
dots <- match.call(expand.dots = FALSE)$...
dots <- sapply(dots, as.character)
output <- lapply(dots, function(x,file){eval(parse(text=paste(x," <- function(x) {0}",sep="")),envir = .GlobalEnv)
suppressMessages(insertSource(file, functions=x))
eval(parse(text=paste(x," <- ",x,"#.Data",sep="")),envir = .GlobalEnv) },file=file)
}
UnloadFunction <- function(...) {
dots <- match.call(expand.dots = FALSE)$...
dots <- sapply(dots, as.character)
output <- lapply(dots, function(x,file){eval(parse(text=paste("rm(",x,",envir = .GlobalEnv)",sep="")))},file=file)
}
They are called like this:
LoadFunction(file="C:\\functions.R",mult,divide)
UnloadFunction(mult,divide)
The second is only one function per call:
LoadFunction2 <- function(file,function_name) {
eval(parse(text=paste(function_name," <- function(x) {0}",sep="")),envir = .GlobalEnv)
suppressMessages(insertSource(file, functions=function_name))
eval(parse(text=paste(function_name," <- ",function_name,"#.Data",sep="")),envir = .GlobalEnv)
}
UnloadFunction2 <- function(function_name) {
eval(parse(text=paste("rm(",function_name,",envir = .GlobalEnv)",sep="")))
}
They are called like this:
LoadFunction2(file="C:\\functions.R","mult")
LoadFunction2(file="C:\\functions.R","divide")
UnloadFunction2("mult")
UnloadFunction2("divide")