【问题标题】:Efficient iterative processing of all data frame records in RR中所有数据帧记录的高效迭代处理
【发布时间】:2014-08-06 04:07:07
【问题描述】:

我进行了搜索,但对于在 R 中执行迭代计算的有效选项可能还不够,我承认这个问题。与我直到最近主要使用的 SAS 相比,R 中的简单迭代计算需要这么长时间来处理,这让我感到惊讶。

这是我根据数据框中的 4 个不同变量计算 rnk2 的代码。

new_rank2 <- function(rnk,penalty,min_rnk,max_rnk){
rnk2=max(min(rnk+penalty,max_rnk),min_rnk)
return(rnk2)
}
step4b <- step4[1:15000,]
for(i in 1:nrow(step4b)){
step4b$rnk2[i] <- new_rank2(step4b$rnk[i],step4b$penalty[i],step4b$min_rnk[i],step4b$max_rnk[i])
}

使用此代码,10k 记录大约需要 32 秒,15k 记录需要 75 秒,20k 记录需要 120 秒,我有大约 400k 记录。

我需要帮助的另一个例子是迭代条件处理。

for (i in 1:nrow(data)) {
if  (data$V1[i]%in% c("A","B","E")) data$V3[i] = data$V4[i]
if  (data$V5[i]=="MED") data$V3[i] == 'XL'
}

【问题讨论】:

  • 第一个应该很容易使用 pmax() 和 pmin() 向量化。
  • 我没有看到任何测试用例。为什么不出席dput( head(testcase))。将您的数据命名为“数据”只会让人们少考虑您。就好像您在 for 循环中将迭代变量命名为“for”。
  • 可能是step4b$rnk2 &lt;- with(step4b, pmax(pmin(rnk+penalty, max_rnk), min_rnk) ) ?
  • @joran 感谢您的提示;我尝试使用 max() 和 min() 进行矢量化但失败了,我不知道 pmin() 和 pmax()。我一定会试一试的。
  • @thelatemail,感谢您的提示;我尝试使用 max() 和 min() 进行矢量化但失败了,我不知道 pmin() 和 pmax()。我一定会试一试的。

标签: r loops iteration


【解决方案1】:

我生成了一个示例数据集,以尝试进行一些基准比较以进行比较。 (如果它不能充分代表您的实际数据,请告诉我,我会再试一次。我不确定这是否重要,因为 pminpmax 往往与千分之一1。)

new_rank2 <- function(rnk,penalty,min_rnk,max_rnk){
    rnk2 = max(min(rnk + penalty, max_rnk), min_rnk)
    return(rnk2)
}

set.seed(1)
n <- 20000
step4 <- data.frame(rnk = runif(n), penalty = runif(n),
                    min_rnk = runif(n), max_rnk = runif(n))
step4b <- step4c <- step4d <- step4

三种方法的基本性能。首先,你的迭代方法:

system.time(
    for(i in 1:nrow(step4b)){
        step4b$rnk2[i] <- new_rank2(step4b$rnk[i], step4b$penalty[i],
                                    step4b$min_rnk[i], step4b$max_rnk[i])
    }
)
##     user  system elapsed 
##     3.40    0.00    3.41 

二、矢量化方法:

system.time(
    step4c$rnk2 <- with(step4, pmax(pmin(rnk + penalty, max_rnk), min_rnk))
)
##     user  system elapsed 
##     0.02    0.00    0.02 

三、利用Hadley Wickham的dplyr的方法:

library(dplyr)
system.time(
    step4d <- step4 %>%
        mutate(rnk2 = pmax(pmin(rnk + penalty, max_rnk), min_rnk))
)
##     user  system elapsed 
##        0       0       0 

虽然对于 20k 条记录,我与您的 120 秒记录相差无几,但我猜测计算量比 rnk2 计算量要多。 (顺便说一句:我的测试计算机是 2 年以上的 i7 2.8GHz,具有 8GB RAM,运行 R-3.1.1 和 cough cough win7。)

这些方法都产生相同的结果:

identical(step4b, step4c)
## [1] TRUE
identical(step4b, step4d)
## [1] TRUE

由于不应将单次运行视为绝对基准,因此进行更严格的比较可能会更有洞察力。

library(microbenchmark)
microbenchmark(
    iterative = {
        for(i in 1:nrow(step4b)){
            step4b$rnk2[i] <- new_rank2(step4b$rnk[i], step4b$penalty[i],
                                        step4b$min_rnk[i], step4b$max_rnk[i])
        }
    },
    vectorized = {
        step4c$rnk2 <- with(step4, pmax(pmin(rnk + penalty, max_rnk), min_rnk))
    },
    dplyr = {
        step4d <- step4 %>%
            mutate(rnk2 = pmax(pmin(rnk + penalty, max_rnk), min_rnk))
    }
)
## Unit: milliseconds
##        expr         min          lq      median          uq         max neval
##   iterative 3151.235603 3226.225834 3257.488366 3286.452867 3504.440315   100
##  vectorized    1.098110    1.159931    1.195153    1.247251    3.051811   100
##       dplyr    1.350165    1.418957    1.524622    1.604054    3.255437   100

仅在这个循环中,测试用例就有超过三个数量级的差异,并且使用了 20k 条记录。选择使用矢量化代码还是 Hadley 的 dplyr 是个人选择,很大程度上受代码复杂性的影响;在这种情况下,我很难使用矢量化代码,但这只是我和这个例子。


对于您的第二批代码,首先请注意,在您的第二个 if 语句中,您应该将 == 替换为单个 = 或(有些人可能会认为“更好”)&lt;-。改变这个:

if (data$V5[i] == "MED") data$V3[i] == 'XL'

if (data$V5[i] == "MED") data$V3[i] <- 'XL'

否则,条件的“then”部分 data$V3[i] == 'XL' 会缩减为 FALSE,而不是将 'XL' 分配到 data$V3[i] 数组元素中。

您可以使用以下方式矢量化您的 for 循环:

data$V3 <- NA
data$V3 <- ifelse(data$V1 %in% c('A', 'B', 'E'),
                  data$V4,
                  ifelse(data$V5 == 'MED',
                         'XL',
                         data$V3))

我在这里首先将$V3 设置为NA 主要是因为我不知道其他地方发生了什么;实际上,我假设它已经设置为一个合理的值,并且您正在有条件地更改它。使用嵌套的ifelse,这仍然有些可读性,但我会防止嵌套更多。如果需要更多条件,您可能会通过以下方式获得更好的可读性(也许还有性能):

idx <- data$V1 %in% c('A', 'B', 'E')
data$V3[idx] <- data$V4[idx]
idx <- (data$V5 == 'MED')
data$V3[idx] <- 'XL'
## ...

...尽管如果任何测试允许数据多次匹配,您需要小心,并且比较的顺序会影响更新。


脚注:

  1. 为了清楚起见,我并不是说以千分之一或千分之一进行数学运算是等价的,只是比较。乘法(例如 1e8 之类的数字)的效率使其比除法(相同 OOM)稍微快一点,但比较本身是等价的。

【讨论】:

  • 这太棒了!感谢您的详细解释。我一定会尝试您的建议并报告。
  • 这太疯狂了!多亏了 r2evans,两个建议的解决方案都快如闪电。顺便说一句,我的电脑也没有那么快;它只是具有 4GB RAM 的 i5。第一个解决方案在 0.08 秒内处理 40 万条记录,第二个在 0.47 秒内处理
猜你喜欢
  • 2014-09-16
  • 1970-01-01
  • 2018-01-22
  • 2023-03-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多