Creating animation in R - r

I'm looking at the possibilities for animation creation in R. The {Animation} package seems to be mainly an R platform for ffmpeg and imagemagick. The only reference I found to creating individual image frames is to nest plot() inside a loop. But this seems an unfeasibly slow process for producing more complex plots given the poor speed performance of the png() renderer, particularly for plots including map objects - for example:
library(maptools)
data(wrld_simpl)
starttime = Sys.time()
for(i in 1:10){
png(paste('frames/', i, '.png', sep=''))
plot(wrld_simpl, col='grey85', bg = 'white', border='white')
points(sample(-180:180, 50), sample(-90:90, 50), col='red', pch=16, cex=2)
title('poxy map')
dev.off()
}
print(Sys.time() - starttime)
yielding 10 frames and:
Time difference of 9.763794 secs
I don't understand why R is so slow at rendering - at this rate it would take 45 mins or so to render a 2 minute video at 25fps, which seems slow for this relatively simple map example. Wrapping with apply is no quicker. Does anyone know of a way to wrap plot more efficiently, or perhaps to save a plot midway after the unchanging elements have been rendered?

Plotting the map as an image with just enough resolution should be more efficient than the plot method for SpatialPolygonsDataFrame.
require(maps) # save the world
png("world.png", width=500, height=200)
map("world", col="grey90", fill=TRUE, border="grey90", mar=c(0,0,0,0))
dev.off()
library(png); library(grid)
img = readPNG("world.png")
animation::saveGIF( {
for( ii in 1:100) {
grid.newpage()
grid.raster(img)
grid.points(default.units="npc")
}
}, ani.height=200, ani.width=500)

Related

Problem with getting an image of a graph made in ggplot2 [duplicate]

In R, I use function savePlot to save graphs into image files. But my colleague can only open .jpgs and .gifs (probably because he's on vacation, reading emails on his mobile phone). I hate to create jpegs because especially the boxplots looks very ugly (whiskers blurred etc.). But the savePlot function only supports the following types:
type = c("wmf", "emf", "png", "jpg", "jpeg", "bmp",
"tif", "tiff", "ps", "eps", "pdf")
How can I save plot in GIF in R?
EDIT: if possible, ideal solution should work without installing ImageMagick (so that the R script is easily portable).
R doesn't have a native GIF graphics driver, mostly (completely?) due to the patent-encumbrances of the GIF format: see http://tolstoy.newcastle.edu.au/R/help/05/02/12809.html .
There is a function in the caTools package (write.gif()) but it is specifically designed for writing images. If you wanted to use it you have to do something hacky to convert your plot to an image first (e.g. save as PNG and then read it back into R as an image). For example:
png("myPlot.png")
plot(rnorm(1000),rnorm(1000))
dev.off()
library(png)
P1 <- readPNG("myPlot.png")
library(caTools)
write.gif(P1,"myPlot.gif")
showGIF <- function(fn) system(paste("display",fn))
showGIF("myPlot.gif")
unlink("myPlot.gif") ## clean up
?write.gif() has a lot of stuff about color indexing that I didn't read but that might be important for more complex graphs ...
The animation package has a saveGIF() function to save GIFs, but (1) it is designed for saving multi-frame animations (not general graphics), and (2) it does it by calling ImageMagick.
It's easier just to construct that function yourself.
install ImageMagick (http://imagemagick.org)
save as a PNG, then use ImageMagick to convert.
For example:
png("myPlot.png")
plot(rnorm(1000),rnorm(1000))
dev.off()
system("convert myPlot.png myPlot.gif")
unlink("myPlot.png") ## clean up
showGIF("myPlot.gif")
unlink("myPlot.gif") ## clean up
Of course you can either of these in a function if you want to use them regularly.
UPDATE: I spent a while longer on this, to try to get a pure-R solution, but don't yet have a working solution. Suggestions or edits welcome ...
## needs ImageMagick: just for testing ...
showGIF <- function(fn) system(paste("display",fn))
The main function:
saveGIF <- function(fn,verbose=FALSE,debug=FALSE) {
require(png)
require(caTools)
tmpfn <- tempfile()
on.exit(unlink(tmpfn))
savePlot(tmpfn,type="png")
P1 <- readPNG(tmpfn)
dd <- dim(P1)
P1 <- aperm(P1,c(3,1,2),resize=TRUE) ## P1[,1,15]
dim(P1) <- c(dd[3],prod(dd[1:2]))
P1 <- t(P1)
if (verbose) cat("finding unique colours ...\n")
P1u <- unique(P1)
rgbMat <- function(x) {
rgb(x[,1],x[,2],x[,3])
}
if (verbose) cat("creating colour index ...\n")
pp <- paste(P1[,1],P1[,2],P1[,3],sep=".")
## make sure factor is correctly ordered
ind <- as.numeric(factor(pp,levels=unique(pp)))
if (verbose) cat("finding colour palette ...\n")
if (nrow(P1u)>256) {
if (verbose) cat("kmeans clustering ...\n")
kk <- kmeans(P1u,centers=256)
ind <- kk$cluster[ind]
pal <- rgbMat(kk$centers)
} else {
pal <- rgbMat(P1u)
}
## test:
if (debug) {
dev.new()
par(mar=rep(0,4))
image(t(matrix(ind-1,nrow=dd[1])),col=pal,axes=FALSE,ann=FALSE)
}
if (verbose) cat("writing GIF ...\n")
indmat <- matrix(ind-1,nrow=dd[1])
storage.mode(indmat) <- "integer"
write.gif(indmat,fn,col=as.list(pal),scale="never")
}
X11.options(antialias="none")
image(matrix(1:64,nrow=8),col=rainbow(10))
saveGIF("tmp.gif",verbose=TRUE,debug=TRUE)
showGIF("tmp.gif")

Plot 3D orography of a grid

I'd like to replicate something like the following plot using R, possibly using ggplot (though I doubt it is possible as, AFAIK, it has no 3D capabilities). The data I have is usually a raster file from the raster package, but I can transform it in the most suitable format.
The plot is taken from:
"Climate Change 2013: The Physical Science Basis. Contribution of Working Group I to the Fifth Assessment Report of the Intergovernmental Panel on Climate Change", figure 1.14. I have no idea which software has produced that plot.
I guess the only is to use lattice::cloud (wireframe) or something like this? I can't seem to find any way to force wireframe to have a ind of barplot instead of a surface plot; additionally, the coloring based on the height over the sea, where the grid is kept level, is probably impossible..
I found some time to look at this. In the end I took the following approach (linked R code):
http://pastebin.com/dA2nNNS0
Thanks to those in the comments who pointed me in the right direction.
I still have problems saving the file but that's material for another Question.
Code from pastebin:
library(raster)
library(rgl)
r <- raster("topo12.nc", varname="topo") #Read file
cols <- terrain.colors(3100) #Define colors
binplot.3d <- function(x,y,z,alpha=1,topcol="#ff0000",sidecol="#aaaaaa"){ #Binplotting function
save <- par3d(skipRedraw=TRUE)
on.exit(par3d(save))
x1<-c(rep(c(x[1],x[2],x[2],x[1]),3),rep(x[1],4),rep(x[2],4))
z1<-c(rep(0,4),rep(c(0,0,z,z),4))
y1<-c(y[1],y[1],y[2],y[2],rep(y[1],4),rep(y[2],4),rep(c(y[1],y[2],y[2],y[1]),2))
x2<-c(rep(c(x[1],x[1],x[2],x[2]),2),rep(c(x[1],x[2],rep(x[1],3),rep(x[2],3)),2))
z2<-c(rep(c(0,z),4),rep(0,8),rep(z,8) )
y2<-c(rep(y[1],4),rep(y[2],4),rep(c(rep(y[1],3),rep(y[2],3),y[1],y[2]),2) )
rgl.quads(x1,z1,y1,col=rep(sidecol,each=4),alpha=alpha)
rgl.quads(c(x[1],x[2],x[2],x[1]),rep(z,4),c(y[1],y[1],y[2],y[2]),
col=rep(topcol,each=4),alpha=1)
rgl.lines(x2,z2,y2,col="#000000")
}
cat("Row ( of", dim(r)[1],"):")
for (row in 1:dim(r)[1]) { #Plotting loop
for (col in 1:dim(r)[2]) {
if (round(r[row, col]) < 1) {
binplot.3d(c(col-1,col), c(row-1,row), r[row, col]/500, alpha=1, topcol="cadetblue3")
} else {
binplot.3d(c(col-1,col), c(row-1,row), r[row, col]/500, alpha=1, topcol=cols[round(r[row, col])])
# cat(round(r[row, col]), "\t")
}
}
cat(row, "")
}

Combining multiple complex plots as panels in a single figure

Introduction by #backlin
Multiple simple plots can combined as panels in a single figure by using layout or par(mfrow=...). However, more complex plots tend to setup their own panel layout internally disabling them from being used as panels. Is there a way to create a nested layout and encapsulating a complex plot into a single panel?
I have a feeling the grid package can accomplish this, e.g. by ploting the panels in separate viewports, but haven't been able to figure out how. Here is a toy example to demonstrate the problem:
my.plot <- function(){
a <- matrix(rnorm(100), 10, 10)
plot.new()
par(mfrow=c(2,2))
plot(1:10, runif(10))
plot(hclust(dist(a)))
barplot(apply(a, 2, mean))
image(a)
}
layout(matrix(1:4, 2, 2))
for(i in 1:4) my.plot()
# How to avoid reseting the outer layout when calling `my.plot`?
Original question by #alittleboy
I use the heatmap.2 function in the gplots package to generate heatmaps. Here is a sample code for a single heatmap:
library(gplots)
row.scaled.expr <- matrix(sample(1:10000),nrow=1000,ncol=10)
heatmap.2(row.scaled.expr, dendrogram ='row',
Colv=FALSE, col=greenred(800),
key=FALSE, keysize=1.0, symkey=FALSE, density.info='none',
trace='none', colsep=1:10,
sepcolor='white', sepwidth=0.05,
scale="none",cexRow=0.2,cexCol=2,
labCol = colnames(row.scaled.expr),
hclustfun=function(c){hclust(c, method='mcquitty')},
lmat=rbind( c(0, 3), c(2,1), c(0,4) ), lhei=c(0.25, 4, 0.25 ),
)
However, since I want to compare multiple heatmaps in a single plot, I use par(mfrow=c(2,2)) and then call heatmap.2 four times, i.e.
row.scaled.expr <- matrix(sample(1:10000),nrow=1000,ncol=10)
arr <- array(data=row.scaled.expr, dim=c(dim(row.scaled.expr),4))
par(mfrow=c(2,2))
for (i in 1:4)
heatmap.2(arr[ , ,i], dendrogram ='row',
Colv=FALSE, col=greenred(800),
key=FALSE, keysize=1.0, symkey=FALSE, density.info='none',
trace='none', colsep=1:10,
sepcolor='white', sepwidth=0.05,
scale="none",cexRow=0.2,cexCol=2,
labCol = colnames(arr[ , ,i]),
hclustfun=function(c){hclust(c, method='mcquitty')},
lmat=rbind( c(0, 3), c(2,1), c(0,4) ), lhei=c(0.25, 4, 0.25 ),
)
However, the result is NOT four heatmaps in a single plot, but four separate heatmaps. In other words, if I use pdf() to output the result, the file is four pages instead of one. Do I need to change any parameters somewhere? Thank you so much!
Okay. I suppose this question has been sitting unanswered for enough time that the long answer should be written up.
The answer to most difficult graphics issues is (as #backlin suggests) the raw use of the 'grid' package. Many prebuilt graphics packages override all current viewports and plot device settings, so if you want something done a very specific way, you have to build it yourself.
I recommend picking up Paul Murrell's book "R Graphics" and going over the chapter on the 'grid' package. It's a crazy useful book, and a copy sits on my desk all the time.
For your heatmap, I've written up a quick primer that will get you started quickly.
Functions to know
grid.newpage() This initializes the plotting device. Use it without parameters.
grid.rect() This draws a rectangle. Your heatmap is basically just a giant set of colored rectangles, so this will be bulk of your graphic. It works like so: grid.rect(x=x_Position, y=y_Position, width=width_Value, height=height_Value, gp=gpar(col=section_Color, fill=section_Color), just=c("left", "bottom"), default.units="native") The 'just' argument specifies which point of the rectangle will sit on your specified (x, y) coordinates.
grid.text() This draws text. It works like so: grid.text("Label Text", x_Value, y_Value, gp=gpar(col=color_Value, cex=font_Size), just=c("right","center"), rot=rot_Degrees, default.units="native")
grid.lines() This draws a line. It works like so: grid.lines(c(x_Start,x_End), c(y_Start, y_End), gp=gpar(col=color_Value), default.units="native")
dataViewport() This defines the attributes of a plotting window, which 'grid' refers to as a "viewport." Use it like so: pushViewport(dataViewport(xData=x_Data, yData=y_Data, xscale=c(x_Min, x_Max), yscale=c(y_Min, y_Max), x=x_Value, y=y_Value, width=width_Value, height=height_Value, just=c("left","center"))) There is some stuff to keep in mind here... see the more detailed explanation of viewports.
pushViewport() This is used to initialize a veiwport. You wrap this around a viewport definition to actually execute the viewport, like so: pushViewport(dataViewport([stuff in here]))
popViewport() This finalizes a viewport and moves you up one level in the hierarchy of viewports. See the more detailed explanation of viewports.
Viewports in a nutshell
Viewports are temporary drawing spaces that define where and how 'grid' objects will be drawn. Everything inside the viewport is drawn relative to the viewport. If the viewport is rotated, everything inside will be rotated. Viewports can be nested, can overlap, and are almost infinitely flexible, with one exception: they are always a rectangle.
Something that messes a lot of people up initially is the coordinate system. Every viewport, including the initial 'grid.newpage()' viewport, goes from 0 to 1 on both the x and y axes. The origin (0,0) is the far lower left corner, and the max (1,1) is the far upper right corner. This is the "npc" unit system, and everything that doesn't have a set of units specified will likely end up being drawn according to this system. This means two things for you:
Use the "npc" system when specifying viewport sizes and locations. Just assume that your viewports have to use the "npc" coordinates, and you'll save yourself a LOT of hassle. This means if I want to draw two plots next to each other, the definitions for the two viewports would look something like:
viewport(x=0, y=0, width=0.5, height=1, just=c("left","lower")) and
viewport(x=0.5, y=0, width=0.5, height=1, just=c("left","lower"))
If your viewport has a different coordinate system (for example a viewport for plotting a graph), then you will need to specify the 'default.units' argument for every 'grid' object you draw. For instance, if you tried to plot a point at (2,4) you would never see the point, because it would be far off-screen. Specifying default.units="native" would tell that point to use the viewport's own coordinate system and would draw the point correctly.
Viewports can be navigated and written to directly, but unless you're doing something very automated, it is easier to specify a viewport, draw inside it, and then "pop" (finalize) the viewport. This returns you to the parent viewport, and you can start on the next viewport. Popping each viewport is a clutter-free approach and will suit most purposes (and make it easier to debug!).
The 'dataViewport' function is all important when plotting a graph. This is a special type of viewport that handles all of the coordinates and scales for you, as long as you tell it what data you are using. This is the one I use for any plotting area. When I first started using the 'grid' package, I adjusted all of my values to fit the "npc" coordinate system, but that was a mistake! The 'dataViewport' function makes is all easy as long as you remember to use the "native" units for each drawing item.
Disclaimer
Data visualization is my forte, and I don't mind spending half a day scripting up a good visual. The 'grid' package allows me to create quite sophisticated visuals faster than anything else I found. I script up my visuals as functions, so I can load various data quickly. I couldn't be happier.
However, if you don't like to script things, 'grid' will be your enemy. Also, if you consider half a day to be too much time for a visual, then 'grid' won't help you too much. The (in)famous 'ggplot2' package is what most people settle on, and I heartily recommend it, even though I don't personally find it useful.
If someone wants help learning 'grid' graphics, I'm more than willing to help teach. It has completely revolutionized my ability to create fast, intelligent, and good-looking data visuals.
The gridGraphics package might help,
library(gridGraphics)
library(grid)
grab_grob <- function(){
grid.echo()
grid.grab()
}
arr <- replicate(4, matrix(sample(1:100),nrow=10,ncol=10), simplify = FALSE)
library(gplots)
gl <- lapply(1:4, function(i){
heatmap.2(arr[[i]], dendrogram ='row',
Colv=FALSE, col=greenred(800),
key=FALSE, keysize=1.0, symkey=FALSE, density.info='none',
trace='none', colsep=1:10,
sepcolor='white', sepwidth=0.05,
scale="none",cexRow=0.2,cexCol=2,
labCol = colnames(arr[[i]]),
hclustfun=function(c){hclust(c, method='mcquitty')},
lmat=rbind( c(0, 3), c(2,1), c(0,4) ), lhei=c(0.25, 4, 0.25 ),
)
grab_grob()
})
grid.newpage()
library(gridExtra)
grid.arrange(grobs=gl, ncol=2, clip=TRUE)
I struggled with a similar problem and came up with a solution that is very easy but requires imagemagick installed. The idea is to plot the heatmaps to separate files and then combine them with the montage command:
library(gplots)
row.scaled.expr <- matrix(sample(1:10000),nrow=1000,ncol=10)
arr <- array(data=row.scaled.expr, dim=c(dim(row.scaled.expr),4))
par(mfrow=c(2,2))
for (i in 1:4) {
ifile <- paste0(i,'_heatmap.pdf')
pdf(ifile)
heatmap.2(arr[ , ,i], dendrogram ='row',
Colv=FALSE, col=greenred(800),
key=FALSE, keysize=1.0, symkey=FALSE, density.info='none',
trace='none', colsep=1:10,
sepcolor='white', sepwidth=0.05,
scale="none",cexRow=0.2,cexCol=2,
labCol = colnames(arr[ , ,i]),
hclustfun=function(c){hclust(c, method='mcquitty')},
lmat=rbind( c(0, 3), c(2,1), c(0,4) ), lhei=c(0.25, 4, 0.25 ),
)
dev.off()
}
system('montage -geometry 100% -tile 2x2 ./*_heatmap.pdf outfile.pdf')
Just as Dinre said, the "grid" pacakge can handle all complex plots. For the original question by #alittleboy, I think the package "ComplexHeatmap" (which is also base on grid) from Bionconductor can be a nice solution (http://www.bioconductor.org/packages/release/bioc/vignettes/ComplexHeatmap/inst/doc/ComplexHeatmap.html)

In R, how to prevent blank page in pdf when using gridBase to embed subplot inside plot

As explained here, it is easy to embed a plot into an existing one thanks to gridBase, even though both plots use the base graphics system of R. However, when saving the whole figure into a pdf, the first page is always blank. How to prevent this?
Here is an example:
require(gridBase)
## generate dummy data
set.seed(1859)
x <- 1:100
y <- x + rnorm(100, sd=5)
ols <- lm(y ~ x)
pdf("test.pdf")
## draw the first plot
plot.new() # blank page also happens when using grid.newpage()
pushViewport(viewport())
plot(x, y)
## draw the second plot, embedded into the first one
pushViewport(viewport(x=.75,y=.35,width=.2,height=.2,just=c("center","center")))
par(plt=gridPLT(), new=TRUE)
hist(ols$residuals, main="", xlab="", ylab="")
popViewport(2)
dev.off()
I think it's a bit of a hack but setting onefile=FALSE worked on my machine:
pdf("test.pdf", onefile=FALSE)
In searching for an answer (which I didn't really find so much as stumbled upon in the forest) I came across this post to Rhelp from Paul Murrell who admits that mixing grid and base graphics is confusing even to the Master.
A work around solution I found was to initiate the pdf file inside the for loop; then insert an if clause to assess whether the first iteration is being run. When the current iteration is the first one, go ahead and create the output device using pdf(). Put the dev.off() after closing the for loop. An quick example follows:
for(i in 1:5){
if (i == 1) pdf(file = "test.pdf")
plot(rnorm(50, i, i), main = i)}
dev.off()

Clearing plotted points in R

I am trying to use the animation package to generate an "evolving" plot of points on a map. The map is generated from shapefiles (from the readShapeSpatial/readShapeLines functions).
The problem is when it's plotted in a for loop, the result is additive, whereas the ideal result is to have it evolve.
Are there ways of using par() that I am missing?
My question is: is there a way to clear just the points ploted from the points function
and not clearing the entire figure thus not having to regraph the shapefiles?
in case someone wants to see code:
# plotting underlying map
newyork <- readShapeSpatial('nycpolygon.shp')
routes <- readShapeLines('nyc.shp')
par(bg="grey25")
plot(newyork, lwd=2, col ="lightgray")
plot(routes,add=TRUE,lwd=0.1,col="lightslategrey")
# plotting points and save to GIF
ani.options(interval=.05)
saveGIF({
par(bg="grey25")
# Begin loop
for (i in 13:44){
infile <-paste("Week",i,".csv",sep='')
mydata <-read.csv(file = infile, header = TRUE, sep=",")
plotvar <- Var$Para
nclr <- 4
plotclr <-brewer.pal(nclr,"RdPu")
class<- classIntervals(plotvar,nclr,style = "pretty")
colcode <- findColours(class,plotclr)
points(Var$Lon,Var$Lat,col=colcode)
}
})
If you can accept a residual shadow or halo of ink, you can over-plot with color ="white" or == to your background choices. We cannot access your shape file but you can try it out by adding this line:
points(Var$Lon, Var$Lat, col="grey25")
It may leave gaps in other previously plotted figures or boundaries, because it's definitely not object-oriented. The lattice and ggplot2 graphics models are more object oriented, so if you want to post a reproducible example, that might be an alternate path to "moving" forward. I seem to remember that the rgl package has animation options in its repetoire.

Resources