I built a function whose return value is named based on its contents. Using as.name() works in the console, but not as a function argument.
x <- "newNameforIris"
assign(x, iris)
as.name(x)
# [1] newNameforIris
head(newNameforIris) # gives familiar results (not included)
save(as.name(x), file = "nnfi.bin")
# [1] Error in save(as.name(x), file = "nnfi.bin") : object ‘as.name(x)’ not found
I also tried eval.promises = FALSE, but to no avail. I don't know the name of the object until the function executes, so I am stuck without as.name() or an alternative.
I discovered this question 2.5 years after it was asked, and because there was not satisfactory answer, I investigated the issue. Here are my findings.
Investigation:
Failure reproduced, but the issue is not just as.name().
x <- "newNameforIris"
assign(x, iris)
as.name(x)
head(newNameforIris) # as expected
save(as.name(x), file = "nnfi.bin")
# Error in save(as.name(x), file = NULL) : object ‘as.name(x)’ not found
save(as.character(x), file = "nnfi.bin")
# Error in save(as.character(x), file = NULL) : object ‘as.character(x)’ not found
save(eval(as.name(x)), file = "nnfi.bin")
# Error in save(eval(as.name(x)), file = "nnfi.bin") : object ‘eval(as.name(x))’ not found
The following succeed.
y <- as.name(x)
save(y, file = "nnfi.bin")
save("x", file = "nnfi.bin")
save(list=c("x"), file = "nnfi.bin")
save(list=c(as.character(as.name(x))), file = "nnfi.bin")
Conclusion:
The ... argument of save() can only accept symbols and characters strings just the as the help file says, "the names of the objects to be saved (as symbols or character strings)".
So let's look at how save() processes .... Just enter save, not save().
save
#....
# names <- as.character(substitute(list(...)))[-1L]
# list <- c(list, names)
#....
Now let's test this with as.name(x) and the other failed tests from above.
fx <- function(..., list = character()) {
names <- as.character(substitute(list(...)))[-1L]
list <- c(list, names)
return(list)
}
fx(as.name(x)) # [1] "as.name(x)"
fx(as.character(x)) # [1] "as.character(x)"
fx(eval(as.name(x))) # [1] "eval(as.name(x))"
Answer:
Items in the ... argument of save() are not evaluated, but rather turned into character strings, so unless these strings are the same as existing objects, the function call will fail.
Suggestion:
Use the following.
x <- as.name(x)
save(x, file = "nnfi.bin")
This is because the class of as.name(x) is name.
class(as.name(x))
# [1] "name"
Try:
save(get(x), file = "nnfi.bin")
Related
When you save a variable in an R data file using save, it is saved under whatever name it had in the session that saved it. When I later go to load it from another session, it is loaded with the same name, which the loading script cannot possibly know. This name could overwrite an existing variable of the same name in the loading session. Is there a way to safely load an object from a data file into a specified variable name without risk of clobbering existing variables?
Example:
Saving session:
x = 5
save(x, file="x.Rda")
Loading session:
x = 7
load("x.Rda")
print(x) # This will print 5. Oops.
How I want it to work:
x = 7
y = load_object_from_file("x.Rda")
print(x) # should print 7
print(y) # should print 5
If you're just saving a single object, don't use an .Rdata file, use an .RDS file:
x <- 5
saveRDS(x, "x.rds")
y <- readRDS("x.rds")
all.equal(x, y)
I use the following:
loadRData <- function(fileName){
#loads an RData file, and returns it
load(fileName)
get(ls()[ls() != "fileName"])
}
d <- loadRData("~/blah/ricardo.RData")
You can create a new environment, load the .rda file into that environment, and retrieve the object from there. However, this does impose some restrictions: either you know what the original name for your object is, or there is only one object saved in the file.
This function returns an object loaded from a supplied .rda file. If there is more than one object in the file, an arbitrary one is returned.
load_obj <- function(f)
{
env <- new.env()
nm <- load(f, env)[1]
env[[nm]]
}
You could also try something like:
# Load the data, and store the name of the loaded object in x
x = load('data.Rsave')
# Get the object by its name
y = get(x)
# Remove the old object since you've stored it in y
rm(x)
Similar to the other solutions above, I load variables into an environment variable. This way if I load multiple variables from the .Rda, those will not clutter my environment.
load("x.Rda", dt <- new.env())
Demo:
x <- 2
y <- 1
save(x, y, file = "mydata.Rda")
rm(x, y)
x <- 123
# Load 'x' and 'y' into a new environment called 'dt'
load("mydata.Rda", dt <- new.env())
dt$x
#> [1] 2
x
#> [1] 123
Rdata file with one object
assign('newname', get(load('~/oldname.Rdata')))
In case anyone is looking to do this with a plain source file, rather than a saved Rdata/RDS/Rda file, the solution is very similar to the one provided by #Hong Ooi
load_obj <- function(fileName) {
local_env = new.env()
source(file = fileName, local = local_env)
return(local_env[[names(local_env)[1]]])
}
my_loaded_obj = load_obj(fileName = "TestSourceFile.R")
my_loaded_obj(7)
Prints:
[1] "Value of arg is 7"
And in the separate source file TestSourceFile.R
myTestFunction = function(arg) {
print(paste0("Value of arg is ", arg))
}
Again, this solution only works if there is exactly one file, if there are more, then it will just return one of them (probably the first, but that is not guaranteed).
I'm extending the answer from #ricardo to allow selection of specific variable if the .Rdata file contains multiple variables (as my credits are low to edit an answer). It adds some lines to read user input after listing the variables contained in the .Rdata file.
loadRData <- function(fileName) {
#loads an RData file, and returns it
load(fileName)
print(ls())
n <- readline(prompt="Which variable to load? \n")
get(ls()[as.integer(n)])
}
select_var <- loadRData('Multiple_variables.Rdata')
Following from #ricardo, another example of using (effectively) a separate environment
load_rdata <- function(file_path) {
res <- local({
load(file_path)
return(get(ls()))
})
return(res)
}
Similar caveats with only expects one object to be returned
I am trying to determine all the objects in a script. ( specifically to get all the dataframes but I'll settle for all the assigned objects ie vectors lists etc.)
Is there a way of doing this. Should I make the script run in its own session and then somehow get the objects from that session rather than rely on the global environment.
Use the second argument to source() when you execute the script. For example, here's a script:
x <- y + 1
z <- 2
which I can put in script.R. Then I will execute it in its own environment using the following code:
x <- 1 # This value will *not* change
y <- 2 # This value will be visible to the script
env <- new.env()
source("script.R", local = env)
Now I can print the values, and see that the comments are correct
x # the original one
# [1] 1
ls(env) # what was created?
# [1] "x" "z"
env$x # this is the one from the script
# [1] 3
I had a similar question and found an answer. I am copying the answer from my other post here.
I wrote the following function, get.objects(), that returns all the objects created in a script:
get.objects <- function(path2file = NULL, exception = NULL, source = FALSE, message = TRUE) {
library("utils")
library("tools")
# Step 0-1: Possibility to leave path2file = NULL if using RStudio.
# We are using rstudioapi to get the path to the current file
if(is.null(path2file)) path2file <- rstudioapi::getSourceEditorContext()$path
# Check that file exists
if (!file.exists(path2file)) {
stop("couldn't find file ", path2file)
}
# Step 0-2: If .Rmd file, need to extract the code in R chunks first
# Use code in https://felixfan.github.io/extract-r-code/
if(file_ext(path2file)=="Rmd") {
require("knitr")
tmp <- purl(path2file)
path2file <- paste(getwd(),tmp,sep="/")
source = TRUE # Must be changed to TRUE here
}
# Step 0-3: Start by running the script if you are calling an external script.
if(source) source(path2file)
# Step 1: screen the script
summ_script <- getParseData(parse(path2file, keep.source = TRUE))
# Step 2: extract the objects
list_objects <- summ_script$text[which(summ_script$token == "SYMBOL")]
# List unique
list_objects <- unique(list_objects)
# Step 3: find where the objects are.
src <- paste(as.vector(sapply(list_objects, find)))
src <- tapply(list_objects, factor(src), c)
# List of the objects in the Global Environment
# They can be in both the Global Environment and some packages.
src_names <- names(src)
list_objects = NULL
for (i in grep("GlobalEnv", src_names)) {
list_objects <- c(list_objects, src[[i]])
}
# Step 3bis: if any exception, remove from the list
if(!is.null(exception)) {
list_objects <- list_objects[!list_objects %in% exception]
}
# Step 4: done!
# If message, print message:
if(message) {
cat(paste0(" ",length(list_objects)," objects were created in the script \n ", path2file,"\n"))
}
return(list_objects)
}
To run it, you need a saved script. Here is an example of a script:
# This must be saved as a script, e.g, "test.R".
# Create a bunch of objects
temp <- LETTERS[1:3]
data <- data.frame(x = 1:10, y = 10:1)
p1 <- ggplot(data, aes(x, y)) + geom_point()
# List the objects. If you want to list all the objects except some, you can use the argument exception. Here, I listed as exception "p1.
get.objects()
get.objects(exception = "p1", message = FALSE)
Note that the function also works for external script and R markdown.
If you run an external script, you will have to run the script before. To do so, change the argument source to TRUE.
I am new in language R,I found something special with it.
When using the method rm(),I wonder why I can't pass ls() as a parameter.
while using rm(list = ls()) will pass the compilation.
The method ls() will return a data whose type is List,won't it ?
It is the first time that I ask a question at foreign website, and my English is terrible, sorry! Waiting for your answers!
It has to do with the ... special argument in R (AKA "dot-dot-dot" or "ellipsis"). ... captures all unnamed arguments (as well as undocumented named arguments), "positionnally".
See ?rm for its arguments: rm(..., list = character(), pos = -1, envir = as.environment(pos), inherits = FALSE).
Since ... is the first argument, it captures ls() in rm(ls()).
But there are expectations on ... as you can see in the source code of rm (simply type rm at the command line):
function (..., list = character(), pos = -1, envir = as.environment(pos),
inherits = FALSE)
{
dots <- match.call(expand.dots = FALSE)$...
if (length(dots) && !all(vapply(dots, function(x) is.symbol(x) ||
is.character(x), NA, USE.NAMES = FALSE)))
stop("... must contain names or character strings")
names <- vapply(dots, as.character, "")
if (length(names) == 0L)
names <- character()
list <- .Primitive("c")(list, names)
.Internal(remove(list, envir, inherits))
}
Here it is is.symbol() that fails.
Maybe it will be easier with an example:
foo <- 1L
bar <- 2L
rm(ls())
# Error
ls()
# [1] "bar" "foo"
rm(c("foo", "bar"))
# Same error
rm("foo", "bar")
# OK
If you want to investigate further, I suggest: debugonce(rm) then rm(ls()) then step by step execution (easier in an IDE like RStudio).
When you save a variable in an R data file using save, it is saved under whatever name it had in the session that saved it. When I later go to load it from another session, it is loaded with the same name, which the loading script cannot possibly know. This name could overwrite an existing variable of the same name in the loading session. Is there a way to safely load an object from a data file into a specified variable name without risk of clobbering existing variables?
Example:
Saving session:
x = 5
save(x, file="x.Rda")
Loading session:
x = 7
load("x.Rda")
print(x) # This will print 5. Oops.
How I want it to work:
x = 7
y = load_object_from_file("x.Rda")
print(x) # should print 7
print(y) # should print 5
If you're just saving a single object, don't use an .Rdata file, use an .RDS file:
x <- 5
saveRDS(x, "x.rds")
y <- readRDS("x.rds")
all.equal(x, y)
I use the following:
loadRData <- function(fileName){
#loads an RData file, and returns it
load(fileName)
get(ls()[ls() != "fileName"])
}
d <- loadRData("~/blah/ricardo.RData")
You can create a new environment, load the .rda file into that environment, and retrieve the object from there. However, this does impose some restrictions: either you know what the original name for your object is, or there is only one object saved in the file.
This function returns an object loaded from a supplied .rda file. If there is more than one object in the file, an arbitrary one is returned.
load_obj <- function(f)
{
env <- new.env()
nm <- load(f, env)[1]
env[[nm]]
}
You could also try something like:
# Load the data, and store the name of the loaded object in x
x = load('data.Rsave')
# Get the object by its name
y = get(x)
# Remove the old object since you've stored it in y
rm(x)
Similar to the other solutions above, I load variables into an environment variable. This way if I load multiple variables from the .Rda, those will not clutter my environment.
load("x.Rda", dt <- new.env())
Demo:
x <- 2
y <- 1
save(x, y, file = "mydata.Rda")
rm(x, y)
x <- 123
# Load 'x' and 'y' into a new environment called 'dt'
load("mydata.Rda", dt <- new.env())
dt$x
#> [1] 2
x
#> [1] 123
Rdata file with one object
assign('newname', get(load('~/oldname.Rdata')))
In case anyone is looking to do this with a plain source file, rather than a saved Rdata/RDS/Rda file, the solution is very similar to the one provided by #Hong Ooi
load_obj <- function(fileName) {
local_env = new.env()
source(file = fileName, local = local_env)
return(local_env[[names(local_env)[1]]])
}
my_loaded_obj = load_obj(fileName = "TestSourceFile.R")
my_loaded_obj(7)
Prints:
[1] "Value of arg is 7"
And in the separate source file TestSourceFile.R
myTestFunction = function(arg) {
print(paste0("Value of arg is ", arg))
}
Again, this solution only works if there is exactly one file, if there are more, then it will just return one of them (probably the first, but that is not guaranteed).
I'm extending the answer from #ricardo to allow selection of specific variable if the .Rdata file contains multiple variables (as my credits are low to edit an answer). It adds some lines to read user input after listing the variables contained in the .Rdata file.
loadRData <- function(fileName) {
#loads an RData file, and returns it
load(fileName)
print(ls())
n <- readline(prompt="Which variable to load? \n")
get(ls()[as.integer(n)])
}
select_var <- loadRData('Multiple_variables.Rdata')
Following from #ricardo, another example of using (effectively) a separate environment
load_rdata <- function(file_path) {
res <- local({
load(file_path)
return(get(ls()))
})
return(res)
}
Similar caveats with only expects one object to be returned
I want to read a lot of text files in a folder using read.table in R, but there are some blank file among those text files, errors coms when I using the following code.
filenames<-list.files("M:/files/test1",pattern=".txt");
datalist<-lapply(filenames,function(name){
read.table(paste("M:/files/test1/",name,sep=""),head=FALSE,stringsAsFactors=FALSE,sep="\t")
})
The easiest way to do this is to add a simple error-catching mechanism using try:
datalist<-lapply(filenames,function(name){
x <- try(read.table(paste("M:/files/test1/",name,sep=""),head=FALSE,stringsAsFactors=FALSE,sep="\t"))
if(inherits(x, "try-error"))
return(NULL)
else
return(x)
})
To see this in action, try a toy example. What try does is return the object, or in the event of an error a special object class containing details of the error:
x <- try(stop("Test error"))
inherits(x, "try-error")
x
# [1] "Error in try(stop(\"Test error\")) : Test error\n"
# attr(,"class")
# [1] "try-error"
# attr(,"condition")
# <simpleError in doTryCatch(return(expr), name, parentenv, handler): Test error>
Versus if you simply introduce an error without try the program will stop and x will be undefined:
rm(x)
x <- stop("Test error")
# Error: Test error
x
# Error: object 'x' not found
If the operation succeeds inside try(), it simply returns the correct object:
x <- try(1)
x
# [1] 1
skip empty files
test the size of each file, and skip the file size of 0
for (file in list.files(,"*.txt")){
if (file.size(file) == 0) next
print(file)
}