Simple gridded data animations in R

I have uploaded a video of some simple gridded data animations. The frames are made in R. I have provided some stock code which can make animation frames of your matrix image in several ways.

The function is fairly strict, but at least you can see what I am doing here. It will only take a matrix. It will only generate png files. I tend not to use the animation package for many animations. It’s a truly awesome, fun, and useful package for sure. However, if I am doing something with a large number of frames, using large data sets, and/or the images I am exporting are high resolution and relatively large file sizes themselves, I prefer to export the frames only and load them as a still image sequence in a dedicated video editing program to make the final step from frames to video.

These animations are nothing fancy. They are based only on ordered cell indices or a few randomized methods. Even with the randomized methods, sequential grid cell sampling in X and Y (columns and rows of matrix) are independent. It also requires the user to define their own vector(s) of sampling probabilities. Nevertheless, the general ideas here can be combined with the right data, the right grid cell sampling probabilities, and other data visualization techniques to yield some really cool sequences. I will post a follow up with more examples.

fill_grid <- function(x, size=1, frames.args,
  fill="ordered", byrow=FALSE, prob.row=NULL, prob.col=NULL){
  
  stopifnot(is.matrix(x))
  nr <- nrow(x)
  nc <- ncol(x)
  n <- nr*nc
  stopifnot(size > 1 & size <= n)
  stopifnot(fill %in% c("ordered", "random"))
  if(fill=="random"){
    if(!is.null(prob.row) && length(prob.row) != nr) stop("Row cell probabilities must have length==nrow(x).")
    if(!is.null(prob.col) && length(prob.col) != nc) stop("Column cell probabilities must have length==ncol(x).")
  }
  
  plot_mat <- function(mdata, frame.number, fill, byrow,
    file="frame", width=480, height=480,
    bg="transparent", clrs=heat.colors(30), alpha=NULL){
    
    if(fill=="ordered" & !byrow) mdata <- t(mdata)
    frame.ind <- paste0(paste0(rep(0, 4-nchar(frame.number)), collapse=""), frame.number)
    png(filename=paste0(file, frame.ind, ".png"), width=width, height=height, bg=bg)
    par(mai=c(0,0,0,0))
    image(mdata, col=clrs, useRaster=T, axes=F)
    dev.off()
    
  }
  
  alpha <- frames.args$alpha
  
  if(!is.null(alpha)){
    alpha <- max(1, min(100*alpha, 99))
    if(alpha < 10) alpha <- paste0(0, alpha)
    frames.args$clrs <- paste0(frames.args$clrs, alpha)
  }
  end.ind <- unique(round(c(seq(size, n, by=size), n)))
  if(fill=="ordered" & !byrow) x <- t(x)
  x.tmp <- x
  x.tmp[] <- NA
  k0 <- k <- 1
  ind <- k0:end.ind[k]
  frames.args$byrow <- byrow
  frames.args$fill <- fill
  
  if(fill=="ordered") {
  
    x.tmp[ind] <- x[ind]
    frames.args$mdata <- x.tmp
    frames.args$frame.number <- k
    do.call(plot_mat, frames.args)
    
    for(k in 2:length(end.ind)){
      k0 <- end.ind[k-1] + 1
      ind <- k0:end.ind[k]
      x.tmp[ind] <- x[ind]
      frames.args$mdata <- x.tmp
      frames.args$frame.number <- k
      do.call(plot_mat, frames.args)
      print(length(end.ind)-k)
    }
  } else if(fill=="random") {
    if(is.null(prob.row)) prob.row <- rep(rep(1, nr), nc) else prob.row <- rep(prob.row, nc)
    if(is.null(prob.col)) prob.col <- rep(rep(1, nc), each=nr) else prob.col <- rep(prob.col, each=nr)
    probs <- prob.row*prob.col
    grd <- as.matrix(expand.grid(1:nr, 1:nc))
    rows <- sample(1:n, size=n, replace=FALSE, prob=probs)
    grd <- grd[rows,]
    ind <- grd[ind,]
    x.tmp[ind] <- x[ind]
    frames.args$mdata <- x.tmp
    frames.args$frame.number <- k
    do.call(plot_mat, frames.args)
    
    for(k in 2:length(end.ind)){
      k0 <- end.ind[k-1] + 1
      ind <- k0:end.ind[k]
      ind <- grd[ind,]
      x.tmp[ind] <- x[ind]
      frames.args$mdata <- x.tmp
      frames.args$frame.number <- k
      do.call(plot_mat, frames.args)
      print(length(end.ind)-k)
    }
  }
}

Below is an example. Set your working directory or specify full paths so you know where your png files are being written. As for making a video from the frames, however, that is up to you how you wish to do that. For something as simple as this example, I would just use the animation package to make a gif. This function, for what it does, is more useful if you intend to take a large set of frames elsewhere.

Note that row and column probability vectors refer to the rows and columns of the matrix object. This has a different orientation by 90 degrees from what you see plotted by image, so plan accordingly for that. (See image help file for explanation if needed.) Since these vectors have to be of proper row and column length, I figured it would only be more confusing to swap the names to try and match the image rather than the matrix. The probability vector assigned to the prob.col argument below will show in the resulting image frames as applied across “rows” of the image.

A better approach is to make probability assignments independent of row and column length and have them mapped internally by the function. This way, the direction of pixel spread across frames as assigned by the user could be more intuitive and based on the image itself. Specifically, it would be more user friendly to provide options for a discrete set of animations and directions by name, where the user enters, e.g., type="random", direction="LR" and let the function deal with the details. Contrary to the fill="random" option, the byrow argument used during ordered filling actually does what seems intuitive from the perspective of the resultant image frames, not according to the input matrix. Not very consistent of me, I know, but this is just an impromptu function for illustration purposes. In any event, my perspective on a function like this is that, with some more improvements, it will serve me well as a helper function in the context of more advanced graphics production.

data(volcano)
size <- prod(dim(volcano))/20
arglist <- list(width=480, height=480)
pc <- (1:ncol(volcano))^5
set.seed(47)
fill_grid(volcano, size=size, frames.args=arglist, fill="random", prob.col=pc)
This entry was posted by Matt Leonawicz.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: