在数据框的每一行上矢量化函数的有效方法

时间:2017-06-11 23:21:24

标签: r dataframe vectorization

我有一个数据框,其中一列是时间戳列表。我需要注释哪些时间戳是有效的,取决于它们是否足够接近(即,在1秒内)到另一个有效时间戳列表的元素。为此,我有一个辅助功能。

valid_times <- c(219.934, 229.996, 239.975, 249.935, 259.974, 344)

actual_times <- c(200, 210, 215, 220.5, 260)
strain <- c("green", "green", "green", "green", "green", "green")
valid_or_not <- c(rep("NULL", 6))

df <- data.frame(strain, actual_times, valid_or_not)

我的数据框架如下所示:

strain actual_times valid_or_not
1 green 200.0        NULL
2 green 210.0        NULL
3 green 215.0        NULL
4 green 220.5        NULL
5 green 260.0        NULL

我的助手(检查实际时间是否在有效时间的1秒内)如下:

valid_or_not_fxn<- function(actual_time){
c = "not valid"
for (i in 1:length(valid_times))
if (abs(valid_times[i] - actual_time) <= 1) {
c <- "valid"
} else {
}
return(c)

}

我试图做的是使用带有此辅助函数的for循环遍历整个数据框。

然而......它真的很慢(在我的真实数据集上),因为它是一个嵌套循环交叉比较两个100个元素长的列表。 我无法理解这一点。

df$valid_or_not <- as.character(df$valid_or_not)

for (i in 1:nrow(df)) 
  print(df[i, "valid_or_not"])
  df[i, "valid_or_not"] <- valid_or_not_fxn(df[i, "actual_times"])

感谢您的帮助!

2 个答案:

答案 0 :(得分:1)

无论你做什么,你基本上都必须至少进行length(valid_times)次比较。可能更好地循环valid_times并将该向量的每个项目作为向量化操作与actual_times列进行比较。这样你就只有5次循环迭代。

这样做的一种方法是:

df$test <- Reduce(`|`, lapply(valid_times, function(x) abs(df$actual_times - x) <= 1))

#  strain actual_times valid_or_not
#1  green        200.0        FALSE
#2  green        210.0        FALSE
#3  green        215.0        FALSE
#4  green        220.5         TRUE
#5  green        260.0         TRUE

df中的100K行和1000 valid_times次测试在<4秒内完成:

df2 <- df[sample(1:5,1e5,replace=TRUE),]
valid_times2 <- valid_times[sample(1:5,1000,replace=TRUE)]
system.time(Reduce(`|`, lapply(valid_times2, function(x) abs(df2$actual_times - x) <= 1)))
# user  system elapsed 
# 3.13    0.40    3.54

答案 1 :(得分:1)

这种简单的方法是避免数据帧操作。因此,您可以执行此检查并填充valid_or_not向量,然后将它们组合到数据框中:

valid_or_not[sapply(actual_times, function(x) any(abs(x - valid_times) <= 1))] <- "valid"

注意,通过该行,valid_or_not向量被索引为具有相等长度的布尔值向量(条件是否满足,T或F)。因此,仅更新向量中的TRUE值索引。 valid_or_not和actual_times向量必须具有相同的长度,而valid_times向量的长度可以不同。

顺便说一句,“plying”for循环不会显着提高性能,因为它只是“for”循环的“包装器”。只有性能的提高来自避免由于整洁的中间对象和更简洁的代码风格,并在某些情况下避免冗余复制。对于Vectorize函数也是如此:它只包含通过函数的for循环,例如“outer”函数,FUN必须以这种方式“向量化”。实际上它并没有给出真正矢量化操作的性能。在我的例子中,性能增强来自于使用“any”函数替换for循环。

由于某种“错误”,子集化数据帧有一个重要的损失。正如Hadley Wickham在Advanced-R的Performance主题中所解释的那样:

  

从数据框中提取单个值

     

以下微基准测试显示了五种访问单个值的方法   (来自右下角的数字)来自内置的mtcars   数据集。性能的变化令人吃惊:最慢的方法   比最快的时间长30倍。没有理由必须这样做   在性能方面有如此巨大的差异。简直就是没有人拥有   有时间解决它。

microbenchmark(
   "[32, 11]"      = mtcars[32, 11],
   "$carb[32]"        = mtcars$carb[32],
   "[[c(11, 32)]]" = mtcars[[c(11, 32)]],
   "[[11]][32]"    = mtcars[[11]][32],
   ".subset2"      = .subset2(mtcars, 11)[32] )



## Unit: nanoseconds
##           expr    min     lq  mean median     uq     max neval
##       [32, 11] 15,300 16,300 18354 17,000 17,800  76,400   100
##      $carb[32]  8,860  9,930 12836 10,600 11,600  85,400   100
##  [[c(11, 32)]]  7,200  8,110  9293  8,780  9,350  21,300   100
##     [[11]][32]  6,330  7,580  8377  8,100  8,690  20,900   100
##       .subset2    334    566  4461    669    800 368,000   100

对数据帧进行子集化的最有效方法是使用.subset2方法。你的糟糕表现主要归功于这个事实。

如前所述:

  • 如果条件语句中的“else”没有执行任何操作(就像您的示例中所示:else {}),则不必包含它。 R有一些惰性操作(只要它没有在代码中执行就不评估语句),但这并不意味着它总是跳过未执行的代码部分。
  • 您示例中的“字符”值实际上是分类:仅 可以为每个条目选择少数值之一。所以没有必要 将它们存储为“字符”,它们可以转换为因子 (这只是integer values)。这也可以增强 性能

@thelatemail工作解决方案的补充:

  • 在R中,“或”(|)运算符不是“任何”函数的延迟。在第一次遇到TRUE值时,“任意”功能停止的一个层次结构或工作直到结束 - 这增强了性能(我将尽快写一篇关于这个主题的博客文章)。矢量化的“任意”几乎与原生C代码一样快,而* ply可能比R中的循环稍快(我将很快在其他博客文章中进行基准测试和显示)。

一些基准测试显示:

纯粹的“任何”和|比较:

> microbenchmark(any(T,F,F,F,F,F), T|F|F|F|F|F)                                                                                                                                               
Unit: nanoseconds                              
                  expr min    lq   mean median    uq   max neval cld                           
 any(T, F, F, F, F, F) 274 307.0 545.86  366.5 429.5 16380   100   a                           
 T | F | F | F | F | F 597 626.5 903.47  668.5 730.0 18966   100   a                           

Pure“Reduce”和矢量化比较:

> vec0 <- rep(1, 1e6)
> microbenchmark(Reduce("+", vec0), sum(vec0), times = 10)
Unit: microseconds
              expr        min         lq        mean      median         uq
 Reduce("+", vec0) 308415.064 310071.953 318503.6048 312940.6355 317648.354
         sum(vec0)    930.625    936.775    944.2416    943.5425    949.257
        max neval cld
 369864.993    10   b
    962.349    10  a 

减少“|”与矢量化的“任何”比较(对于极端情况)。 “任何”节拍超过1e5次:

> vec1 <- c(T, rep(F, 1e6))
> microbenchmark(Reduce("|", vec1), any(vec1), times = 10)
Unit: nanoseconds
              expr       min        lq        mean    median        uq
 Reduce("|", vec1) 394040518 395792399 402703632.6 399191803 400990304
         any(vec1)       154       267      1932.5      2588      2952
       max neval cld
 441805451    10   b
      3420    10  a 

当单个TRUE位于最后(所以“任何”不再是懒惰并且必须检查整个向量),“任何”仍然超过400次:

> vec2 <- c(rep(F, 1e6), T)
> microbenchmark(Reduce("|", vec2), any(vec2), times = 10)
Unit: microseconds
              expr        min         lq        mean     median         uq
 Reduce("|", vec2) 396625.318 401744.849 416732.5087 407447.375 424538.222
         any(vec2)    736.975    787.047    857.5575    832.137    926.076
        max neval cld
 482116.632    10   b
   1013.732    10  a