【问题标题】:R Vectorize FOR loop using previous iteration valuesR使用先前的迭代值向量化FOR循环
【发布时间】:2020-03-03 22:30:08
【问题描述】:

是否可以矢量化/加速使用先前迭代值的 FOR 循环的执行?

在下面的复制示例中:

  • 当前产量是根据当前库存计算得出的
  • 当前生产更新 NEXT 库存
  • 下一次迭代使用更新后的库存来确定当前的产量等...

所以我需要在每次迭代时计算库存,以便计算生产设定点......是否可以避免(慢)for循环?

对于 50k 行,当前实现大约需要 45 秒。

# Dummy functions for the examples. Real code is more complicated
function1 <- function(energy, stock, critical) {
    if (stock < critical) {
        return (energy)
    } else {
        return(0)
    }
}
function2 <- function(power) {
  return(round(power/100))
}
# Dummy data
d <- data.frame( "energy"= c(660, 660, 660, 660),
                 "stock" = c(20,   0,    0, 0),
                 "delivery" = c(0, 0, 2, 0),
                 "critical" = c(50, 50 ,50, 50),
                 "power" = c(0, 0, 0, 0),
                 "production" = c(0, 0, 0, 0) )

for (i in 1:length(d$energy)) {

  # Computing power, based on CUURENT stock
  d$power[i] <- function1(d$energy[i], d$stock[i], d$critical[i])

  # Computing production
  d$production[i] <- function2(d$power[i])

  # Updating NEXT stock with current production / delivery
  if (i < length(d$energy)) {
    d$stock[i+1] <- d$stock[i] + d$production[i] - d$delivery[i]
  }
}

View(d)

【问题讨论】:

  • 您也许可以使用 lapplymap 等伪循环来加快速度,但问题可能出在您的两个函数的设计中,而不是在循环中本身。如果您使用的是 RStudio,请尝试在您的函数上运行“配置文件”以查看时间花费在哪里。
  • 有什么理由用大写锁定写for
  • This 可能会有所帮助。你的功能看起来不错。它尽可能快。正如@AndrewGustar 在他的评论中指出的那样,这不是你的循环,而是你的函数。为了回答您的问题,您可以优化您的函数或用 C 或 Fortran 重写您的代码。

标签: r dataframe for-loop


【解决方案1】:

base 中,您可以使用 Reduceaccumulate = TRUE,例如:

fun  <- function(x,y) {
    ttStock <- x[[2]] + x[[6]] - x[[3]]
    ttPower <- function1(y[[1]], ttStock, y[[4]])
    ttProduction <- function2(ttPower)
    c(y[[1]], ttStock, y[[3]], y[[4]], ttPower, ttProduction)
}
d$power[1] <- function1(d$energy[1], d$stock[1], d$critical[1])
d$production[1] <- function2(d$power[1])
do.call(rbind, Reduce(fun, as.data.frame(t(d[-1,])), d[1,], accumulate = TRUE))
#  energy stock delivery critical power production
#1    660    20        0       50   660          7
#2    660    27        0       50   660          7
#3    660    34        0       50   660          7
#4    660    39        2       50   660          7

为了方便我在d的第一行填写powerproduction

如果您使用名称而不是列号:

fun  <- function(x,y) {
    names(x)  <- colnames(d)
    ttStock <- x[["stock"]] + x[["production"]] - x[["delivery"]]
    ttPower <- function1(y[[1]], ttStock, y[[4]])
    ttProduction <- function2(ttPower)
    c(y[[1]], ttStock, y[[3]], y[[4]], ttPower, ttProduction)
}

【讨论】:

  • 我想我可以用你的原则来满足我的需求。有没有办法保留列名? (x$stock 代替 $[[2]] )?
  • 看来我将计算时间除以 50 ! (与 for 循环相比)
  • @StéphaneV 我添加了一个变体,您可以在其中使用名称而不是列号。
  • 列名不起作用,因为返回的值是一个没有名称的向量...所以第二次迭代的调用失败... x[["stock"]] 中的错误:索引超出范围。
  • @StéphaneV 在names(x) &lt;- colnames(d) 中,我从d 中获取名称并将它们设置在x 中。使用您问题中的函数和数据以及我的答案中的代码,我得到了正确的结果。
【解决方案2】:

如何跨函数调用保存状态。

my_env <- new.env(parent = emptyenv())
my_env$stock <- d$stock[0]

f <- function(item){
   power <- function1()
   production <- function1()/100
   stock <- my_env$stock 
   ....
   rest of the businesss logic
   ...
}

apply(d, 2, f)

【讨论】:

    【解决方案3】:

    一种可能性是使用dplyr 包,它是tidyverse 的一部分。

    library(dplyr)
    
    d %>%
      mutate(power = function1(energy, stock, critical),
             production = function2(power),
             stock_new = cumsum(stock + lag(production - delivery, 1, default = 0)))
    
      energy stock delivery critical power production stock_new
    1    660    20        0       10   500          5        20
    2    660     0        0       10   500          5        25
    3    660     0        2       10   500          5        30
    4    660     0        0       10   500          5        33
    

    如果函数 function1function2 是矢量化的,这很容易实现。如果没有,您将不得不在mutate 中使用purrr::map

    【讨论】:

    • 这不符合 OP 的要求,因为 power 应该是 stock_new 的函数。
    • 我已经编辑了这个问题,因为 function1 依赖于变量“stock”......在这种情况下,我无法重现你的解决方案......你能解释一下如何使用 purrr::map ?
    • 例如,如果function2 未矢量化,您可以将 mutate 中的第二行替换为:production = purrr::map_dbl(power, function2)。如果您的函数只有一个输入,则此方法有效。对于两个输入,您可以使用purrr::map2_dbl,对于三个或更多输入,您可以使用pmap_dbl。 mutate 中的第一行将读取为:power = purrr::pmap_dbl(list(energy, stock, critical), function1)
    猜你喜欢
    • 2021-04-12
    • 1970-01-01
    • 2019-06-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多