在R语言中函数内部可以嵌套另一个函数

8
请您告诉我为什么代码会提示找不到“Samdat”?
我试图在不同的模型之间切换,因此声明了一个包含指定模型的函数,并且我只需要将这个函数作为 get.f 函数的一个参数调用,其中重新采样将改变模型中每个设计矩阵的结构。当已经找到“Samdat”时,代码会提示找不到它。
此外,是否有一种方法可以使条件语句 if(Model == M1()) 成立,而无需创建另一个参数 M 并设置 if(M==1)?
以下是我的代码:
dat <-  cbind(Y=rnorm(20),rnorm(20),runif(20),rexp(20),rnorm(20),runif(20), rexp(20),rnorm(20),runif(20),rexp(20))
nam <- paste("v",1:9,sep="")
colnames(dat) <- c("Y",nam)

M1 <- function(){
    a1 = cbind(Samdat[,c(2:5,7,9)])
    b1 = cbind(Samdat[,c(2:4,6,8,7)])
    c1 = b1+a1
    list(a1=a1,b1=b1,c1=c1)}

M2 <- function(){
    a1= cbind(Samdat[,c(2:5,7,9)])+2
    b1= cbind(Samdat[,c(2:4,6,8,7)])+2
    c1 = a1+b1
    list(a1=a1,b1=b1,c1=c1)}

M3 <- function(){
    a1= cbind(Samdat[,c(2:5,7,9)])+8
    b1= cbind(Samdat[,c(2:4,6,8,7)])+8
    c1 = a1+b1
    list(a1=a1,b1=b1,c1=c1)}
#################################################################
get.f <- function(asim,Model,M){
    sse <-c()
    for(i in 1:asim){
        set.seed(i)
        Samdat <- dat[sample(1:nrow(dat),nrow(dat),replace=T),]
        Y <- Samdat[,1]
        if(M==1){
            a2 <- Model$a1
            b2 <- Model$b1
            c2 <- Model$c1
            s<- a2+b2+c2
            fit <- lm(Y~s)
            cof <- sum(summary(fit)$coef[,1])
            coff <-Model$cof
            sse <-c(sse,coff)
        }
        else if(M==2){
            a2 <- Model$a1
            b2 <- Model$b1
            c2 <- Model$c1
            s<- c2+12
            fit <- lm(Y~s)
            cof <- sum(summary(fit)$coef[,1])
            coff <-Model$cof
            sse <-c(sse,coff)
        }
        else {
            a2 <- Model$a1
            b2 <- Model$b1
            c2 <- Model$c1
            s<- c2+a2
            fit <- lm(Y~s)
            cof <- sum(summary(fit)$coef[,1])
            coff <- Model$cof
            sse <-c(sse,coff)
        }
    }
    return(sse)
}

get.f(10,Model=M1(),M=1)
get.f(10,Model=M2(),M=2)
get.f(10,Model=M3(),M=3)

3
请您重新缩进代码以查看函数的起始和终止位置,这样或许也能帮助您解决问题。 - Gabor Csardi
我用一行空格分隔了,你说的重新缩进是什么意思? - Falcon-StatGuy
我所指的正是Jilber为您所做的。 - Gabor Csardi
不是我做的缩进,是@themel做的,应该归功于他。 :) - Jilber Urbina
2个回答

12
您可能需要查看R作用域规则。特别是,没有理由期望您在函数中定义的变量可见于其他函数中。

您可能会感到困惑,因为全局环境(即所有函数之外的顶层)是此规则的例外。我不会回答您的其他问题,但请注意,整个脚本对我来说看起来非常混乱 - 即从M1M3基本上是一个函数,而get.f中的复制/粘贴堆肯定很糟糕。无论您试图做什么,都可以以更简单的方式编写。

让我们首先看一下M - 为什么不使用参数一个函数?包括解决作用域问题的解决方案,这使得有两个参数 -

M <- function(sampleData, offset) { 
    a1 = sampleData[,c(2:5,7,9)] + offset
    b1 = sampleData[,c(2:4,6,8,7)] + offset
    c1 = b1+a1
    list(a1=a1,b1=b1,c1=c1)
}

如果您坚持要定义别名,您也可以像这样做。
M1 <- function(sampleData) M(sampleData, 0) 
M2 <- function(sampleData) M(sampleData, 2) 
M3 <- function(sampleData) M(sampleData, 8) 

这已经不那么重复了,但理想情况下,你希望计算机为你做重复的工作(DRY!):

offsets <- c(0,2,8)
Models <- sapply(offsets, FUN=function(offset) function(sampleData) M(sampleData, offset))

看一下get.f,不太清楚你想做什么——你想拟合一些东西并从结果中收集一些内容,但是Model$cof部分涉及到一个未定义的变量(你的Model只有a1b1c1条目)。假设你想实际收集cof并丢弃中间代码,get.f可能长这样:

M <- function(sampleData, offset) { 
    a1 = sampleData[,c(2:5,7,9)] + offset
    b1 = sampleData[,c(2:4,6,8,7)] + offset
    c1 = b1+a1
    list(a1=a1,b1=b1,c1=c1)
}

get.f <- function(asim,Model,M){
    sse <-c()
    for(i in 1:asim){
        set.seed(i)
        Samdat <- dat[sample(1:nrow(dat),nrow(dat),replace=T),]
        Y <- Samdat[,1]
        model <- Model()
        if(M==1){
            a2 <- model$a1
            b2 <- model$b1
            c2 <- model$c1
            s<- a2+b2+c2
            fit <- lm(Y~s)
            cof <- sum(summary(fit)$coef[,1])
            sse <-c(sse,cof)
        }
        else if(M==2){
            a2 <- model$a1
            b2 <- model$b1
            c2 <- model$c1
            s<- c2+12
            fit <- lm(Y~s)
            cof <- sum(summary(fit)$coef[,1])
            sse <-c(sse,cof)
        }
        else {
            a2 <- model$a1
            b2 <- model$b1
            c2 <- model$c1
            s<- c2+a2
            fit <- lm(Y~s)
            cof <- sum(summary(fit)$coef[,1])
            sse <-c(sse,cof)
        }
    }
    return(sse)
}


get.f(10,Model=M1,M=1) 
get.f(10,Model=M2,M=2)
get.f(10,Model=M3,M=3)

这段代码还是非常重复的,我们为什么不思考一下呢? 你所做的一切就是从样本中计算出一个列来用于拟合。我不明白为什么你需要在 M 函数中进行计算,然后在 get.f 中提取单个值(取决于你使用的具体M)——这似乎表明提取应该更多是M的一部分......但如果你需要保持它们分开,好吧,让我们使用独立的提取函数。仍然可以在写得合理的R代码中缩小到原来的一半大小:

# Set up test data
dat <-  cbind(Y=rnorm(20),rnorm(20),runif(20),rexp(20),rnorm(20),runif(20), rexp(20),rnorm(20),runif(20),rexp(20))
nam <- paste("v",1:9,sep="")
colnames(dat) <- c("Y",nam)

# calculate a1..c1 from a sample
M <- function(sampleData, offset) { 
    a1 = sampleData[,c(2:5,7,9)] + offset
    b1 = sampleData[,c(2:4,6,8,7)] + offset
    c1 = b1+a1
    list(a1=a1,b1=b1,c1=c1)
}

# create a fixed-offset model from the variable offset model by fixing offset
makeModel <- function(offset) function(sampleData) M(sampleData, offset)   

# run model against asim subsamples of data and collect coefficients
get.f <- function(asim,model,expected) 
    sapply(1:asim,  function (i){
        set.seed(i)
        Samdat <- dat[sample(1:nrow(dat),nrow(dat),replace=T),]
        Y <- Samdat[,1]
        s <- expected(model(Samdat))
        fit <- lm(Y~s)
        sum(summary(fit)$coef[,1])
    })

# list of models to run and how to extract the expectation values from the model reslts
todo <- list(
        list(model=makeModel(0), expected=function(data) data$a1+data$b1+data$c1),
        list(model=makeModel(2), expected=function(data) data$c1+12),
        list(model=makeModel(8), expected=function(data) data$c1+data$a1))

sapply(todo, function(l) { get.f(10, l$model, l$expected)})

谢谢你的回答。我尝试创建一个类似于我正在尝试做的示例。首先:函数M1-M3相似但不相同,我为每个函数添加了不同的常量。然而,在我编写实际工作时,它们具有不同的矩阵。我正在尝试做的是在get.f函数中调用这些函数(M1-M3)以使用M1-M3创建新矩阵。我会在对数据进行抽样后执行此操作,以便可以拟合新模型。我不明白你所说的可怕部分是什么。我有超过M1-M3(有M1-M30),因此无法只在get.f函数中编写它们。 - Falcon-StatGuy
嗨@themel,我已经用R语言创建了3个脚本。第一个脚本是用于对数据进行抽样,并为抽样数据创建不同的矩阵。然后在第二个脚本中,我创建了模型函数(M1-M3),这些函数组合了不同的矩阵并检查一些结果。第三个脚本是我创建的get.f,用于根据第二个脚本中的不同矩阵拟合不同的模型。我的主要目标是实现SSE结果。我大约有3000列。 - Falcon-StatGuy

6
当您调用时。
get.f(10, Model=M1(), M=1)

你的M1函数被立即调用并失败,因为在M1主体内部使用了Samdat,而后者在get.f的主体内部定义。

你需要想办法在Samdat被定义之后再调用M1。一种方法是将M1(函数)作为参数传递给get.f,然后从get.f内部调用该函数:

get.f <- function(asim, Model.fun, M) {
   ...
   Sambat <- ...
   Model  <- Model.fun()
   ...
}
get.f(10, Model.fun = M1, M=1)

此外,通常来说,使用全局变量是不好的编程习惯,即使你的函数使用在其作用域之外定义的变量。相反地,建议将函数使用的所有内容都作为输入参数传递。您的代码中有两种这样的情况:M1M2M3)使用了Samdatget.f使用了dat。它们应该是函数的参数。下面是更好的代码版本。我没有完全修复所有问题,所以您需要再做一些工作才能使其正常运行:
M1 <- function(sampled.data) {
   a1 <- sampled.data[, c("v1", "v2", "v3", "v4", "v6", "v8")]
   b1 <- sampled.data[, c("v1", "v2", "v3", "v5", "v7", "v6")]
   c1 <- a1 + b1
   list(a1 = a1, b1 = b1, c1 = c1)
}

get.f <- function(dat, asim, Model.fun, offset, M) {
   sse <- c()
   for(i in 1:asim){
      set.seed(i)
      Samdat <- dat[sample(1:nrow(dat), nrow(dat), replace = TRUE), ]
      Y      <- Samdat[, "Y"]
      Model  <- Model.fun(sampled.data = Samdat)
      a2     <- Model$a1
      b2     <- Model$b1
      c2     <- Model$c1      
      s      <- switch(M, a2 + b2 + c2, c2 + 12, c2 + a2)
      fit    <- lm(Y ~ s)
      cof    <- sum(summary(fit)$coef[,1])
      coff   <- Model$cof        # there is a problem here...
      sse    <- c(sse, coff)     # this is not efficient
   }
   return(sse)
}

dat <- cbind(Y = rnorm(20), v1 = rnorm(20), v2 = runif(20), v3 = rexp(20),
                            v4 = rnorm(20), v5 = runif(20), v6 = rexp(20),
                            v7 = rnorm(20), v8 = runif(20), v9 = rexp(20))

get.f(dat, 10, Model.fun = M1, M = 1)

还有一件事情值得注意:如果s的定义(我在switch()下收集的内容)与你使用的Model相关,则考虑合并Models的定义:将s添加到M1M2M3函数的输出列表中,这样s就可以直接定义为s <- Model$s,然后你可以删除get.f中的M输入。


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