R:使用 "microbenchmark" 和 ggplot2 绘制运行时间

R: Using "microbenchmark" and ggplot2 to plot runtimes

我正在使用 R 编程语言。我想学习如何随着数据量的增加测量和绘制差分过程的 运行 时间。

我发现以前的 Whosebug post 回答了一个类似的问题:绘制三个函数的 运行 时间

看来R中的“microbenchmark”库应该可以完成这个任务。

假设我模拟以下数据:

#load libraries

library(microbenchmark)
library(dplyr)
library(ggplot2)
library(Rtsne)
library(cluster)
library(dbscan)
library(plotly)

#simulate data

var_1 <- rnorm(1000,1,4)
var_2<-rnorm(1000,10,5)
var_3 <- sample( LETTERS[1:4], 1000, replace=TRUE, prob=c(0.1, 0.2, 0.65, 0.05) )
var_4 <- sample( LETTERS[1:2], 1000, replace=TRUE, prob=c(0.4, 0.6) )


#put them into a data frame called "f"
f <- data.frame(var_1, var_2, var_3, var_4)

#declare var_3 and response_variable as factors
f$var_3 = as.factor(f$var_3)
f$var_4 = as.factor(f$var_4)

#add id
f$ID <- seq_along(f[,1])
Now, I want to measure the run time of 7 different procedures:

#Procedure 1: :

gower_dist <- daisy(f[,-5],
                    metric = "gower")

gower_mat <- as.matrix(gower_dist)


#Procedure 2

lof <- lof(gower_dist, k=3)

#Procedure 3

lof <- lof(gower_dist, k=5)

#Procedure 4

tsne_obj <- Rtsne(gower_dist,  is_distance = TRUE)

tsne_data <- tsne_obj$Y %>%
    data.frame() %>%
    setNames(c("X", "Y")) %>%
    mutate(
           name = f$ID)

#Procedure 5

tsne_obj <- Rtsne(gower_dist, perplexity =10,  is_distance = TRUE)

tsne_data <- tsne_obj$Y %>%
    data.frame() %>%
    setNames(c("X", "Y")) %>%
    mutate(
           name = f$ID)

#Procedure 6

plot = ggplot(aes(x = X, y = Y), data = tsne_data) + geom_point(aes())

#Procedure 7

tsne_obj <- Rtsne(gower_dist,  is_distance = TRUE)

tsne_data <- tsne_obj$Y %>%
  data.frame() %>%
  setNames(c("X", "Y")) %>%
  mutate(
    name = f$ID, 
    lof=lof,
    var1=f$var_1,
    var2=f$var_2,
    var3=f$var_3
    )

p1 <- ggplot(aes(x = X, y = Y, size=lof, key=name, var1=var1, 
  var2=var2, var3=var3), data = tsne_data) + 
  geom_point(shape=1, col="red")+
  theme_minimal()

ggplotly(p1, tooltip = c("lof", "name", "var1", "var2", "var3"))

使用“microbenchmark”库,我可以找出各个函数的时间:

procedure_1_part_1 <- microbenchmark(daisy(f[,-5],
                    metric = "gower"))

procedure_1_part_2 <-  microbenchmark(as.matrix(gower_dist))

我想像这样绘制 运行 次的图表:

https://umap-learn.readthedocs.io/en/latest/benchmarking.html

问题:谁能告诉我如何制作这张图并一次使用多个函数的微基准语句(对于不同大小的数据框“f”(对于 f = 5、10、50、100、200 , 500, 100)?

microbench(cbind(gower_dist <- daisy(f[1:5,-5], metric = "gower"), gower_mat <- as.matrix(gower_dist))

microbench(cbind(gower_dist <- daisy(f[1:10,-5], metric = "gower"), gower_mat <- as.matrix(gower_dist))

microbench(cbind(gower_dist <- daisy(f[1:50,-5], metric = "gower"), gower_mat <- as.matrix(gower_dist))

等等

在 R 中似乎没有直接的方法可以做到这一点:

mean(procedure_1_part_1$time)
[1] NA

Warning message:
In mean.default(procedure_1_part_1) :
  argument is not numeric or logical: returning NA

我可以手动 运行 每一个,将结果复制到 excel 并绘制它们,但这也需要很长时间。

 tm <- microbenchmark( daisy(f[,-5],
                        metric = "gower"),
    as.matrix(gower_dist))

 tm
Unit: microseconds
                             expr    min     lq     mean  median      uq    max neval cld
 daisy(f[, -5], metric = "gower") 2071.9 2491.4 3144.921 3563.65 3621.00 4727.8   100   b
            as.matrix(gower_dist)  129.3  147.5  194.709  180.80  232.45  414.2   100  a 

有没有更快的制作图表的方法?

谢谢

我的第一个回答严重误解了你的问题。 希望对您有所帮助。

library(tidyverse)
library(broom)

# Benchmark your expressions. The following script assumes you name the benchmarks as function_n, but this can (and should be) improved on.
res = microbenchmark(
  rnorm_100 = rnorm(100),
  runif_100 = runif(100),
  rnorm_1000 = runif(1000),
  runif_1000 = runif(1000)
)

# We will be using this gist to tidy the frame
# Source: https://gist.github.com/nutterb/e9e6da4525bacac99899168b5d2f07be
tidy.microbenchmark <- function(x, unit, ...){
  summary(x, unit = unit)
}

# Tidy the frame
res_tidy = tidy(res) %>% 
  mutate(expr = as.character(expr)) %>% 
  separate(expr, c("func","n"), remove = FALSE)

res_tidy
#>         expr  func    n    min      lq     mean  median      uq     max neval
#> 1  rnorm_100 rnorm  100  8.112  9.3420 10.58302 10.2915 10.9755  44.903   100
#> 2  runif_100 runif  100  4.487  5.1180  6.12284  6.1990  6.5925  10.907   100
#> 3 rnorm_1000 rnorm 1000 34.631 36.3155 37.78117 37.2665 38.4510  62.951   100
#> 4 runif_1000 runif 1000 34.668 36.6330 39.48718 37.7995 39.2905 105.325   100

# Plot the runtime for the different expressions by sample number
ggplot(res_tidy, aes(x = n, y = mean, group = func, col = func)) +
  geom_line() +
  geom_point() +
  labs(y = "Runtime", x = "n")

reprex package (v0.3.0)

于 2020-12-26 创建

这是一个解决方案,它对原始 post 中的前三个过程进行基准测试并绘制图表,然后用 ggplot().

绘制它们的平均 运行 次

设置

我们通过执行从原始 post 创建数据所需的代码来启动该过程。

library(dplyr)
library(ggplot2)
library(Rtsne)
library(cluster)
library(dbscan)
library(plotly)
library(microbenchmark)

#simulate data

var_1 <- rnorm(1000,1,4)
var_2<-rnorm(1000,10,5)
var_3 <- sample( LETTERS[1:4], 1000, replace=TRUE, prob=c(0.1, 0.2, 0.65, 0.05) )
var_4 <- sample( LETTERS[1:2], 1000, replace=TRUE, prob=c(0.4, 0.6) )

#put them into a data frame called "f"
f <- data.frame(var_1, var_2, var_3, var_4,ID=1:1000)

#declare var_3 and response_variable as factors
f$var_3 = as.factor(f$var_3)
f$var_4 = as.factor(f$var_4)

按数据帧大小自动执行基准测试过程

首先,我们创建一个数据帧大小向量来驱动基准测试。

# configure run sizes
sizes <- c(5,10,50,100,200,500,1000)

接下来,我们采用第一个过程并对其进行更改,以便我们可以改变从数据框 f 中使用的观察值的数量。请注意,由于我们需要在后续步骤中使用此过程的输出,因此我们使用 assign() 将它们写入全局环境。我们还在对象名称中包含观察的数量,以便我们可以在后续步骤中按大小检索它们。

# Procedure 1: :
proc1 <- function(size){
    assign(paste0("gower_dist_",size), daisy(f[1:size,-5],
                        metric = "gower"),envir = .GlobalEnv)
        
    assign(paste0("gower_mat_",size),as.matrix(get(paste0("gower_dist_",size),envir = .GlobalEnv)),
           envir = .GlobalEnv)
        
}     

为了运行 数据帧大小的基准,我们使用 sizes 向量和 lapply() 以及一个重复执行 proc1() 的匿名函数。我们还将观察值的数量分配给名为 obs 的列,以便我们可以在图中使用它。

proc1List <- lapply(sizes,function(x){
        b <- microbenchmark(proc1(x))
        b$obs <- x
        b
})

在这一点上,我们有一个基于大小的每个基准的数据框。我们将基准与 do.call()rbind().

组合成一个数据框
proc1summary <- do.call(rbind,(proc1List))

接下来,我们使用与过程 2 和 3 相同的过程。注意我们如何使用 get()paste0() 来按大小检索正确的 gower_dist 对象。

#Procedure 2

proc2 <- function(size){
        lof <- lof(get(paste0("gower_dist_",size),envir = .GlobalEnv), k=3)
}
proc2List <- lapply(sizes,function(x){
    b <- microbenchmark(proc2(x))
    b$obs <- x
    b
})
proc2summary <- do.call(rbind,(proc2List))

#Procedure 3

proc3 <- function(size){
    lof <- lof(get(paste0("gower_dist_",size),envir = .GlobalEnv), k=5)
}

由于 k 必须小于观察次数,我们将 sizes 向量调整为从 10 开始程序 3。

# configure run sizes
sizes <- c(10,50,100,200,500,1000)

proc3List <- lapply(sizes,function(x){
    b <- microbenchmark(proc3(x))
    b$obs <- x
    b
})
proc3summary <- do.call(rbind,(proc3List))

为前三个过程中的每一个生成了 运行 时间基准,我们绑定汇总数据,用 dplyr::summarise() 总结为均值,并用 ggplot() 绘图。

do.call(rbind,list(proc1summary,proc2summary,proc3summary)) %>% 
    group_by(expr,obs) %>%
    summarise(.,time_ms = mean(time) * .000001) -> proc_time 

生成的数据框包含生成图表所需的所有信息:使用的过程、原始数据框中的观察次数以及以毫秒为单位的平均时间。

> head(proc_time)
# A tibble: 6 x 3
# Groups:   expr [1]
  expr       obs time_ms
  <fct>    <dbl>   <dbl>
1 proc1(x)     5   0.612
2 proc1(x)    10   0.957
3 proc1(x)    50   1.32 
4 proc1(x)   100   2.53 
5 proc1(x)   200   5.78 
6 proc1(x)   500  25.9 

最后,我们使用 ggplot() 生成 xy 图表,按使用的程序对线进行分组。

ggplot(proc_time,aes(obs,time_ms,group = expr)) +
    geom_line(aes(group = expr),color = "grey80") + 
    geom_point(aes(color = expr))

...输出:

由于程序 2 和 3 仅略有不同,k = 3k = 5,它们在图表中几乎无法区分。

结论

结合包装函数和 lapply() 我们可以生成生成原始 post 中请求的图表所需的信息。

修改的一般模式是:

  1. 将原始过程包装在我们可以用作 microbenchmark() 分析单元的函数中,并包含一个 size 参数
  2. 修改程序以在必要时使用 size 作为变量
  3. 根据size参数
  4. 修改程序以访问前面步骤中的对象
  5. 修改程序以使用 assign()size 编写其输出(如果后续程序步骤需要这些输出)

我们根据数据框大小保留基准测试程序 4 - 7 的自动化,并将它们集成到图中作为原始 poster 的有趣练习。