dynamically assign functions from list - r

Context:
I'm trying to set up some mock data testing and I have saved some mock data down as csvs in a directory called 'test/'.
Each file relates to a get_data type function e.g. the mock data for get_energy() is stored as test/get_energy.csv.
I'd like to use list.files() to assign functions to my environment that read the csv.
files<-list.files('test/')
for (name in substr(files,1,nchar(files)-4)){
assign(name,function(){read.csv(eval(parse(text=paste0('test/',name,'.csv')))) })
}
but when I try and see the source code for the function get_energy by running
get_energy
it returns
function(){read.csv(eval(parse(text=paste0('test/',name,'.csv'))))}
whereas I need it to evaluate the string expression so that it returns
function(){read.csv('test/get_energy.csv')}

Put the whole function definition in the string like so:
files <- list.files("test/", pattern = 'csv$')
for (name in substr(files,1,nchar(files)-4)) {
assign(name, eval(parse(text = paste0('function(){ read.csv(', '"test/', name, '.csv', '")}'))))
}

Related

Using for loop to write data frame as dta file in R

I have data frames in a list a and I want to use a loop to save these as both rda and write as dta. I don't get why I get the error message that object data frame cannot be found:
for (f in a) {
for (name in 1:length(filenames)) {
save(as.data.frame(f),file = paste("~/Dropbox/Data_Insert/Panels/",name,end_rda,sep=""))
write.dta(as.data.frame(f),file = paste("~/Dropbox/Data_Insert/Panels/",name,end_dta,sep=""))
}
}
Error in save(as.data.frame(f), file = paste("~/Dropbox/Data_Insert/Panels/", :
object ‘as.data.frame(f)’ not found
So by f, this would be indexing the data frame in the list? I did as.data.frame(f) because when I only used f, I got the message:
The object "dataframe" must have class data.frame
I changed the code to for f in a, but it still returns an error saying that as.data.frame(f) not found.
I think that this is what you are trying to do. I assume that a is a list of data frames and filenames is a character vector of the same length.
for (i in 1:length(a)) {
to_save = as.data.frame(a[[i]])
save(to_save, file = paste0("~/Dropbox/Data_Insert/Panels/", filenames[i], end_rda))
write.dta(to_save, file = paste0("~/Dropbox/Data_Insert/Panels/", filenames[i], end_dta))
}
Note that save preserves the name of the R object, so when you load any of these files it will be loaded into the workspace with the name to_save. This seems bad. For individual R objects I would strongly encourage you to use saveRDS and create .RDS files instead of save. See, e.g., Ricardo's answer to this question for an example of Rda vs RDS.

How to create R functions with private variables?

How do I create a set of R functions that all access the same private variable?
Let's say I want to create readSetting(key) and writeSetting(key,value) functions that both operate on the same hidden list settings. If I try it like so...
local( {
settings <- list()
readSetting <<- function ( key ) settings[[key]]
writeSetting <<- function ( key, value ) settings[[key]] = value
} )
...then readSetting and writeSetting are not visible outside of the local call. If I want them to be visible there, I have to first assign
readSetting <- writeSetting <- NULL
outside the local call. There must be a better way, because my code isn't DRY if I have to say in two different ways which variables are public.
(The context of this work is that I'm developing an R package, and this code will be in an auxiliary file loaded into the main file via source.)
This question is related to How to limit the scope of the variables used in a script? but the answers there do not solve my problem.
You can simulate somthing like that using R6Class package and the following very rough code:
Privates <- R6Class("Privates",
public=list(
readSetting = function(key) {
private$settings[[key]]
},
writeSetting = function(key,value) {
private$settings[[key]] <<- value
}
),
private=list(
settings = list()
)
)
a <- Privates$new()
a$writeSetting("a",4)
a$readSetting("a")
Directly reading o setting the a$setting would not work.

How to read all the files in a folder using R and create objects with the same file names?

I need to create a function in R that reads all the files in a folder (let's assume that all files are tables in tab delimited format) and create objects with same names in global environment. I did something similar to this (see code below); I was able to write a function that reads all the files in the folder, makes some changes in the first column of each file and writes it back in to the folder. But the I couldn't find how to assign the read files in to an object that will stay in the global environment.
changeCol1 <- function () {
filesInfolder <- list.files()
for (i in 1:length(filesInfolder)){
wrkngFile <- read.table(filesInfolder[i])
wrkngFile[,1] <- gsub(0,1,wrkngFile[,1])
write.table(wrkngFile, file = filesInfolder[i], quote = F, sep = "\t")
}
}
You are much better off assigning them all to elements of a named list (and it's pretty easy to do, too):
changeCol1 <- function () {
filesInfolder <- list.files()
lapply(filesInfolder, function(fname) {
wrkngFile <- read.table(fname)
wrkngFile[,1] <- gsub(0, 1, wrkngFile[,1])
write.table(wrkngFile, file=fname, quote=FALSE, sep="\t")
wrkngFile
}) -> data
names(data) <- filesInfolder
data
}
a_list_full_of_data <- changeCol1()
Also, F will come back to haunt you some day (it's not protected where FALSE and TRUE are).
add this to your loop after making the changes:
assign(filesInfolder[i], wrkngFile, envir=globalenv())
If you want to put them into a list, one way would be, outside your loop, declare a list:
mylist = list()
Then, within your loop, do like so:
mylist[[filesInfolder[i] = wrkngFile]]
And then you can access each object by looking at:
mylist[[filename]]
from the global env.

How do I save the contents of ellipsis in R to be used in deeply nested function calls?

I'm trying to write a function that iterates through all the files in the immediate sub-directories of a directory, and then runs a custom-written function to extract features from the text of the files:
load.samples <- function(path = ".", ...) {
dirs <- dir(path, full.names = TRUE)[file.info(dir(path, full.names = TRUE))$isdir]
lapply(dirs, function(p) {
lapply(dir(p, full.names = TRUE), function(f) {
text <- readChar(f, file.info(f)$size)
extractFeatures(text, ...) #I want this call to pass along the extra parameters to load.samples
})
})
}
I'm trying to have any additional arguments passed to the load.samples function be carried through to the extractFeatures function. However, the code above obviously doesn't work, because instead of being directly called, the call to extractFeatures is wrapped inside two nested functions that are called via two nested calls to lapply, and I get an unused argument error whenever I try to pass any extra parameters to load.samples.
What is the simplest way to save the value of the ellipses to be used in such nested functions?
Change the first line to this:
load.samples <- function(..., path = ".") {
The trick is that ... should come after the regular parameters, but before the named parameters.

How to pass object in nested functions?

I'm trying to override save() in R so that it creates any missing directories before saving an object. I'm having trouble passing an object through one function to another using the ellipsis method.
My example:
save <- function(...,file){ #Overridden save()
target.dir <- dirname(file) #Extract the target directory
if(!file.exists(target.dir)) {
#Create the target directory if it doesn't exist.
dir.create(target.dir,showWarnings=T,recursive=T)
}
base::save(...,file=file.path(target.dir,basename(file)))
}
fun1 <- function(obj) {
obj1 <- obj + 1
save(obj1,file="~/test/obj.RData")
}
fun1(obj = 1)
The code above results in this error:
Error in base::save(..., file = file.path(target.dir, basename(file))) :
object ‘obj1’ not found
I realize that the problem is that the object 'obj1' doesn't exist inside my custom save() function, but I haven't yet figured out how to pass it from fun1 to base::save.
I have tried:
base::save(parent.frame()$...,file=file.path(target.dir,basename(file)))
and:
base::save(list=list(...),file=file.path(target.dir,basename(file)))
with no success.
Any suggestions?
You need to specify the parent's environment to 'base::save' :
save <- function(...,file){ #Overridden save()
target.dir <- dirname(file) #Extract the target directory
if(!file.exists(target.dir)) {
#Create the target directory if it doesn't exist.
dir.create(target.dir,showWarnings=T,recursive=T)
}
base::save(...,file=file.path(target.dir,basename(file)),envir=parent.frame())
}
Note the parameter added to the base::save call.
fun1 <- function(obj) {
obj1 <- obj + 1
save(obj1,file="~/test/obj.RData")
}
In addition, use '=' to specify parameter names:
fun1(obj = 1)

Resources