while 在 apply 函数中循环

while loop in a function in apply

我有这样一个数据框:

df <- data.frame(A=1:10, B=3, C=17)

我想对数据框的每一行应用一个函数,它根据行的值迭代计算一个值。我的原始数据和我使用的功能要复杂得多,但是结构和问题在这里都是一样的。

例如,我使用以下函数迭代计算 A 的平方根:

fun_iter <- function(df_input, diff=10){
              sqrt_iter <- df_input["A"]
              while(diff>0.01) {
               sqrt_iter_new <- (sqrt_iter + df_input["A"] / sqrt_iter) / 2  # approximate the square-root
               diff <- abs(sqrt_iter - sqrt_iter_new)  # difference between the iteration steps
               sqrt_iter <- sqrt_iter_new  # overwrite old value with new iteration
             }
             sqrt_iter[[1]]
             }

然后我可以获得一行的计算值:

fun_iter(df[3, ])

正确地 returns 3 的平方根。此外,我可以使用这样的 for 循环遍历数据帧:

for (i in 1:nrow(df)) {
  print(fun_iter(df[i, ]))
}

它给出了“A”列中所有值的平方根。但是,由于我有一个相当大的数据框,我想使用“应用”或“映射”或类似有效的方式来获取输出,但它总是 returns 这个错误:

apply(df, 2, fun_iter)

Error in while (diff > 0.01) { : Missing Value, where TRUE/FALSE is needed

因此,不知何故,apply 似乎在评估函数内的“while”条件时遇到了问题。 “map”、“mapply”、“do.call”也是如此。非常感谢任何解决此问题的提示。

根据评论中的说明,我们可以使用其中之一迭代行。对于那些有名称的解决方案,如果您不需要它们,请在结果上使用 unname

# 1
nr <- nrow(df)
sapply(1:nr, function(i) fun_iter(df[i, ]))
##  [1] 1.000000 1.414216 1.732051 2.000000 2.236069 2.449494 2.645767 2.828427
##  [9] 3.000000 3.162278

# 2
do.call("c", by(df, 1:nr, fun_iter, simplify = FALSE))
##        1        2        3        4        5        6        7        8 
## 1.000000 1.414216 1.732051 2.000000 2.236069 2.449494 2.645767 2.828427 
##        9       10 
## 3.000000 3.162278 

# 3
sapply(split(df, 1:nr), fun_iter)
##        1        2        3        4        5        6        7        8 
## 1.000000 1.414216 1.732051 2.000000 2.236069 2.449494 2.645767 2.828427 
##        9       10 
## 3.000000 3.162278 

如果我们确定 df 中只有数值,那么我们可以像这样使用 apply

# 4
apply(df, 1, fun_iter)
##  [1] 1.000000 1.414216 1.732051 2.000000 2.236069 2.449494 2.645767 2.828427
##  [9] 3.000000 3.162278

CRAN 上还有一些列表理解包(comprehenr、eList、listcompr)。例如,

# 5
library(listcompr)
gen.vector(fun_iter(df[i, ]), i = 1:nr)
## [1] 1.000000 1.414216 1.732051 2.000000 2.236069 2.449494 2.645767 2.828427
## [9] 3.000000 3.162278

# 6
library(comprehenr)
to_vec(for(i in 1:nr) fun_iter(df[i, ]))
## [1] 1.000000 1.414216 1.732051 2.000000 2.236069 2.449494 2.645767 2.828427
## [9] 3.000000 3.162278

我们遵循问题下方的评论,除了我们只传递 df["A"],因为 apply 会将输入强制转换为普通向量,如果任何列是,这可能导致行变成字符。通过使用 df["A"] 我们可以避免这种情况。

apply(df["A"], 1, fun_iter)
##  [1] 1.000000 1.414216 1.732051 2.000000 2.236069 2.449494 2.645767 2.828427
##  [9] 3.000000 3.162278

如果该函数被编写为接受 A 而不是 df 会更容易,并且由于列不需要命名为 A 而使其更通用,它也避免了上面讨论的问题.我们保留了原始名称,但您可以考虑使用更短的名称。函数中使用的过于冗长的命名几乎没有增加代码,而且确实使代码变得模糊。

fun_iter2 <- function(A, diff = 10) {
  sqrt_iter <- A
  while(diff > 0.01) {
      sqrt_iter_new <- (sqrt_iter + A / sqrt_iter) / 2
      diff <- abs(sqrt_iter - sqrt_iter_new)
      sqrt_iter <- sqrt_iter_new
  }
  sqrt_iter
}
sapply(df$A, fun_iter2)
## [1] 1.000000 1.414216 1.732051 2.000000 2.236069 2.449494 2.645767 2.828427
## [9] 3.000000 3.162278