Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Wrapper to FOR loops with progress bar

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 tcltk package.

A small example:

pb <- tkProgressBar(title = "Working hard:", min = 0, max = length(urls), width = 300)
for (i in 1:300) {
    # DO SOMETHING
    Sys.sleep(0.5)
    setTkProgressBar(pb, i, label=paste( round(i/length(urls)*100, 0), "% ready!"))
}
close(pb)

And I would like to set up a small function to store in my .Rprofile named to forp (as: for loop with progressbar), to call just like for but with auto added progress bar - but unfortunately have no idea how to implement and grab the expr part of the loop function. I had some experiments with do.call but without success :(

Imaginary working example (which acts like a for loop but creates a TkProgressBar and auto updates it in each iteration):

forp (i in 1:10) {
    #do something
}

UPDATE: I think the core of the question is how to write a function which not only has parameters in the parentheses after the function (like: foo(bar)), but also can handle expr specified after the closing parentheses, like: foo(bar) expr.


BOUNTY OFFER: would go to any answer that could modify my suggested function to work like the syntax of basic for loops. E.g. instead of

> forp(1:1000, {
+   a<-i
+ })
> a
[1] 1000

it could be called like:

> forp(1:1000) {
+   a<-i
+ }
> a
[1] 1000

Just to clarify the task again: how could we grab the { expression } part of a function call? I am afraid that this is not possible, but will leave on the bounty for a few days for the pros :)

like image 318
daroczig Avatar asked Sep 08 '11 14:09

daroczig


People also ask

How do I put progress bar in for loop?

Instead of printing out indices or other info at each iteration of your Python loops to see the progress, you can easily add a progress bar. wrap the object on which you iterate with pbar() . and it will display a progress that automatically updates itself after each iteration of the loop.


4 Answers

Given the other answers supplied, I suspect that it is impossible tough to do in exactly the way you specify.

However, I believe there is a way of getting very close, if you use the plyr package creatively. The trick is to use l_ply which takes a list as input and creates no output.

The only real differences between this solution and your specification is that in a for loop you can directly modify variables in the same environment. Using l_ply you need to send a function, so you will have to be more careful if you want to modify stuff in the parent environment.

Try the following:

library(plyr)
forp <- function(i, .fun){
  l_ply(i, .fun, .progress="tk")
}

a <- 0
forp(1:100, function(i){
  Sys.sleep(0.01)
  a<<-a+i
  })
print(a)
[1] 5050

This creates a progress bar and modifies the value of a in the global environment.


EDIT.

For the avoidance of doubt: The argument .fun will always be a function with a single argument, e.g. .fun=function(i){...}.

For example:

for(i in 1:10){expr} is equivalent to forp(1:10, function(i){expr})

In other words:

  • i is the looping parameter of the loop
  • .fun is a function with a single argument i
like image 109
Andrie Avatar answered Oct 22 '22 18:10

Andrie


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
like image 28
Aaron left Stack Overflow Avatar answered Oct 22 '22 18:10

Aaron left Stack Overflow


If you use the plyr family of commands instead of a for loop (generally a good idea if possible), you get as an added bonus a whole system of progress bars.

R.utils also has some progress bars built into it, and there exist instructions for using them in for loops.

like image 22
Ari B. Friedman Avatar answered Oct 22 '22 18:10

Ari B. Friedman


R's syntax doesn't let you do exactly what you want, ie:

forp (i in 1:10) {
    #do something
}

But what you can do is create some kind of iterator object and loop using while():

while(nextStep(m)){sleep.milli(20)}

Now you have the problem of what m is and how you make nextStep(m) have side effects on m in order to make it return FALSE at the end of your loop. I've written simple iterators that do this, as well as MCMC iterators that let you define and test for a burnin and thinning period within your loop.

Recently at the R User conference I saw someone define a 'do' function that then worked as an operator, something like:

do(100) %*% foo()

but I'm not sure that was the exact syntax and I'm not sure how to implement it or who it was put that up... Perhaps someone else can remember!

like image 25
Spacedman Avatar answered Oct 22 '22 18:10

Spacedman