Store results of function in complex object (?) in R - r

everbody knows this functions:
fitted_lm = lm(mpg ~ wt, mtcars)
> class(fitted_lm)
[1] "lm"
fitted_lm$
In my global environment I see that fitted_lm is stored as a list, but when i call the class function on the object, I get "lm" as result. These object allows it to easily access different values with the "$"-sign. How can I store my own results that kind of way?
For example something like this:
complex_output <- function(x) {
row.means <- rowMeans(x)
col.means <- colMeans(x)
result <- list(row.means, col.means)
return(result)
}
complex_result <- complex_output(x = mtcars)
complex_result[[1]]
complex_result$ # does not work
I can of course access all results via double brackets, but it would be much more convenient to access them with the "$"-sign. How can I do that?

Try assigning names to the elements in the list generated by your function:
complex_output <- function(x) {
row.means <- rowMeans(x)
col.means <- colMeans(x)
result <- list(r=row.means, c=col.means)
return(result)
}
complex_result$r

Related

control scoping of arguments supplied to lm() from whithin a function called by lapply

I have a function that takes a dataset, extracts different variables, and then makes linear models from those variables (it expects the response in the last column). I want the data argument of the calls for these models to use objects in the global environment so that I can manipulate them with other functions outside this function. The following gives the expected behavior when provided with a single dataset.
make_mods <- function(dataset) {
make_mod <- function(x){
response <- names(dataset)[length(dataset)]
form <- paste0(response, " ~ ", x)
form <- as.formula(form)
bquote( lm(.(form), data = .(d_sub)) ) # Unevaluated to show output
}
d_sub <- substitute(dataset)
vars <- names(dataset)[-length(dataset)]
mods <- lapply(vars, make_mod)
return(mods)
}
# Make some different datasets
ex1 <- ex2 <- ex3 <- mtcars[c(3,4,6,1)]
new_data <- function(x) {
x + rnorm(length(x), mean = 0, sd = sd(x))
}
ex2[-length(ex2)] <- lapply(ex2[-length(ex2)], new_data)
ex3[-length(ex3)] <- lapply(ex3[-length(ex3)], new_data)
make_mods(ex1)
I also want to be able to use this function within lapply
# List of datasets for testing function with lapply
ex_l <- mget(c("ex1", "ex2", "ex3"))
lapply(ex_l, make_mods)
But here the model calls end up looking like this: lm(mpg ~ disp, data = X[[i]]) and, of course, this model call doesn't evaluate in the default environment (the actual function evaluates the model call in the function). The desired output is a list of lists of models that look like: lm(mpg ~ disp, data = ex_l[["ex1"]]), i.e., they have valid calls with data arguments that reference data frames in the global environment.
I've experimented with passing names to lapply and different wrapper functions for calling make_mods from lapply but it seems like my function, in using substitute only gives the expected behavior when called from the global environment. I'm new to working with scoping and environments. How can I get my function to give the desired lm call both when passed a data frame from the global environment, and when passed data frames from within lapply.
The only thing that I could think of was to add an if statement to my make mods function that tests if the input is a call or not. If it's a call, it expects it to be a call for a dataset in the global environment.
make_mods <- function(dataset) {
make_mod <- function(x){
response <- names(dataset)[length(dataset)]
form <- paste0(response, " ~ ", x)
form <- as.formula(form)
bquote( lm(.(form), data = .(d_sub)) )
}
if(is.call(dataset)) {
d_sub <- dataset
dataset <- eval(dataset)
} else {
d_sub <- substitute(dataset)
}
vars <- names(dataset)[-length(dataset)]
mods <- lapply(vars, make_mod)
return(mods)
}
Then I can use lapply like this:
out <- lapply(names(ex_l), function(x){
g <- bquote(ex_l[[.(x)]])
make_mods(g)
})
names(out) <- names(ex_l)
which gives me this:
$ex1
$ex1[[1]]
lm(mpg ~ disp, data = ex_l[["ex1"]])
$ex1[[2]]
lm(mpg ~ hp, data = ex_l[["ex1"]])
$ex1[[3]]
lm(mpg ~ wt, data = ex_l[["ex1"]])
<<output truncated>>
Maybe not an elegant solution, but it's working.

R saving function call with formula for reuse in bootstrapping

I've got some code that creates an object from a formula and saves the call for future use, as so:
create_obj <- function(formula, data) {
obj <- list()
# Populate obj with whatever we're interested in
# ...
# Save call for future use
obj$call <- match.call()
obj
}
obj <- create_obj(time ~ sex, data)
If I then bootstrap data I can easily build the model on the new dataset:
data <- data[sample(1:nrow(data), replace=T), ]
new_obj <- eval(obj$call)
However, if I have the formula saved in a variable and I pass the object into a new environment this won't work:
do_stuff <- function(object, newdata) {
data <- newdata[sample(1:nrow(newdata), replace=T), ]
new_object <- eval(object$call)
}
main <- function() {
my_form <- time ~ sex
obj2 <- create_obj(my_form, data)
# obj2$call: 'create_obj(formula = my_form, data = data)'
do_stuff(obj2, data)
}
Error: object my_form not found.
How can I have it so that obj$call saves time~sex rather than myform? Otherwise I need to pass the formula itself around rather than just the object, limiting the practicality.
The above example isn't reproducible but you can see the same thing with a standard lm call.
EDIT: I've solved the problem, see the accepted answer.
I've solved it by having the constructor function modify the call by evaluating the constant argument in the local environment:
create_obj <- function(formula, data) {
obj <- list()
# Populate obj with whatever we're interested in
# ...
# Save call for future use
func_call <- match.call()
func_call$formula <- eval(formula)
# obj_call is now: create_obj(formula=time~sex, data=data)
obj$call <- func_call
obj
}

Write a wrapper function to successfully take addition arguments (like subset) via ellipsis (...)

I am writing a function that calls another function (e.g. lm), and I would like to pass other
arguments to it using ellipsis (...). However, the data to be used is not
in the global environment, but inside a list. A minimal example:
L <- list(data = chickwts, other = 1:5)
wrapper <- function(list, formula = NULL, ...){
if (missing(formula)) formula <- formula(weight~feed)
lm(formula, data = list$data, ...)
}
wrapper(L, subset = feed != "casein") #fails
I can make it work using attach but I'm sure there is more efficient ways of doing it by specifying the evaluation frame...?
wrapper2 <- function(list, formula = NULL, ...){
if (missing(formula)) formula <- formula(weight~feed)
attach(list$data)
m <- lm(formula, ...)
detach(list$data)
return(m)
}
wrapper2(L, subset = feed != "casein") #works
Another solution I have used before is to use list(...), and dealing with the arguments manually, but that would not be practical in the real situation.
I can see that this is fairly basic, but I couldn't find a solution. Any suggestion to the specific problem and also a link to a good conceptual explanation of environments in general would be appreciated.
We would need to construct a call and eval it.
wrapper <- function(list, formula = NULL, ...){
if (missing(formula)) formula <- weight ~ feed
cl <- match.call()
cl$list <- NULL
cl$formula <- formula
cl$data <- quote(list$data)
cl[[1]] <- quote(stats::lm)
eval(cl)
}
Reproducible example:
L <- list(data = trees, other = 1:5)
wrapper(L, Height ~ Girth, subset = Volume > 20)

How to stop reference a data frame in a function

I want to build a function in such a way that once i supplied data='name of data frame' there is no need to write variable=data$variable as just writing variable name from the supplied data frame will serve the purpose
myfunction<-function(variable,data)
{
result=sum(data)/sum(variable)
return(result)
}
for example i have a data frame df
df<-data.frame(x=1:5,y=2:6,z=3:7,u=4:8)
I want to provide following input
myfunction(variable=x,data=df)
instead of below input to serve the purpose
myfunction(variable=df$x,data=df)
We can use non-standard evaluation:
myfunction <- function(variable, data) {
var <- eval(substitute(variable), data)
result = sum(data)/sum(var)
return(result)
}
# Test
myfunction(variable = x, data = df)
#[1] 6
The with or attach functions can help you here, see the ?with and ?attach documentation. Alternatively, you can supply the variable name as a character and use this in the function body. I.e. you can do something like this:
myfunction2 <- function(variable, data) {
result <- sum(data)/sum(data[[variable]])
return(result)
}
df <- data.frame(x=1:5,y=2:6,z=3:7,u=4:8)
myfunction2("x", df)
#[1] 6
Yet another resort is to use non-standard evaluation. A small example of this is something like:
myfunction3 <- function(variable, data) {
var.name <- deparse(substitute(variable))
result <- sum(data)/sum(data[[var.name]])
return(result)
}
myfunction3(variable = x, data = df)
#[1] 6

R: Source function by name/Import subset of functions

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")

Resources