In R, How to Make the Variables Inside a Function Available to the Lower Level Function Inside This Function(With, Attach, Environment)

In R, how to make the variables inside a function available to the lower level function inside this function?(with, attach, environment)

(1) Pass caller's environment. You can explicitly pass the parent environment and index into it. Try this:

f2a <- function(P, env = parent.frame()) {
env$calls <- env$calls + 1
print(env$calls)
return(P + env$c + env$d)
}

a <- 1
b <- 2
# same as f1 except f2 removed and call to f2 replaced with call to f2a
f1a <- function(){
c <- 3
d <- 4
calls <- 0
v <- vector()
for(i in 1:10){
v[i] <- f2a(P=0)
c <- c+1
d <- d+1
}
return(v)
}
f1a()

(2) Reset called function's environment We can reset the environment of f2b in f1b as shown here:

f2b <- function(P) {
calls <<- calls + 1
print(calls)
return(P + c + d)
}

a <- 1
b <- 2
# same as f1 except f2 removed, call to f2 replaced with call to f2b
# and line marked ## at the beginning is new
f1b <- function(){
environment(f2b) <- environment() ##
c <- 3
d <- 4
calls <- 0
v <- vector()
for(i in 1:10){
v[i] <- f2b(P=0)
c <- c+1
d <- d+1
}
return(v)
}
f1b()

(3) Macro using eval.parent(substitute(...)) Yet another approach is to define a macro-like construct which effectively injects the body of f2c inline into f1c1. Here f2c is the same as f2b except for the calls <- calls + 1 line (no <<- needed) and the wrapping of the entire body in eval.parent(substitute({...})). f1c is the same as f1a except the call to f2a is replaced with a call to f2c .

f2c <- function(P) eval.parent(substitute({
calls <- calls + 1
print(calls)
return(P + c + d)
}))

a <- 1
b <- 2
f1c <- function(){
c <- 3
d <- 4
calls <- 0
v <- vector()
for(i in 1:10){
v[i] <- f2c(P=0)
c <- c+1
d <- d+1
}
return(v)
}
f1c()

(4) defmacro This is almost the same as the the last solution except it uses defmacro in the gtools package to define the macro rather than doing it ourself. (Also see the Rcmdr package for another defmacro version.) Because of the way defmacro works we must also pass calls but since it's a macro and not a function this just tells it to substitute calls in and is not the same as passing calls to a function.

library(gtools)

f2d <- defmacro(P, calls, expr = {
calls <- calls + 1
print(calls)
return(P + c + d)
})

a <- 1
b <- 2
f1d <- function(){
c <- 3
d <- 4
calls <- 0
v <- vector()
for(i in 1:10){
v[i] <- f2d(P=0, calls)
c <- c+1
d <- d+1
}
return(v)
}
f1d()

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

Inject variables automatically in R function environment

1) reset environment Convert pool to an environment and set the environment of f to it:

# inputs
pool <- list(u = 10)
f <- function(x) u + x

environment(f) <- list2env(pool, parent = environment(f))
f(1)
## [1] 11

2) proto Another possibility is to use the proto package. A proto object is an environment with modified methods. Convert pool to a proto object p and insert f into it. Inserting f into p resets its environment to p so:

library(proto)

p <- as.proto(pool, parent = environment(f))
p$f <- f
with(p, f(10)) # use the version of f inserted into p
## [1] 11

or we could overwrite f with the version of f in p:

library(proto)

p <- as.proto(pool, parent = environment(f))
p$f <- f
f <- with(p, f)
f(1)
## [1] 11

Update: Have set parent of new environment formed from pool to original environment of f so that free variables in f are first looked up in the environment formed from pool and then f's environment in that order rather than the environment derived from pool and then the parent frame.

make created variable available in parent environment in r

The last line of f will copy all variables in f to the specified environment. Alternately, replace as.list(environment()) with list("a", "b") or to only copy variables beginning with a lower case letter, say, as.list(ls(pattern = "^[a-z]"))

if (exists("a")) rm(a)
if (exists("b")) rm(b)

f <- function(envir = parent.frame()) {
a <- b <- 1
invisible( list2env(as.list(environment()), envir) )
}
f()
a
## [1] 1
b
## [1] 1

Rather than inject the variables directly into the parent another possibility which is a bit cleaner is to return the environment itself:

f2 <- function(envir = parent.frame()) {
a <- b <- 1
environment()
}
e <- f()
e$a
## [1] 1
e$b
## [1] 1

or return a list by replacing the last statement in f2 with:

list2env(environment())

equivalent of within(), attach() etc. for working within an environment?

What about with()? From here,

with(data, expr)

data is the data to use for constructing an environment. For the default with method this may be an environment, a list, a data frame, or an integer.

expr is the expression to evaluate.

with is a generic function that evaluates expr in a local environment constructed from data. The environment has the caller's environment as its parent. This is useful for simplifying calls to modeling functions. (Note: if data is already an environment then this is used with its existing parent.)

Note that assignments within expr take place in the constructed environment and not in the user's workspace.

with() returns value of the evaluated expr.



ee <- list2env(list(x=1,y=2))
with(ee, {
x <- x+1
y <- y+2
z <- 6
})

attach() inside function

Noah has already pointed out that using attach is a bad idea, even though you see it in some examples and books. There is a way around. You can use "local attach" that's called with. In Noah's dummy example, this would look like

with(params, print(a))

which will yield identical result, but is tidier.

How can I define a function with a foor loop inside and with a condfitional if statement inside the for loop?

We can change the function to

l <- function(x, ma) {
for(i in seq_along(ma)) {
if(i %% 2 == 1) {
ma[i] <- i + x
} else {
ma[i] <- 0
}
}
return(ma)
}

-testing

ma <- rep(0, 20)
l(5, ma)
#[1] 6 0 8 0 10 0 12 0 14 0 16 0 18 0 20 0 22 0 24 0

These are vectorized operators, so a for loop is not really required here

ifelse(seq_along(ma) %% 2 == 1, seq_along(ma) + 5, 0)
#[1] 6 0 8 0 10 0 12 0 14 0 16 0 18 0 20 0 22 0 24 0


Related Topics



Leave a reply



Submit