29

I can't seem to make apply function access/modify a variable that is declared outside... what gives?

    x = data.frame(age=c(11,12,13), weight=c(100,105,110))
    x

    testme <- function(df) {
        i <- 0
        apply(df, 1, function(x) {
            age <- x[1]
            weight <- x[2]
            cat(sprintf("age=%d, weight=%d\n", age, weight))
            i <- i+1   #this could not access the i variable in outer scope
            z <- z+1   #this could not access the global variable
        })
        cat(sprintf("i=%d\n", i))
        i
    }

    z <- 0
    y <- testme(x)
    cat(sprintf("y=%d, z=%d\n", y, z))

Results:

    age=11, weight=100
    age=12, weight=105
    age=13, weight=110
    i=0
    y=0, z=0
fatdragon
  • 2,211
  • 4
  • 26
  • 43
  • 1
    You need to pass the variables to `testme`, and then to `apply`: `testme <- function(x, z) {` and `apply(df, 1, function(x, i, z) {}, i, z)` – bdemarest Nov 30 '12 at 06:59
  • @bdemarest: that won't work as the value of `i` will be reset at iteration of `apply` (ie, for every row of `df`). I think the OP wants to track which row they are on – Ricardo Saporta Nov 30 '12 at 07:32
  • @RicardoSaporta, you are quite right. Probably the OP would be better off not using `apply`, but instead a standard `for` loop: `for (i in 1:nrow(df)) {...}`. Currently, we can only guess at the underlying problem he/she is trying to solve. – bdemarest Nov 30 '12 at 07:42
  • 1
    this was only a test snip to demonstrate the problem i had :-) It turns out that I should return the results back to the caller i.e. assign the result of the apply call to another variable. That's a better functional style. – fatdragon Nov 30 '12 at 08:12

2 Answers2

42

Using the <<- operator you can write to variables in outer scopes:

x = data.frame(age=c(11,12,13), weight=c(100,105,110))
x

testme <- function(df) {
    i <- 0
    apply(df, 1, function(x) {
        age <- x[1]
        weight <- x[2]
        cat(sprintf("age=%d, weight=%d\n", age, weight))
        i <<- i+1   #this could not access the i variable in outer scope
        z <<- z+1   #this could not access the global variable
    })
    cat(sprintf("i=%d\n", i))
    i
}

z <- 0
y <- testme(x)
cat(sprintf("y=%d, z=%d\n", y, z))

The result here:

age=11, weight=100
age=12, weight=105
age=13, weight=110
i=3
y=3, z=3

Note that the usage of <<- is dangerous, as you break up scoping. Do this only if really necessary and if you do, document that behavior clearly (at least in bigger scripts)

Thilo
  • 8,827
  • 2
  • 35
  • 56
8

try the following inside your apply. Experiment with the value of n. I believe that for i it should be one less than for z.

         assign("i", i+1, envir=parent.frame(n=2))
         assign("z", z+1, envir=parent.frame(n=3))



testme <- function(df) {
    i <- 0
    apply(df, 1, function(x) {
        age <- x[1]
        weight <- x[2]
        cat(sprintf("age=%d, weight=%d\n", age, weight))

        ## ADDED THESE LINES
         assign("i", i+1, envir=parent.frame(2))
         assign("z", z+1, envir=parent.frame(3))

    })
    cat(sprintf("i=%d\n", i))
    i
}

OUTPUT

> z <- 0
> y <- testme(x)
age=11, weight=100
age=12, weight=105
age=13, weight=110
i=3
> cat(sprintf("y=%d, z=%d\n", y, z))
y=3, z=3     
Ricardo Saporta
  • 54,400
  • 17
  • 144
  • 178
  • I would use `assign` over `eval(parse(...))`. – Roman Luštrik Nov 30 '12 at 10:52
  • @RomanLuštrik, I had originally put `assign` in the answer and then edited it to `eval(parse((.))`. Why your preference? – Ricardo Saporta Nov 30 '12 at 14:19
  • I guess religious reasons. See the entry on `fortune`. http://stackoverflow.com/questions/11025031/r-evalparse-is-often-suboptimal I wonder if this question sparked the question made by @CarlWitthoft a few minutes ago: http://stackoverflow.com/questions/13647046/avoiding-the-infamous-evalparse-construct – Roman Luštrik Nov 30 '12 at 14:45
  • @RomanLuštrik, there are certainly quite a few questions today on it. I [added one](http://stackoverflow.com/questions/13649979/what-specifically-are-the-dangers-of-evalparse#comment18731187_13649979) myself, and @DWin made a pretty good argument. Changed the above back to `assign()` – Ricardo Saporta Nov 30 '12 at 20:53
  • @RicardoSaporta `eval(parse(...))` allows addressing elements of a list as well, while `assign` doesn't support that, so I actually prefer the former option – Michiel Oct 15 '15 at 10:22