如何从R的apply函数中访问全局/外部作用域变量?

29
我似乎无法让 apply 函数访问/修改在外部声明的变量... 怎么回事?
    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))

结果:

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

1
你需要将变量传递给 testme 函数,然后再传递给 apply 函数:testme <- function(x, z) {apply(df, 1, function(x, i, z) {}, i, z) - bdemarest
@bdemarest:这样做行不通,因为i的值将在apply的每次迭代(即对于df的每一行)时被重置。我认为OP想要跟踪他们正在处理哪一行。 - Ricardo Saporta
@RicardoSaporta,你说得很对。也许最好不要使用apply,而是使用标准的for循环:for (i in 1:nrow(df)) {...}。目前,我们只能猜测他/她试图解决的潜在问题。 - bdemarest
1
这只是一个测试片段,用来演示我遇到的问题 :-) 原来我应该将结果返回给调用者,即将 apply 调用的结果赋值给另一个变量。这是更好的函数式风格。 - fatdragon
2个回答

43
使用 <<- 操作符,您可以写入外部作用域中的变量:
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))

这里是结果:

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

请注意,使用<<-是危险的,因为它会打破作用域。只有在真正必要的情况下才这样做,并且如果确实需要这样做,请清楚地记录此行为(至少在较大的脚本中)。


8

在你的应用程序中尝试以下操作。尝试更改n的值。我认为对于i,它应该比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
}

输出

> 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     

我会使用assign而不是eval(parse(...)) - Roman Luštrik
@RomanLuštrik,我最初在答案中使用了assign,然后将其编辑为eval(parse((.)))。为什么您更喜欢后者? - Ricardo Saporta
我猜是出于宗教原因。请参阅“fortune”的条目。https://dev59.com/z2gu5IYBdhLWcg3w3ana 我想知道这个问题是否引发了@CarlWitthoft几分钟前提出的问题:https://dev59.com/nmYr5IYBdhLWcg3wdqGw - Roman Luštrik
@RomanLuštrik,今天关于它肯定有相当多的问题。我自己添加了一个,@DWin提出了一个很好的论点。将上述更改回assign() - Ricardo Saporta
@RicardoSaporta eval(parse(...)) 允许对列表中的元素进行操作,而 assign 不支持该操作,因此我更喜欢前者。 - Michiel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接