0

I am scratching my head at the following problem:

I am creating two functions inside a for loop with parameters that depend on some dataframe. Each function is then put inside a list.

Printing the parameters inside the for loop shows that eachh function is well defined. Yet, when I use those outside of the loop, only the last parameters are used for both functions. The following example should make that clearer.

dt <- data.frame(color = c("red", "blue"),
                 a = c(3,9),
                 b = c(1.3, 1.8))
function_list <- list()
for (col in dt$color) {
  a <- dt$a[dt$color == col]
  b <- dt$b[dt$color == col]

  foo <- function(x) {
      a*x^b
  }
  print(paste(col, foo(1)))
  function_list[[col]] <- foo
}

[1] "red 3"

[1] "blue 9"

function_list[["red"]](1)

[1] 9

function_list[["blue"]](1)

[1] 9

To note, this is inspired from the following question: R nested for loop to write multiple functions and plot them

The equivalent solution with assign and get works (my answer to the previous question).

Community
  • 1
  • 1
Choubi
  • 640
  • 3
  • 9
  • 1
    I'm not sure why you are surprised. The relevant values of `a` and `b` are those when you call the function and not when you define it. – Roland Aug 08 '16 at 08:27
  • @Roland what is the correct way of dynamically assigning the constant values within the function? – thepule Aug 08 '16 at 08:33
  • I was mostly surprised by the fact that the seemingly equivalent solution with assign and get worked. I now realise that the solution using assign and get given previously worked solely from the coincidence that the plotting was done inside a new loop... Thanks. – Choubi Aug 08 '16 at 09:27

3 Answers3

0

The relevant values of a and b are those when you call the function and not when you define it. The way you create the list, they are taken from the global environment. The solution is to create closures. I'd use Map for this, but you can do the same with a for loop:

funs <- Map(function(a, b) function(x) a*x^b, a = dt$a, b = dt$b)

print(funs)
#[[1]]
#function (x) 
#a * x^b
#<environment: 0x000000000a9a4298>
#
#[[2]]
#function (x) 
#a * x^b
#<environment: 0x000000000a9a3728>

Notice the different environments.

environment(funs[[1]])$a
#[1] 3
environment(funs[[2]])$a
#[1] 9

funs[[1]](1)
#[1] 3
funs[[2]](1)
#[1] 9
Roland
  • 127,288
  • 10
  • 191
  • 288
0

Your confusion will be solved by going a bit deeper with Environments

Let's check why your code doesn't work. When I try to print(function_list), you can see that both of the functions stored will return a*x^b.

# Part 1 : Why it doesn't work
# --------------------------

print(function_list)
# $red
# function (x) 
# {
#   a * x^b
# }
# 
# $blue
# function (x) 
# {
#   a * x^b
# }

If you try to remove a and re-run the function, an error will be returned .

rm(a)
function_list[['red']](1)
# Error in function_list[["red"]](1) : object 'a' not found

.

And now to how to make your code work:

There is more than one way to make it work, most of which will require either playing around with your environments or changing the data structure.

One way to manage your environments - in such way that it will keep your values and not search for the variable in the global environment - is returning a function from the function.

# Part 2 : How to make it work
# ----------------------------

function_list <- list()
for (col in dt$color) {

  a <- dt$a[dt$color == col]
  b <- dt$b[dt$color == col]

  foo1 <- function(inner.a, inner.b) {
    return(function(x) {inner.a*x^inner.b})
  }

  foo2 <- foo1(a,b)
  print(paste(col, foo2(1)))

  function_list[[col]] <- foo2
}

Now, if we check what's in the function_list, you can see that the functions are in two environments

print(function_list)

# $red
# function (x) 
# {
#   inner.a * x^inner.b
# }
# <environment: 0x186fb40>
#   
#   $blue
# function (x) 
# {
#   inner.a * x^inner.b
# }
# <environment: 0x2536438>

The output is also as expected. And even when we remove a, it will still work as expected.

function_list[['red']](1) # 3
function_list[['blue']](1) # 9

rm(a)
function_list[['red']](1) #[1] 3
Deena
  • 5,925
  • 6
  • 34
  • 40
0

I think that the for loop does not create new environments (you can check this by print(environment) within the loop), so the values of a and b are taken by foo in the global environment where they are 9 and 1.8, i.e. their last assigned values.

user7669
  • 638
  • 4
  • 13