I'd like to assign a function to variable, using a string. The get() function in the base package does almost exactly what I want. For example,
valueReadFromFile <- "median"
ds <- data.frame(X=rnorm(10), Y=rnorm(10))
dynamicFunction <- get(valueReadFromFile)
dynamicFunction(ds$X) #Returns the variable's median.
However, I want to qualify the function with its package, so that I don't have to worry about (a) loading the function's package with library(), or (b) calling the wrong function in a different package.
Is there a robust, programmatic way I can qualify a function's name with its package using get() (or some similar function)? The following code doesn't work, presumably because get() doesn't know how to interpret the package name before the ::.
require(scales) #This package has functions called `alpha()` and `rescale()`
require(psych) #This package also has functions called `alpha()` and `rescale()`
dynamicFunction1 <- get("scales::alpha")
dynamicFunction2 <- get("psych::alpha")
Try this:
dynamicFunction1 <- get("alpha", envir=as.environment("package:scales"))
dynamicFunction2 <- get("alpha", as.environment("package:psych"))
A matter of terminology: I would not call dynamicFunction1 a "variable" but rather a "name". There is not really a formal class of object named "variable" but I usually see that term used for data-objects whereas "names" are language objects.
You can also call :: directly with character values, or getExportedValue, which :: uses internally
eg
dynamicFunction1 <- `::`('scales', 'alpha')
dynamicFunction2 <- `::`('psych', 'alpha')
or
dynamicFunction1 <- getExportedValue('scales', 'alpha')
dynamicFunction2 <- getExportedValue('psych', 'alpha')
Related
In my code there is a situation where I conditionally want to use one accessor function or another throughout the code. Instead of having an if-else statement for every time I want to pick which accessor to use and coding it explicitly, I tried to conditionally assign either of the accessor functions to a new function called accessor_fun and use it throughout the code, but this returns an error when I use the accessor function to reassign the values it accesses. Here is a simplified example of the problem I am having:
#reassigning the base r function names to a new function name
alt_names_fun <- names
example_list <- list(cat = 7, dog = 8, fish = 33)
other_example_list <- list(table = 44, chair = 101, desk = 35)
#works
alt_names_fun(example_list)
#throws error
alt_names_fun(example_list) <- alt_names_fun(other_example_list)
#still throws error
access_and_assign <- function(x, y, accessor) {
accessor(x) <- accessor(y)
}
access_and_assign(x = example_list, y = other_example_list, accessor = alt_names_fun)
#still throws error
alt_names_fun_2 <- function(x){names(x)}
alt_names_fun_2(example_list) <- alt_names_fun_2(other_example_list)
#works
names(example_list) <- names(other_example_list)
As you see if you try the code above, an example of the kind of error I am getting is
Error in alt_names_fun(example_list) <- alt_names_fun(other_example_list) :
could not find function "alt_names_fun<-"
So my question is, is there a way to do the reassignment of R accessor functions and use them in a way like I am trying to in the example above?
Accessor functions are really pairs of functions. One for retrieval and one for assignment. If you want to replicate that, you need to replicate both parts
alt_names_fun <- names
`alt_names_fun<-` <- `names<-`
The assignment versions have <- in their name. This is a special naming convection that R uses to find them. Since these are character normally not allowed in basic symbol names, you need to use the back ticks to enclose the function names.
Data for reproducibility
.i <- tibble(a=2*1:4+1, b=2*1:4)
This function is supposed to take its data and other arguments as unquoted names, find those names in the data, and use them to add a column and filter out the
top row. It does not work. Mutate says it can not find a.
t1 <- function(.j=.i, X=a, Y=b){
e_X <- enquo(X)
e_Y <- enquo(Y)
mutate(.data=.j, pass=UQ(e_X)+1) %>%
filter(UQ(e_Y) > 3) -> out
out
}
t1(a,b)
This function, which I found by typo -- note the .i instead of .j in the mutate statement -- does what the previous function was supposed to do. And I don't know why. I think it is skipping over the function arguments and finding .i in the global environment. Or maybe it is using a ouiji board.
t2 <- function(.j=.i, X=a, Y=b){
e_X <- enquo(X)
e_Y <- enquo(Y)
mutate(.data=.i, pass=UQ(e_X)+1) %>%
filter(UQ(e_Y) > 3) -> out
out
}
t1(a,b)
Since mutate could not find .j when passed to it in the usual R way, maybe it needs to be passed in an rlang-style quosure, like the formals X and Y. This function also does not work, with UQ in mutate saying that it can not find a. Like the first function above, it works if the .j in mutate is replaced with a .i. (Seems like there should be an "enquos" to parallel quos).
t3 <- function(.j=.i, X=a, Y=b){
e_j <- enquo(.j)
e_X <- enquo(X)
e_Y <- enquo(Y)
mutate(.data=UQ(.j), pass=UQ(e_X)+1) %>%
filter(UQ(e_Y) > 3) -> out
out
}
t1(a,b)
Finally, it appears that, once the .i substitution in mutate is made, t4() no longer needs a data argument at all. See below, where I replace it with bop_foo_foo. If, however, you replace bop_foo_foo throughout with the name of the data, .i, (t5()) then UQ again fails to find a.
bop_foo_foo <- 0
t4 <- function(bop_foo_foo, X=a, Y=b){
e_j <- enquo(bop_foo_foo)
e_X <- enquo(X)
e_Y <- enquo(Y)
mutate(.data=UQ(.i), pass=UQ(e_X)+1) %>%
filter(UQ(e_Y) > 3) -> out
out
}
t1(a,b)
The functions above seem to me to be relatively minor variants on a single function. I have run dozens more, and although I have observed some patterns,
and read the enquo and UQ help files I do not know how many times, a real
understanding continues to elude me.
I would like to know why the functions above that that don't work don't, and why the ones that do work do. I don't necessarily need a function by function critique. If you can state general principles that embody the required, understanding, that would be delightful. And more than sufficient.
I think it is skipping over the function arguments and finding .i in the global environment.
Yes, scope of symbols in R is hierarchical. The variables local to a function are looked up first, and then the surrounding environment of the function is inspected, and so on.
mutate(.data = UQ(.j), ...)
I think you are missing the difference between regular arguments and (quasi)quoted arguments. Unquoting is only relevant for quasiquoted arguments. Since the .data argument of mutate() is not quasiquoted it does not make sense to try and unquote stuff. The quasiquoted arguments are the ones that are captured/quoted with enexpr() or enquo(). You can tell whether an argument is quasiquoted either by looking at the documentation or by recognising that the argument supports direct references to columns (regular arguments need to be explicit about where to find the columns).
In the next version of rlang, the exported UQ() function will throw an error to make it clear that it should not be called directly and that it can only be used in quasiquoted arguments.
I would suggest:
Call the first argument of your function data or df rather than .i.
Don't give it a default. The user should always supply the data.
Don't capture it with enquo() or enexpr() or substitute(). Instead pass it directly to the data argument of other verbs.
Once this is out of the way it will be easier to work out the rest.
I have made a function that takes as an argument another function, the argument function takes as its argument some object (in the example a vector) which is supplied by the original function. It has been challenging to make the function call in the right way. Below are three approaches I have used after having read Programming with dplyr.
Only Option three works,
I would like to know if this is in fact the best way to evaluate a function within a function.
library(dplyr);library(rlang)
#Function that will be passed as an argument
EvaluateThis1 <- quo(mean(vector))
EvaluateThis2 <- ~mean(vector)
EvaluateThis3 <- quo(mean)
#First function that will recieve a function as an argument
MyFunc <- function(vector, TheFunction){
print(TheFunction)
eval_tidy(TheFunction)
}
#Second function that will recieve a function as an argument
MyFunc2 <- function(vector, TheFunction){
print(TheFunction)
quo(UQ(TheFunction)(vector)) %>%
eval_tidy
}
#Option 1
#This is evaluating vector in the global environment where
#EvaluateThis1 was captured
MyFunc(1:4, EvaluateThis1)
#Option 2
#I don't know what is going on here
MyFunc(1:4, EvaluateThis2)
MyFunc2(1:4, EvaluateThis2)
#Option 3
#I think this Unquotes the function splices in the argument then
#requotes before evaluating.
MyFunc2(1:4, EvaluateThis3)
My question is:
Is option 3 the best/most simple way to perform this evaluation
An explanation of what is happening
Edit
After reading #Rui Barradas very clear and concise answer I realised that I am actually trying to do someting similar to below which I didn't manage to make work using Rui's method but solved using environment setting
OtherStuff <-c(10, NA)
EvaluateThis4 <-quo(mean(c(vector,OtherStuff), na.rm = TRUE))
MyFunc3 <- function(vector, TheFunction){
#uses the captire environment which doesn't contain the object vector
print(get_env(TheFunction))
#Reset the enivronment of TheFunction to the current environment where vector exists
TheFunction<- set_env(TheFunction, get_env())
print(get_env(TheFunction))
print(TheFunction)
TheFunction %>%
eval_tidy
}
MyFunc3(1:4, EvaluateThis4)
The function is evaluated within the current environment not the capture environment. Because there is no object "OtherStuff" within that environment, the parent environments are searched finding "OtherStuff" in the Global environment.
I will try to answer to question 1.
I believe that the best and simpler way to perform this kind of evaluation is to do without any sort of fancy evaluation techniques. To call the function directly usually works. Using your example, try the following.
EvaluateThis4 <- mean # simple
MyFunc4 <- function(vector, TheFunction){
print(TheFunction)
TheFunction(vector) # just call it with the appropriate argument(s)
}
MyFunc4(1:4, EvaluateThis4)
function (x, ...)
UseMethod("mean")
<bytecode: 0x000000000489efb0>
<environment: namespace:base>
[1] 2.5
There are examples of this in base R. For instance approxfun and ecdf both return functions that you can use directly in your code to perform subsequent calculations. That's why I've defined EvaluateThis4 like that.
As for functions that use functions as arguments, there are the optimization ones, and, of course, *apply, byand ave.
As for question 2, I must admit to my complete ignorance.
I'm trying to use the snow and snowfall packages, specifically the sfSapply() function to extract data from multiple raster files. It looks something like this:
queue <- list(rast1, rast2, rast3)
sfInit(parallel=TRUE, cpus=3)
sfLibrary(raster)
sfLibrary(rgdal)
sfLibrary(sp)
a <- sfSapply(queue, extract, sp=TRUE, fun=mean, y=tracts)
sfStop()
The fun argument passed to sfSapply() is intended for the extract() function (in the raster library). However, sfSapply() also takes a fun argument (extract()); in this example, I've provided it as the second positional argument.
How can I specify a fun argument for the passed function and not have it confused with the fun argument expected by sfSapply()?
One workaround is to create a custom extract function that has these arguments built in:
sfRasterExtract=function(raster_obj){
extract(raster_obj, sp=TRUE, fun=mean, y=tracts)
}
Make sure to use sfExportAll() after sfInit to import the function to all instances you will use, then run
a <- sfSapply(queue, sfRasterExtract)
I want a function with parameters such as data name (dat), factor(myfactor), variable names(myvar) to dynamically generate histograms (have to use lattice).
Using IRIS as a minimal example:
data(iris)
my_histogram <- function(myvar,myfactor,dat){
listofparam <- c(myvar,myfactor)
myf <- as.formula(paste("~",paste(listofparam,collapse="|")))
histogram(myf,
data=dat,
main=bquote(paste(.(myvar),"distribution by",.(myfactor),seq=" ")))}
my_histogram("Sepal.Length","Species","iris")
I also tried do.call as some posts indicated:
my_histogram <- function(myvar,myfactor,dat){
listofparam <- c(myvar,myfactor)
myf <- as.formula(paste("~",paste(listofparam,collapse="|")))
p <- do.call("histogram",
args = list(myf,
data=dat))
print(p)
}
my_histogram("Sepal.Length","Species","iris")
But the error appears: invalid 'envir' argument of type 'character'. I think the program doesn't know where to look for thismyf` string. How can I fix this or there's a better way?
Readers of this should be aware that the question has completely mutated from an earlier version and doesn't really match up with this answer anymore. The answer to the new question appears in the comments.
There is no object named Sepal.Length. (So R is creating an error even before that my_function gets called.) There is only a column name and it would need to be quoted to pass it to a function. (The data object could not be created because that URL fails to deliver the data. Why aren't you using the built-in copy of the iris data object?
You will also need to build a formula from myvar and fac. Formulas are expressions and get parsed without evaluation of their tokens. You need to build a formula inside your function that looks like: ~Sepal.Length|Species and then pass it to the histogram call. Consult ?as.formula