How can I reference the local environment within a function, in R?
To get the current environment, just call environment()
.
In general, sys.frame
returns any of the environments currently on the call stack, and sys.nframe
returns the current depth of the call stack. sys.frames
returns a list of all environments on the call stack.
environment(f)
returns the closure environment for a function f
(where it will look for functions and global variables).
parent.env(e)
returns the parent environment where it will look if a symbol is not found in e
.
f <- function() {
function() list(curEnv=environment(), parent=parent.env(environment()),
grandParent=parent.env(parent.env(environment())), callStack=sys.frames(),
callStackDepth=sys.nframe())
}
g <- function(f, n=2) if (n>2) g(f, n-1) else f()
floc <- f() # generate a local function
g(floc, 3) # call it
This will call the local function floc
with a stack depth of 3. It returns a list with the current environment, it's parent (the local environment in f
), and it's grand parent (where f
was defined, so globalenv
). It also returns the list of stack frames (environments). These are the environments for the recursive calls in g
(except the last one which is the current environment of floc
).
R: How can I save all objects within a functions local environment?
Use the indicated save
command.
outside = "not in function"
testFun <- function(){
a = 1
b = 2
c = 3
save(list = ls(all.names = TRUE), file = "environment.RData")
}
testFun()
load("environment.RData", e <- new.env())
ls(e)
## [1] "a" "b" "c"
Accessing variables in a function within a function
I do encourage you to read about lexical scoping,
but I think a good approach to avoid writing a lot of variables could be:
get_args_for <- function(fun, env = parent.frame(), inherits = FALSE, ..., dots) {
potential <- names(formals(fun))
if ("..." %in% potential) {
if (missing(dots)) {
# return everything from parent frame
return(as.list(env))
}
else if (!is.list(dots)) {
stop("If provided, 'dots' should be a list.")
}
potential <- setdiff(potential, "...")
}
# get all formal arguments that can be found in parent frame
args <- mget(potential, env, ..., ifnotfound = list(NULL), inherits = inherits)
# remove not found
args <- args[sapply(args, Negate(is.null))]
# return found args and dots
c(args, dots)
}
f_a <- function(b, c = 0, ..., d = 1) {
b <- b + 1
c(b = b, c = c, d = d, ...)
}
f_e <- function() {
b <- 2
c <- 2
arg_list <- get_args_for(f_a, dots = list(5))
do.call(f_a, arg_list)
}
> f_e()
b c d
3 2 1 5
Setting inherits = FALSE
by default ensures that we only get variables from the specified environment.
We could also set dots = NULL
when calling get_args_for
so that we don't pass all variables,
but leave the ellipsis empty.
Nevertheless, it isn't entirely robust,
because dots
is simply appended at the end,
and if some arguments are not named,
they could end up matched by position.
Also, if some values should be NULL
in the call,
it wouldn't be easy to detect it.
I would strongly advise against using these below inside an R package.
Not only will it be rather ugly,
you'll get a bunch of notes from R's CMD check regarding undefined global variables.
Other options.
f_a <- function() {
return(b + c)
}
f_e <- function() {
b <- 2
c <- 2
# replace f_a's enclosing environment with the current evaluation's environment
environment(f_a) <- environment()
d <- f_a()
d
}
> f_e()
[1] 4
Something like the above probably wouldn't work inside an R package,
since I think a package's functions have their enclosing environments locked.
Or:
f_a <- function() {
with(parent.frame(), {
b + c
})
}
f_e <- function() {
b <- 2
c <- 2
f_a()
}
> f_e()
[1] 4
That way you don't modify the other function's enclosing environment permanently.
However, both functions will share an environment,
so something like this could happen:
f_a <- function() {
with(parent.frame(), {
b <- b + 1
b + c
})
}
f_e <- function() {
b <- 2
c <- 2
d <- f_a()
c(b,d)
}
> f_e()
[1] 3 5
Where calling the inner function modifies the values in the outer environment.
Yet another option that is a bit more flexible,
since it only modifies the enclosing environment temporarily by using eval
.
However, there are certain R functions that detect their current execution environment through "daRk magic",
and cannot be fooled by eval
;
see this discussion.
f_a <- function() {
b <- b + 1
b + c
}
f_e <- function() {
b <- 2
c <- 2
# use current environment as enclosing environment for f_a's evaluation
d <- eval(body(f_a), list(), enclos=environment())
c(b=b, d=d)
}
> f_e()
b d
2 5
Global and local variables in R
Variables declared inside a function are local to that function. For instance:
foo <- function() {
bar <- 1
}
foo()
bar
gives the following error: Error: object 'bar' not found
.
If you want to make bar
a global variable, you should do:
foo <- function() {
bar <<- 1
}
foo()
bar
In this case bar
is accessible from outside the function.
However, unlike C, C++ or many other languages, brackets do not determine the scope of variables. For instance, in the following code snippet:
if (x > 10) {
y <- 0
}
else {
y <- 1
}
y
remains accessible after the if-else
statement.
As you well say, you can also create nested environments. You can have a look at these two links for understanding how to use them:
- http://stat.ethz.ch/R-manual/R-devel/library/base/html/environment.html
- http://stat.ethz.ch/R-manual/R-devel/library/base/html/get.html
Here you have a small example:
test.env <- new.env()
assign('var', 100, envir=test.env)
# or simply
test.env$var <- 100
get('var') # var cannot be found since it is not defined in this environment
get('var', envir=test.env) # now it can be found
do.call specify environment inside function
This seems to work, but i'm not sure if it has other implications I'm not considering:
fun_wrap1 <- function(){
funa1 <- function(x) x^2
funb1 <- function(x) x^3
lapply(c('funa1', 'funb1'), do.call, args=list(x=3), envir=environment())
}
fun_wrap1()
#[[1]]
#[1] 9
#
#[[2]]
#[1] 27
So this is essentially equivalent to having the lapply
statement as:
lapply(
c('funa1', 'funb1'),
function(f) do.call(f, args=list(x=3), envir=environment() )
)
Look up local variable in function called by do.call
1) Since b is not defined in Foo, Foo will look for b in the environment in which Foo was defined, not the environment in which it was called.
You can redefine Foo's environment. This will make a copy of Foo such that free variables in it will be looked up in the local environment. No packages are used.
local({
b <- 3
environment(Foo) <- environment()
print(Foo(2))
print(do.call(Foo, list(a = 2)))
})
## [1] 5
## [1] 5
2) If it is ok to modify Foo then other possibilities are to modify it by passing b as an additional argument or passing an environment as an additional argument and have Foo evaluate b in that environment.
Foo2 <- function(a, b) {
return(a + b)
}
local({
b <- 3
print(Foo2(2, b))
print(do.call(Foo2, list(a = 2, b = b)))
})
## [1] 5
## [1] 5
3) or
Foo3 <- function(a, envir = parent.frame()) {
return(a + envir$b)
}
local({
b <- 3
print(Foo3(2))
print(do.call(Foo3, list(a = 2)))
})
## [1] 5
## [1] 5
4) A variation of the above that only involves modifying the signature of Foo but not its body is the following (or use get("b", parent.frame())
if you want to allow it to look into the ancestors of the parent frame as well).
Foo4 <- function(a, b = parent.frame()$b) {
return(a + b)
}
local({
b <- 3
print(Foo4(2))
print(do.call(Foo4, list(a = 2)))
})
## [1] 5
## [1] 5
5) Another approach is to inject a statement into Foo using trace
and then remove it afterwards.
local({
b <- 3
on.exit(untrace(Foo))
trace(Foo, bquote(b <- .(b)), print = FALSE)
print(Foo(2))
print(do.call(Foo, list(a = 2)))
})
## [1] 5
## [1] 5
6) If we wrap the body of Foo in eval.parent(substitute({...})) that will effectively inject it into the caller giving it access to b. Also see Thomas Lumley article starting on page 11 of R News 1/3 .
Foo6 <- function(a) eval.parent(substitute({
return(a + b)
}))
local({
b <- 3
print(Foo6(2))
print(do.call(Foo6, list(a = 2)))
})
## [1] 5
## [1] 5
7) This is really the same as (6) under the hood except it wraps it nicely. This is the only one here that uses a package.
library(gtools)
Foo7 <- defmacro(a, expr = {
return(a + b)
})
local({
b <- 3
print(Foo7(2))
print(do.call(Foo7, list(a = 2)))
})
## [1] 5
## [1] 5
R Programming - creates variable in in the environment it was called
Try this:
createVariable <- function(var.name, var.value) {
assign(var.name,var.value,envir=parent.env(environment()))
}
Edit:
Some more details here and here.
With the initial solution, the variable is created in the global env because parent.env
is the environment in which the function is defined and the createVariable
function is defined in the global environment.
You might also want to try assign(var.name,var.value,envir=as.environment(sys.frames()[[1]]))
, which will create it in the highest test function calling createVariable
in your example (first one on the call stack), in that case however, you will need to remove print(testVar)
from testFunc
when you call testFunc2
because the variable only be created in the environment of testFunc2
, not testFunc
. I don't know if that's what you mean by at the level at which it's called
.
If you run this:
createVariable <- function(var.name, var.value) {
assign(var.name,var.value,envir=as.environment(sys.frames()[[1]]))
print("creating")
}
testFunc <- function() {
createVariable("testVar","test")
print("func1")
print(exists("testVar"))
}
testFunc2 <- function() {
testFunc()
print("func2")
print(exists("testVar"))
}
testFunc()
testFunc2()
You get
> testFunc()
[1] "creating"
[1] "func1"
[1] TRUE
> testFunc2()
[1] "creating"
[1] "func1"
[1] FALSE
[1] "func2"
[1] TRUE
Which means testVar
is in testFun2
's environment, not in testFunc
's. Creating a new environment as others say might be safer.
Related Topics
How to Install R Package from Private Repo Using Devtools Install_Github
How to Add an Inset (Subplot) to "Topright" of an R Plot
R Convert Between Zoo Object and Data Frame, Results Inconsistent for Different Numbers of Columns
Dependency 'Slam' Is Not Available When Installing Tm Package
Cannot Install R Packages in Jupyter Notebook
Change Text Color for Cells Using Tablegrob
Using R's Lm on a Dataframe with a List of Predictors
How to Find Index of Match Between Two Set of Data Frame
Reorder Rows Using Custom Order
How to Drop Unused Levels from a Data Frame
How to Compute Roc and Auc Under Roc After Training Using Caret in R
Disregarding Simple Warnings/Errors in Trycatch()
Setting the Color for an Individual Data Point
Glpk: No Such File or Directory Error When Trying to Install R Package
Explanation of R: Options(Expressions=) to Non-Computer Scientists