Using non-standard evaluation to call an argument in a nested function - r

I am trying to take an argument from a simple function "adder" and then use a loop to look at the effect of incrementing that argument.
I know there must be better approaches, such as building a single function that makes a longer data frame or maybe a nested loop without the second function... so I welcome those!
But what I'm more specifically interested is how to quote(?) and then parse(?) the argument, here called either "a" or "b" (but the function would declare them "arg_to_change") inside the new function, here called "change_of_adder_arguments".
adder <- function(a=1,b=2){
data.frame(t=1:100) %>% mutate(x=a*t, y=b*2)
}
change_of_adder_arguments <- function(arg_to_change) {
output <- list()
arg_to_change_enquo <- enquo(arg_to_change)
for (i in 1:5) {
output[[i]] <- ggplot(adder(!!arg_to_change_enquo := i), aes(x, y)) + geom_point()
}
return(output)
}
change_of_adder_arguments(a)
change_of_adder_arguments(b)
Error: Problem with mutate() input x.
x could not find function ":="
i Input x is a * t.
The nail in the coffin seems to be using the arg_to_change_enquo on the LHS of the assignment operator. I know there are many articles here about non-standard evaluation, but I have tried quote, enquo, bquote, parse/eval, sym, substitute, !!, {{}}, =, :=, assign and combinations of all these with no luck. My instinct is that the answer is in specifying which environment? If anybody knows of any good references that "ELI5" about enviroments, I would greatly appreciate it. Thanks!

You can use do.call and pass the arguments to change as a list.
library(ggplot2)
change_of_adder_arguments <- function(arg_to_change) {
output <- vector('list', 5)
arg_to_change_string <- deparse(substitute(arg_to_change))
for (i in 1:5) {
output[[i]] <- ggplot(do.call(adder, setNames(as.list(i),
arg_to_change_string)), aes(x, y)) + geom_point()
}
return(output)
}
plot <- change_of_adder_arguments(b)

Related

How can create a function using variables in a dataframe

I'm sure the question is a bit dummy (sorry)... I'm trying to create a function using differents variables I have stored in a Dataframe. The function is like that:
mlr_turb <- function(Cond_in, Flow_in, pH_in, pH_out, Turb_in, nm250_i, nm400_i, nm250_o, nm400_o){
Coag = (+0.032690 + 0.090289*Cond_in + 0.003229*Flow_in - 0.021980*pH_in - 0.037486*pH_out
+0.016031*Turb_in -0.026006*nm250_i +0.093138*nm400_o - 0.397858*nm250_o - 0.109392*nm400_o)/0.167304
return(Coag)
}
m4_turb <- mlr_turb(dataset)
The problem is when I try to run my function in a dataframe (with the same name of variables). It doesn't detect my variables and shows this message:
Error in mlr_turb(dataset) :
argument "Flow_in" is missing, with no default
But, actually, there is, also all the variables.
I think I missplace or missing some order in the function that gives it the possibility to take the variables from the dataset. I have searched a lot about that but I have not found any answer...
No dumb questions!
I think you're looking for do.call. This function allows you to unpack values into a function as arguments. Here's a really simple example.
# a simple function that takes x, y and z as arguments
myFun <- function(x, y, z){
result <- (x + y)/z
return(result)
}
# a simple data frame with columns x, y and z
myData <- data.frame(x=1:5,
y=(1:5)*pi,
z=(11:15))
# unpack the values into the function using do.call
do.call('myFun', myData)
Output:
[1] 0.3765084 0.6902654 0.9557522 1.1833122 1.3805309
You meet a standard problem when writing R that is related to the question of standard evaluation (SE) vs non standard evaluation (NSE). If you need more elements, you can have a look at this blog post I wrote
I think the most convenient way to write function using variables is to use variable names as arguments of the function.
Let's take again #Muon example.
# a simple function that takes x, y and z as arguments
myFun <- function(x, y, z){
result <- (x + y)/z
return(result)
}
The question is where R should find the values behind names x, y and z. In a function, R will first look within the function environment (here x,y and z are defined as parameters) then it will look at global environment and then it will look at the different packages attached.
In myFun, R expects vectors. If you give a column name, you will experience an error. What happens if you want to give a column name ? You must say to R that the name you gave should be associated to a value in the scope of a dataframe. You can for instance do something like that:
myFun <- function(df, col1 = "x", col2 = "y", col3 = "z"){
result <- (df[,col1] + df[,col2])/df[,col3]
return(result)
}
You can go far further in that aspect with data.table package. If you start writing functions that need to use variables from a dataframe, I recommend you to start having a look at this package
I like Muon's answer, but I couldn't get it to work if there are columns in the data.frame not in the function. Using the with() function is a simple way to make this work as well...
#Code from Muon:
# a simple function that takes x, y and z as arguments
myFun <- function(x, y, z){
result <- (x + y)/z
return(result)
}
# a simple data frame with columns x, y and z
myData <- data.frame(x=1:5,
y=(1:5)*pi,
z=(11:15),
a=6:10) #adding a var not used in myFun
# unpack the values into the function using do.call
do.call('myFun', myData)
#generates an error for the unused "a" column
#using with() function:
with(myData, myFun(x, y, z))

R: Reference list item within the same list

In R, we can reference items created within that same list, i.e.:
list(a = a <- 1, b = a)
I am curious if there is a way to write a function which takes the place of a = a <- 1. That is, if something like
`%=%` <- function(x,y) {
envir <- environment()
char_x <- deparse(substitute(x))
assign(char_x, y, parent.env(envir))
unlist(lapply(setNames(seq_along(x),char_x), function(T) y))
}
# does not work
list(a%=%1, b=a)
is possible in R (i.e. returns the list given above)?
edit: I think this boils down to asking, 'can we call list with a language object that preserves all aspects of manually coding list?' (specifically, assigns the list's names attribute the left-hand side of the language element).
It seems to me that below shows that such a solution is hopeless.
my_call <- do.call(substitute, list(expr(expr = {x = y}), list(x=quote(a), y=1)))
equals <- languageEl(my_call, which = 1)
str(equals)
do.call(list, list(equals))
Welp, the clever folk behind tibble have figured this out in their lst() function (also in package dplyr)
library(dplyr)
lst(a=1, b=a, c=c(3,4), d=c)
What a useful feature!

r function in function arguments + apply

I'm having troubles using several functions within the same one and calling the arguments generated. I'm using a more complicated function that can be simplified as followed:
func.essai <- function(x) {
g <- sample(seq(1,30), x)
i <- sample(x,1)
func.essai.2 <- function(y,i) {
z <- y+i
}
h <- sapply(g,func.essai.2(y,i))
}
sq <- seq(1,4)
lapply(sq, func.essai)
I'm using arguments that are generated at the beginning of func.essai (and that depend on x) as a fixed input for func.essai.2, here for i, and as a vector to go through on the sapply function, here for g. This code doesn't work as such -- it doesn't recognize y and/or i. How can I rewrite the code to do so?
I think the error you get is because of your use of sapply. This should work instead of your line containing sapply:
h <- sapply(g,func.essai.2, i)
See ?sapply, which tells you that you should provide additional arguments behind the function that you are applying.

R mapply with named arguments

One fear I have when using mapply in R is that I may mess up the order of arguments & hence unconsciously generate garbage results.
mydata<-data.frame(Temperature=foobar,Pressure=foobar2)
myfunction<-function(P,T)
{
....
}
mapply(FUN = myfunction,mydata$Temperature,mydata$Pressure)
Is there a way to utilize named arguments to avoid this sort of error via mapply?
If we need to match the function arguments, name the arguments for Map/mapply with the arguments of the function
mapply(FUN = myfunction,T=mydata$Temperature,P=mydata$Pressure)
We can apply the function directly instead of mapply though (based on the example provided below in my post)
do.call(myfunction, unname(mydata[2:1]))
data
mydata <- data.frame(Temperature = 1:5, Pressure = 16:20)
myfunction <- function(P, T) {P*5 + T*10}

Using ddply inside a function

I'm trying to make a function using ddply inside of it. However I can't get to work. This is a dummy example reproducing what I get. Does this have anything to do this bug?
library(ggplot2)
data(diamonds)
foo <- function(data, fac1, fac2, bar) {
res <- ddply(data, .(fac1, fac2), mean(bar))
res
}
foo(diamonds, "color", "cut", "price")
I don't believe this is a bug. ddply expects the name of a function, which you haven't really supplied with mean(bar). You need to write a complete function that calculates the mean you'd like:
foo <- function(data, fac1, fac2, bar) {
res <- ddply(data, c(fac1, fac2), function(x,ind){
mean(x[,ind]},bar)
res
}
Also, you shouldn't pass strings to .(), so I changed that to c(), so that you can pass the function arguments directly to ddply.
There are quite a few things wrong with your code, but the main issue is: you are passing column names as character strings.
Just doing a 'find-and-replace' with your parameters within the function yields:
res <- ddply(diamonds, .("color", "cut"), mean("price"))
If you understand how ddply works (I kind of doubt this, given the rest of the code), you will understand that this is not supposed to work: ignoring the error in the last part (the function), this should be (notice the lack of quotes: the .() notation is nothing more than plyr's way of providing the quotes):
res <- ddply(diamonds, .(color, cut), mean(price))
Fortunately, ddplyalso supports passing its second argument as a vector of characters, i.e. the names of the columns, so (once again disregarding issues with the last parameter), this should become:
foo <- function(data, facs, bar) {
res <- ddply(data, facs, mean(bar))
res
}
foo(diamonds, c("color", "cut"), "price")
Finally: the function you pass to ddply should be a function that takes as its first argument a data.frame, which will each time hold the part of you passed along data.frame (diamonds) for the current values of color and cut. mean("price") or mean(price) are neither. If you insist on using ddply, here's what you need to do:
foo <- function(data, facs, bar) {
res <- ddply(data, facs, function(dfr, colnm){mean(dfr[,colnm])}, bar)
res
}
foo(diamonds, c("color", "cut"), "price")

Resources