R - 使用apply函数替换for循环

3

我看过很多学习apply、sapply、lapply的网站,但它们都只教你如何对行或列进行求和或平均值,并不能帮助你更深入地学习。我经常需要使用大量的for循环,而且经常嵌套。请告诉我如何在下面的代码中用apply替换for。

for (i in 1:NTR) {
    X = as.vector(as.matrix(XTR[i,]))
    YZ = FN(W, V, U, X) # return Y and Z
    Y = YZ$Y
    Z = YZ$Z
    for (j in 1:NOUT) {
        F[i+NOUT*(j-1)] = Y[j]-YTR[i,j]
    } # j
} # i

并且

    for (k in 1:NOUT) {
        for (iwt in 1:NWT) {
            m = NHID*(NNLIN+1) 
            if (iwt <= m) { 
                i = (iwt-1) %/% (NNLIN+1) + 1
                j = (iwt-1) %% (NNLIN+1)
                EVZ = V[k,i+1]*Z[i]*(1-Z[i]) 
                if (j>0) EVZ = EVZ*X[j] 
                J[k+(n-1)*NOUT,iwt] = EVZ
            }
        } # iwt     
    } # k

非常感谢您的回复。

3
你提供的例子可能在你看来很有意义,但如果没有上下文,别人可能无法理解。这段代码的输入和预期输出是什么? - zacdav
1个回答

16

将循环转换为lapply最简单的方法是将for循环中的内容放入一个函数中。我们从内部循环开始。

foo_inner <- function(j) {
    F[i+NOUT*(j-1)] = Y[j]-YTR[i,j]
}

然而这里有一个问题:你的函数应该返回一个值而不是分配它。在这种情况下,你想要返回Y[j]-YTR[i,j]

foo_inner <- function(j) {
    return(Y[j]-YTR[i,j])
}

您可以将此函数应用于1:NOUT。在这种情况下,根据您的帖子中缺乏信息(虽然我不能确定),似乎该返回值仅为一个数字,因此您可以直接创建向量而不是列表。在这种情况下,最好使用sapply而不是lapply('s'表示简化(为向量),而'l'表示列表):

sapply(1:NOUT, foo_inner)

现在必须在外层循环中分配此新向量。从您的代码中,似乎您希望将此向量分配给F [i + NOUT *(1:NOUT-1)] 。我只是将您的 j 值替换为 1:NOUT ,这是您的迭代器。因此,您的代码片段可以修改如下:

for (i in 1:NTR) {
    X = as.vector(as.matrix(XTR[i,]))
    YZ = FN(W, V, U, X) # return Y and Z
    Y = YZ$Y
    Z = YZ$Z
    F[i+NOUT*(1:NOUT-1)] <- sapply(1:NOUT, foo_inner)
} # i

现在让我们来处理外层循环。和之前一样,我们可以将内部内容放入一个函数中:

foo_outer <- function(i) {
    X = as.vector(as.matrix(XTR[i,]))
    YZ = FN(W, V, U, X) # return Y and Z
    Y = YZ$Y
    Z = YZ$Z
    F[i+NOUT*(1:NOUT-1)] <- sapply(1:NOUT, foo_inner)
}

这里有两个问题需要解决:首先,您的foo_inner函数以i作为参数。但是,由于foo_innerfoo_outer外定义,它将始终使用环境中定义的i而不是foo_outer的参数。有两种解决方案:要么在foo_outer内定义foo_inner

foo_outer <- function(i) {
    X = as.vector(as.matrix(XTR[i,]))
    YZ = FN(W, V, U, X) # return Y and Z
    Y = YZ$Y
    Z = YZ$Z
    foo_inner <- function(j) {
        return(Y[j]-YTR[i,j])
    }
    F[i+NOUT*(1:NOUT-1)] <- sapply(1:NOUT, foo_inner)
}

或者,修改你的 foo_inner 函数,使其接受 i 作为参数并返回一个正确的函数。然后在内部的 sapply 中应用函数 foo_inner(i) 而不是 foo_inner

foo_inner <- function(i) {
    function(j) {
        return(Y[j]-YTR[i,j])
    }
}
foo_outer <- function(i) {
    X = as.vector(as.matrix(XTR[i,]))
    YZ = FN(W, V, U, X) # return Y and Z
    Y = YZ$Y
    Z = YZ$Z
    F[i+NOUT*(1:NOUT-1)] <- sapply(1:NOUT, foo_inner(i))
}
下一步需要修改的是确保你想要在函数中返回一个值而不是进行赋值操作:
foo_inner <- function(i) {
    function(j) {
        return(Y[j]-YTR[i,j])
    }
}
foo_outer <- function(i) {
    X = as.vector(as.matrix(XTR[i,]))
    YZ = FN(W, V, U, X) # return Y and Z
    Y = YZ$Y
    Z = YZ$Z
    return(sapply(1:NOUT, foo_inner(i)))
}

您现在可以应用此函数:

lapply(1:NTR, foo_outer)
在这种情况下,我使用了lapply,因为每个元素返回的将是一个向量,所以我宁愿返回一个列表,之后再折叠它,因为我不确定在这种情况下sapply是否会这样做(并且现在懒得找出来,如果有人可以确认,我会纠正)。
所以现在你想要一个大向量,所以我只需折叠所有。
do.call(c, lapply(1:NTR, foo_outer))

我可以直接将这个值赋给F

F <- do.call(c, lapply(1:NTR, foo_outer))

很显然,如果不知道FYTRY和所有输入的内容,我无法确保这正是你想要的。但我希望它能让你走上正确的道路!

编辑:我认为你的最终向量顺序是错误的:使用以上方法将把所有i=1的"j"值放在一起,然后是所有i=2的"j"值......但回顾你的F,看起来你想要的顺序是对于j=1的所有i值......为了做到这一点,你只需要重新排列第二个lapply的输出。这应该可以解决问题。

我想创建一个函数,从列表中获取所有的j元素:

LL <- lapply(1:NTR, foo_outer)
get_j_values <- function(j) {
    sapply(LL, function(x) x[[j]])
}

对于任何j值,get_j_value都会返回一个向量,其中包含LL的所有第j个元素。通过将此函数应用于所有可能的j值,它会返回一个列表,第一个元素是:j=1时所有"i"值,然后第二个元素是j=2时所有"i"值...

LL2 <- lapply(1:NOUT, get_j_values)
我可以将此列表合并成一个大向量并进行分配。
F <- do.call(c, LL2)

编辑2:尽管使用apply函数重新创建您的for循环是可能的,但这可能是其中一个时候,for循环实际上更好:没有结果的累积,因此apply方法不应更快,而且我认为for循环会更清晰。当您循环遍历用于多个不同对象的索引时,通常情况下就是这种情况,因此您无法直接在任何特定对象上使用apply,但需要将函数应用于索引向量...仅代表我个人观点...


这太棒了,比任何教程都要好。我仍然需要理解函数定义内部的函数是如何工作的,但我可以查阅资料。F和Y是向量,YTR是一个矩阵(全部为数字),所以Y[j]-YTR[i,j]就是一个数字。它只需要放在向量F的正确位置即可。FN是由矩阵W、V、U和向量X构成的函数。(XTR是一个数据框,因为我用read.csv从文件中读取它。)我不理解最后一部分 - LL和LL2。我们根本没有创建任何列表。只有YZ是一个列表,因为我想从FN返回两个矩阵。再次感谢您的解释。 - user6439024
使用 foo_inner <- function(i) function(j) paste("i=", i, "j=", j) 并运行它,然后比较第一种方法创建的 F 和第二种方法创建的 F,这应该使 LLLL2 之间的差异更加清晰。我在这里使用列表只是为了确保我将正确的值放在正确的位置。 - Choubi
我很高兴你觉得它有用。 - Choubi
@user6439024,您可以通过单击答案旁边的灰色勾号接受Choubi的答案,并且您还可以通过单击勾号上方的向上箭头为他的答案点赞“+1”。 - KenHBS

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