如何在 R / tidyverse 中为管道函数使用进度条

How can I use a progress bar for piped functions in R / tidyverse

提问人:Moohan 提问时间:5/4/2023 更新时间:5/17/2023 访问量:255

问:

我有一个 main 函数,它可以对某些数据执行一些各种复杂(且长时间运行)的计算,它使用 tidyverse / magrittr 的管道执行这些步骤。我想要一个进度条来报告处理阶段,但是,我不知所措。我看过 和 包,从中我只能开始工作(在某种程度上。cliprogressprogressrcli

下面是一个最小的示例:

library(tidyverse)
library(cli)

main_fun <- function() {
  cli_progress_step(msg = "Running main function")
  tibble(a = 1:5) %>% 
    fun1() %>% 
    fun2() %>% 
    fun3()
}

fun1 <- function(data) {
  cli_progress_step(msg = "Doing sub function 1")
  Sys.sleep(2)

  return(data)
}
fun2 <- function(data) {
  cli_progress_step(msg = "Doing sub function 2")
  Sys.sleep(1)

  return(data)
}
fun3 <- function(data) {
  cli_progress_step(msg = "Doing sub function 3")
  Sys.sleep(3)

  return(data)
}

main_fun()
#> ℹ Running main function
#> ℹ Doing sub function 3
#> ℹ Doing sub function 2
#> ℹ Doing sub function 1
#> ✔ Doing sub function 1 [2s]
#> 
#> ℹ Doing sub function 2✔ Doing sub function 2 [3s]
#> 
#> ℹ Doing sub function 3✔ Doing sub function 3 [6.1s]
#> 
#> ℹ Running main function✔ Running main function [6.1s]
#> # A tibble: 10 × 1
#>        a
#>    <int>
#>  1     1
#>  2     2
#>  3     3
#>  4     4
#>  5     5

这将显示进度条,但以“相反”的顺序显示,即 3、2、1。一旦全部完成,所有内容都会显示出来,这是我唯一满意的地方。

r tidyverse 命令行界面 进度条 magrittr

评论

0赞 Robert Schauner 5/6/2023
我通过删除和执行常规作业来让它工作。我不知道为什么。这可能是管道实现影响 cli 进度条的问题。我也尝试了原生管道,遇到了同样的问题。较短函数中的管道不是必需的,因为对象仅存在于函数作用域中。这并不能真正回答问题,但这是一种解决方法。%>%<-|>

答:

5赞 GKi 5/15/2023 #1

这是因为,在管道中,函数不是从左到右计算的。用于评估的常规 R 语义应用 - 延迟评估或按需调用。 您对基础管道的调用将如下所示:|>

fun3(fun2(fun1(tibble(a = 1:5))))

您可以强制进行评估,例如使用 .forceAndCall

data.frame(a = 1:5) |> forceAndCall(n=1, Fun=fun1, data=_) |>
  forceAndCall(n=1, Fun=fun2, data=_) |> forceAndCall(n=1, Fun=fun3, data=_)
#✔ Doing sub function 1 [2s]
#✔ Doing sub function 2 [1s]
#✔ Doing sub function 3 [3s]
#...

或者,您可以使用急切的管道从左到右评估表单(感谢@Moohan的评论!magrittr%!>%

data.frame(a = 1:5) %!>% fun1() %!>%  fun2() %!>% fun3()
#✔ Doing sub function 1 [2s]
#✔ Doing sub function 2 [1s]
#✔ Doing sub function 3 [3s]
#...

您可以在函数的第一行中对函数参数进行计算,这将如您预期的那样产生结果。这适用于管道和 .force|>%>%

library(magrittr)
library(cli)

fun1 <- function(data) {
  force(data) #or simple only data
  cli_progress_step(msg = "Doing sub function 1")
  Sys.sleep(2)
  data
}
fun2 <- function(data) {
  force(data)
  cli_progress_step(msg = "Doing sub function 2")
  Sys.sleep(1)
  data
}
fun3 <- function(data) {
  force(data)
  cli_progress_step(msg = "Doing sub function 3")
  Sys.sleep(3)
  data
}

data.frame(a = 1:5) %>% fun1() %>% fun2() %>% fun3()
#✔ Doing sub function 1 [2s]
#✔ Doing sub function 2 [1s]
#✔ Doing sub function 3 [3s]
#✔ Running main function [6.1s]
#...

data.frame(a = 1:5) |> fun1() |> fun2() |> fun3()
#✔ Doing sub function 1 [2s]
#✔ Doing sub function 2 [1s]
#✔ Doing sub function 3 [3s]
#✔ Running main function [6.1s]
#...

另一种方法是编写自定义管道函数。

`:=` <- function(lhs, rhs) eval(substitute(rhs), list(. = lhs))

data.frame(a = 1:5) := fun1(.) := fun2(.) := fun3(.)
#✔ Doing sub function 1 [2s]
#✔ Doing sub function 2 [1s]
#✔ Doing sub function 3 [3s]
#...

另一个示例显示了进入和退出函数时的情况。

library(magrittr)
f1 <- \(x) {message("IN 1"); x$b <- 1; message("OUT 1"); x}
f2 <- \(x) {message("IN 2"); x$c <- 2; message("OUT 2"); x}

data.frame(a=0) %>% f1 %>% f2
#IN 2
#IN 1
#OUT 1
#OUT 2
#  a b c
#1 0 1 2

data.frame(a=0) |> f1() |> f2()
#IN 2
#IN 1
#OUT 1
#OUT 2
#  a b c
#1 0 1 2

f2(f1(data.frame(a=0)))
#IN 2
#IN 1
#OUT 1
#OUT 2
#  a b c
#1 0 1 2

data.frame(a=0) %!>% f1 %!>% f2
#IN 1
#OUT 1
#IN 2
#OUT 2
#  a b c
#1 0 1 2

data.frame(a=0) := f1(.) := f2(.)
#IN 1
#OUT 1
#IN 2
#OUT 2
#  a b c
#1 0 1 2

. <- data.frame(a=0)
. <- f1(.)
#IN 1
#OUT 1
. <- f2(.)
#IN 2
#OUT 2
.
#  a b c
#1 0 1 2

评论

0赞 Moohan 5/15/2023
我想我没有考虑过你布置的评估顺序,但这是有道理的......也就是说,是否有一种技巧/解决方法可以使消息按我们的预期显示,尽管 R 在“相反”方向上评估?
1赞 GKi 5/15/2023
也许你的解决方案是编写一个自己的管道函数?
0赞 Moohan 5/15/2023
谢谢!我想知道使用可能有什么缺点,我遇到了 magrittr 的“急切管道”。这基本上是您的解决方案!magrittr.tidyverse.org/reference/pipe-eager.htmlforce()
4赞 Moohan 5/15/2023 #2

这可以使用 'eager pipe' () 来实现%!>%{magrittr}

library(tidyverse)
library(cli)
library(magrittr)

main_fun <- function() {
  cli_progress_step(msg = "Running main function")
  tibble(a = 1:5) %!>% 
    fun1() %!>% 
    fun2() %!>% 
    fun3()
}

main_fun()

#> ℹ Running main function
#> ℹ Doing sub function 1
#> ✔ Doing sub function 1 [2s]
#> 
#> ℹ Running main functionℹ Doing sub function 2
#> ✔ Doing sub function 2 [1s]
#> 
#> ℹ Running main functionℹ Doing sub function 3
#> ✔ Doing sub function 3 [3s]
#> 
#> ℹ Running main function✔ Running main function [6.1s]
#> # A tibble: 10 × 1
#>        a
#>    <int>
#>  1     1
#>  2     2
#>  3     3
#>  4     4
#>  5     5