【问题标题】:while loop in a function in apply应用中的函数中的while循环
【发布时间】:2021-04-30 13:33:38
【问题描述】:

我有一个这样的数据框:

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, ])

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

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

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

apply(df, 2, fun_iter)

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

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

【问题讨论】:

  • apply 只是变相的循环。它不会为您提供与矢量化相同的速度提升(假设矢量化是可能的)。即使你得到了正确的细节,你也可能会对结果感到失望。
  • 试试这个 apply(df, 1, fun_iter)
  • 它有效,谢谢!尽管使用了多年,但我似乎对 apply 中的“1”和“2”的方向感到困惑。可能与下面答案中描述的输入格式有关。谢谢!
  • @John Coleman 谢谢,不幸的是,矢量化是不可能的。我更喜欢 apply 而不是 for 循环,这样我就不必处理将每个迭代步骤写入预定义的数据帧

标签: r while-loop apply


【解决方案1】:

根据 cmets 中的说明,我们可以使用其中之一对行进行迭代。对于那些有名称的解决方案,如果您不想要它们,请在结果上使用 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

我们遵循问题下方的 cmets,但我们只传递 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

【讨论】:

  • 我使用了 df["A"] 因为我有多个变量作为输入(目前有 20 个,而且会变得更多)。因此,我认为将整个 df 作为输入是最容易的。但是,正如您指出的那样,“应用”会将其更改为字符,以您的方式提供变量可能是不可避免的。谢谢!!
  • 查看修改后的答案。
  • 感谢您提供替代方案。 apply(df, 1, fun_iter) 在删除带有名称的向量后对我来说是最快的。 sapply 函数要慢得多(最多慢 18 倍!),不知何故。
  • 可能与数据存储在数据框中并应用先将其转换为矩阵有关。你可以尝试从一开始就将它存储在一个矩阵中,因为它都是数字的。
  • 此外,如果您将矩阵存储为转置然后使用m &lt;- t(df); apply(m, 2, fun_iter),那么矩阵会逐列存储,这也可能会带来一些额外的速度。
猜你喜欢
  • 1970-01-01
  • 2017-06-07
  • 2021-03-08
  • 2016-07-23
  • 2014-04-30
  • 1970-01-01
  • 2012-11-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多