Wrapper to FOR loops with progress bar

前端 未结 8 681
走了就别回头了
走了就别回头了 2021-01-31 19:57

I like to use a progress bar while running slow for loops. This could be done easily with several helpers, but I do like the tkProgressBar from tcl

8条回答
  •  陌清茗
    陌清茗 (楼主)
    2021-01-31 20:25

    My solution is very similar to Andrie's except it uses base R, and I second his comments on the need to wrap what you want to do in a function and the subsequent need to use <<- to modify stuff in a higher environment.

    Here's a function that does nothing, and does it slowly:

    myfun <- function(x, text) {
      Sys.sleep(0.2)
      cat("running ",x, " with text of '", text, "'\n", sep="")
      x
    }
    

    Here's my forp function. Note that regardless of what we're actually looping over, it instead loops over the sequence 1:n instead and get the right term of what we actually want within the loop. plyr does this automatically.

    library(tcltk)
    forp <- function(x, FUN, ...) {
      n <- length(x)
      pb <- tkProgressBar(title = "Working hard:", min = 0, max = n, width = 300)
      out <- vector("list", n)
      for (i in seq_len(n)) {
        out[[i]] <- FUN(x[i], ...)
        setTkProgressBar(pb, i, label=paste( round(i/n*100, 0), "% ready!"))
      }
      close(pb)
      invisible(out)
    }
    

    And here's how both for and forp might be used, if all we want to do is call myfun:

    x <- LETTERS[1:5]
    for(xi in x) myfun(xi, "hi")
    forp(x, myfun, text="hi")
    

    And here's how they might be used if we want to modify something along the way.

    out <- "result:"
    for(xi in x) {
      out <- paste(out, myfun(xi, "hi"))
    }
    
    out <- "result:"
    forp(x, function(xi) {
        out <<- paste(out, myfun(xi, "hi"))
    })
    

    For both versions the result is

    > out
    [1] "result: A B C D E"
    

    EDIT: After seeing your (daroczig's) solution, I have another idea that might not be quite so unwieldy, which is to evaluate the expression in the parent frame. This makes it easier to allow for values other than i (now specified with the index argument), though as of right now I don't think it handles a function as the expression, though just to drop in instead a for loop that shouldn't matter.

    forp2 <- function(index, x, expr) {
      expr <- substitute(expr)
      n <- length(x)
      pb <- tkProgressBar(title = "Working hard:", min = 0, max = n, width = 300)
      for (i in seq_len(n)) {
        assign(index, x[i], envir=parent.frame())
        eval(expr, envir=parent.frame())
        setTkProgressBar(pb, i, label=paste( round(i/n*100, 0), "% ready!"))
      }
      close(pb)
    }
    

    The code to run my example from above would be

    out <- "result:"
    forp2("xi", LETTERS[1:5], {
        out <- paste(out, myfun(xi, "hi"))
    })
    

    and the result is the same.

    ANOTHER EDIT, based on the additional information in your bounty offer:

    The syntax forX(1:1000) %doX$ { expression } is possible; that's what the foreach package does. I'm too lazy right now to build it off of your solution, but building off mine, it could look like this:

    `%doX%` <- function(index, expr) {
      x <- index[[1]]
      index <- names(index)
      expr <- substitute(expr)
      n <- length(x)
      pb <- tkProgressBar(title = "Working hard:", min = 0, max = n, width = 300)
      for (i in seq_len(n)) {
        assign(index, x[i], envir=parent.frame())
        eval(expr, envir=parent.frame())
        setTkProgressBar(pb, i, label=paste( round(i/n*100, 0), "% ready!"))
      }
      close(pb)
      invisible(out)
    }
    
    forX <- function(...) {
      a <- list(...)
      if(length(a)!=1) {
        stop("index must have only one element")
      }
      a
    }
    

    Then the use syntax is this, and the result is the same as above.

    out <- "result:"
    forX(xi=LETTERS[1:5]) %doX% {
      out <- paste(out, myfun(xi, "hi"))
    }
    out
    

提交回复
热议问题