How to save a chart object created in loops - r

I have a chart created inside a couple of loops and I want to automatically write the chart to a file at the end of the outer loop. Here is a toy example:
filename <- "mychart"
for(i in 1:5) {
x <- 1:5
fun1 <- sample(1:10, 5, replace = TRUE)
xlim <- c(1, 5)
ylim <- c(0, 10)
plot(x, fun1, xlim = xlim, ylim = ylim, type = "l")
for(j in 1:3) {
fun2 <- 2:6 + j
lines(x, fun2, type = "l", col = "red")
}
out.filename <- paste(filename, i, sep = "")
## want to save this plot out to disk here!
}
I would also like to create the plot on the console so I can watch the program’s progress. Most answers to a similar question seem to deal with a plot that is created with a single “plot” statement, or do not enable the console plot window. Any suggestions much appreciated.

I think this does what you're after:
plotit <- function(i) {
x = 1:5
fun1 = sample(1:10, 5, replace=TRUE)
plot(x, fun1, xlim=c(1,5), ylim=c(0,10), type="l")
for(j in 1:3) {
fun2 = 2:6 + j
lines(x, fun2, type = "l", col = "red")
}
savePlot(paste0("mychart", i, ".png"), type="png")
}
Then:
for(i in seq(5)) plotit(i)

The typical way to save base graphics plots is with individual device functions such as pdf(), png(), etc. You open a plot device with the appropriate filename, create your plot, then close the device with dev.off(). It doesn't matter if your plot is created in a for loop or not. See lots of devices (and examples at the bottom) in ?png.
For your code, it would go something like this:
filename <- "mychart"
for(i in 1:5) {
out.filename <- paste(filename, i, ".png", sep = "")
## Open the device before you start plotting
png(file = out.filename)
# you can set the height and width (and other parameters) or use defaults
x <- 1:5
fun1 <- sample(1:10, 5, replace = TRUE)
xlim <- c(1, 5)
ylim <- c(0, 10)
plot(x, fun1, xlim = xlim, ylim = ylim, type = "l")
for(j in 1:3) {
fun2 <- 2:6 + j
lines(x, fun2, type = "l", col = "red")
}
## Close the device when you are done plotting.
dev.off()
}

Related

LaTeX in plot titles using a loop

I just learned how to insert (a limited number of) LaTeX expressions into my plot titles with
expression(<LaTeX code>). How can I generate plots containing LaTeX in their titles using a loop? For example, say I have:
par(mfrow = c(2,2))
x <- seq(1,10,0.1)
y <- sin(x)
plot(x, y, main = expression(sigma[1]))
plot(x, y, main = expression(sigma[2]))
This produces the desired output:
How can I achieve the same output, but by replacing the last two lines with a loop? I tried
par(mfrow = c(2,2))
for (i in 1:2){
plot(x, y, main = expression(sigma[i]))
}
but the i was not interpreted as a variable:
Any solutions for this?
We can use bquote instead of expression. This allows partial unquoting, meaning you can substitute the value of i inside the expression by wrapping it like this: .(i)
par(mfrow = c(2,2))
x <- seq(1,10,0.1)
y <- sin(x)
for(i in 1:4) plot(x, y, main = bquote(paste("My plot for ", sigma[.(i)], " :")))
Created on 2022-02-19 by the reprex package (v2.0.1)
par(mfrow = c(2,2))
loop.vector <- 1:2
for (i in loop.vector) {
x <- seq(1,10,0.1)
y <- sin(x)
plot(x, y, main = bquote(sigma[.(i)]))
}
Another option is substitute
for (i in 1:2){
plot(x, y, main = substitute(paste("My plot for ", sigma[i]), list(i = i)))
}
-output
For your reference, there is also tikzDevice, which actually generates .tex:
sigma <- 2^(1:4)
x <- seq(0, 2 * pi, 0.01 * pi)
tikzDevice::tikz("sine.tex", standAlone = TRUE)
par(mfrow = c(2L, 2L))
for (i in seq_along(sigma)) {
y <- sin(sigma[i] * x)
plot(x, y, type = "o", xlab = "$x$", ylab = "$y$",
main = sprintf("$y = \\sin(\\sigma_{%d} x)$", i))
}
dev.off()
tools::texi2dvi("sine.tex", pdf = TRUE)
system(paste(getOption("pdfviewer"), "sine.pdf"))

Using base R, how to create a "joy plot" (aka ridgeline plots), with many distributions on top of each other with vertical offset?

The type of plot I am trying to achieve in R seems to have been known as either as moving distribution, as joy plot or as ridgeline plot:
There is already a question in Stackoverflow whose recorded answer explains how to do it using ggplot: How to reproduce this moving distribution plot with R?
However, for learning purposes, I am trying to achieve the same using only base R plots (no lattice, no ggplot, no any plotting package).
In order to get started, I generated the following fake data to play with:
set.seed(2020)
shapes <- c(0.1, 0.5, 1, 2, 4, 5, 6)
dat <- lapply(shapes, function(x) rbeta(1000, x, x))
names(dat) <- letters[1:length(shapes)]
Then using mfrow I can achieve this:
par(mfrow=c(length(shapes), 1))
par(mar=c(1, 5, 1, 1))
for(i in 1:length(shapes))
{
values <- density(dat[[names(dat)[i]]])
plot(NA,
xlim=c(min(values$x), max(values$x)),
ylim=c(min(values$y), max(values$y)),
axes=FALSE,
main="",
xlab="",
ylab=letters[i])
polygon(values, col="light blue")
}
The result I get is:
Clearly, using mfrow (or even layout) here is not flexible enough and also does allow for the overlaps between the distributions.
Then, the question: how can I reproduce that type of plot using only base R plotting functions?
Here's a base R solution. First, we calculate all the density values and then manually offset off the y axis
vals <- Map(function(x, g, i) {
with(density(x), data.frame(x,y=y+(i-1), g))
}, dat, names(dat), seq_along(dat))
Then, to plot, we calculate the overall range, draw an empty plot, and the draw the densities (in reverse so they stack)
xrange <- range(unlist(lapply(vals, function(d) range(d$x))))
yrange <- range(unlist(lapply(vals, function(d) range(d$y))))
plot(0,0, type="n", xlim=xrange, ylim=yrange, yaxt="n", ylab="", xlab="Value")
for(d in rev(vals)) {
with(d, polygon(x, y, col="light blue"))
}
axis(2, at=seq_along(dat)-1, names(dat))
d = lapply(dat, function(x){
tmp = density(x)
data.frame(x = tmp$x, y = tmp$y)
})
d = lapply(seq_along(d), function(i){
tmp = d[[i]]
tmp$grp = names(d)[i]
tmp
})
d = do.call(rbind, d)
grp = unique(d$grp)
n = length(grp)
spcx = 5
spcy = 3
rx = range(d$x)
ry = range(d$y)
rx[2] = rx[2] + n/spcx
ry[2] = ry[2] + n/spcy
graphics.off()
plot(1, type = "n", xlim = rx, ylim = ry, axes = FALSE, ann = FALSE)
lapply(seq_along(grp), function(i){
x = grp[i]
abline(h = (n - i)/spcy, col = "grey")
axis(2, at = (n - i)/spcy, labels = grp[i])
polygon(d$x[d$grp == x] + (n - i)/spcx,
d$y[d$grp == x] + (n - i)/spcy,
col = rgb(0.5, 0.5, 0.5, 0.5))
})

Fix layout / plot options of plot(fevd()) function

I am currently busy with a Vector Autoregressive Analysis in R, using the vars package. I was wondering if the following things are possible:
1) the previous solution for a better fit on the page worked properly, but now with more variables, we have more plots and the layout is screwed again. I already played with some of the win.graph() parameters, but nothing gives me a proper readable solution.
2) The plot(irf(...) function of the vars package gives a one-graph per page output. I was wondering if this is also possible for the plot(fevd() function by adding some extra (unkown to me) parameters options
3) Also, for readability, I would like to color the graphs, plot(fevd() gives an all-kinds-of-gray output, Is it possible to change those colors?
Thank you in advance
Olivier
You will have to modify the plot function for fevd to do what you want. Here's a modified plot.varfevd function that removes all calls to par(). This allows to use layout properly. The lines that have been removed have been commented out (#). I also removed the parameter that asked for confirmation in "single" plots.
plot.varfevd <-function (x, plot.type = c("multiple", "single"), names = NULL,
main = NULL, col = NULL, ylim = NULL, ylab = NULL, xlab = NULL,
legend = NULL, names.arg = NULL, nc, mar = par("mar"), oma = par("oma"),
addbars = 1, ...)
{
K <- length(x)
ynames <- names(x)
plot.type <- match.arg(plot.type)
if (is.null(names)) {
names <- ynames
}
else {
names <- as.character(names)
if (!(all(names %in% ynames))) {
warning("\nInvalid variable name(s) supplied, using first variable.\n")
names <- ynames[1]
}
}
nv <- length(names)
# op <- par(no.readonly = TRUE)
ifelse(is.null(main), main <- paste("FEVD for", names), main <- rep(main,
nv)[1:nv])
ifelse(is.null(col), col <- gray.colors(K), col <- rep(col,
K)[1:K])
ifelse(is.null(ylab), ylab <- rep("Percentage", nv), ylab <- rep(ylab,
nv)[1:nv])
ifelse(is.null(xlab), xlab <- rep("Horizon", nv), xlab <- rep(xlab,
nv)[1:nv])
ifelse(is.null(ylim), ylim <- c(0, 1), ylim <- ylim)
ifelse(is.null(legend), legend <- ynames, legend <- legend)
if (is.null(names.arg))
names.arg <- c(paste(1:nrow(x[[1]])), rep(NA, addbars))
plotfevd <- function(x, main, col, ylab, xlab, names.arg,
ylim, ...) {
addbars <- as.integer(addbars)
if (addbars > 0) {
hmat <- matrix(0, nrow = K, ncol = addbars)
xvalue <- cbind(t(x), hmat)
barplot(xvalue, main = main, col = col, ylab = ylab,
xlab = xlab, names.arg = names.arg, ylim = ylim,
legend.text = legend, ...)
abline(h = 0)
}
else {
xvalue <- t(x)
barplot(xvalue, main = main, col = col, ylab = ylab,
xlab = xlab, names.arg = names.arg, ylim = ylim,
...)
abline(h = 0)
}
}
if (plot.type == "single") {
# par(mar = mar, oma = oma)
# if (nv > 1)
# par(ask = TRUE)
for (i in 1:nv) {
plotfevd(x = x[[names[i]]], main = main[i], col = col,
ylab = ylab[i], xlab = xlab[i], names.arg = names.arg,
ylim = ylim, ...)
}
}
else if (plot.type == "multiple") {
if (missing(nc)) {
nc <- ifelse(nv > 4, 2, 1)
}
nr <- ceiling(nv/nc)
par(mfcol = c(nr, nc), mar = mar, oma = oma)
for (i in 1:nv) {
plotfevd(x = x[[names[i]]], main = main[i], col = col,
ylab = ylab[i], xlab = xlab[i], names.arg = names.arg,
ylim = ylim, ...)
}
}
# on.exit(par(op))
}
Then, you will need short variable names. Pick acronyms if needed.
library(vars)
data(Canada)
colnames(Canada) <-c("name1","name2","name3","name4")
var <- VAR(Canada , p=4 , type = "both")
Using a wide plot window (using win.graph) and using layout (to get the placement of your eight plots), you can get all charts displayed properly. I also changed the colors of the plots as requested. Finally, we are now using single plots as there are no calls to par() which do not sit well with layout().
win.graph(width=15,height=8)
layout(matrix(1:8,ncol=2))
plot.varfevd(fevd(var, n.ahead = 10 ),plot.type = "single", col=1:4)
plot.varfevd(fevd(var, n.ahead = 10 ),plot.type = "single", col=1:4)

Facet_wrap like plot using R base graphics

I want to compare two datasets with same x and y variables. However, not all X variable points are present on both. As a toy example say this is what I have:
position.x <- c(1,2,3)
score.x <- c(450,220,330)
x <- data.frame(position,score.x)
position.y <- c(2,3,5)
score.y <- c(333,423,988)
y<- data.frame(position.y,score.y)
par(mfrow = c(2,1))
plot(x, pch = 19)
plot(y, pch = 19)
X axes are not comparable. I found some post explaining how to do it on ggplot using facet_wrap but I would like to do it using base graph.
Thank you in advance.
you could specify the range of the x and y axises by xlim and slim
position.x <- c(1,2,3)
score.x <- c(450,220,330)
x <- data.frame(position,score.x)
position.y <- c(2,3,5)
score.y <- c(333,423,988)
y<- data.frame(position.y,score.y)
par(mfrow = c(2,1))
plot(x, pch = 19, xlim=c(1,5))
plot(y, pch = 19, xlim=c(1,5))
if you are going to repeat this, you might as well write some kind of function (which is one of the benefits of ggplot--it takes care of all the set-up for you):
## data needs to be in a long format
dat <- data.frame(position = c(1,2,3,2,3,5),
score = c(450,220,330,333,423,988),
z = c('x','x','x','y','y','y'))
facet_wrap <- function(data, x, y, z, horiz = TRUE, ...) {
## save current par settings and return after finished
op <- par(no.readonly = TRUE)
on.exit(par(op))
zz <- unique(data[, z])
## sets up the layout to cascade horizontally or vertically
## and sets xlim and ylim appropriately
if (horiz) {
par(mfrow = c(1, length(zz)), ...)
ylim <- range(data[, y])
xlim <- NULL
} else {
par(mfrow = c(length(zz), 1), ...)
xlim <- range(data[, x])
ylim <- NULL
}
## make a subset of data for each unique by variable
## and draw a basic plot for each one
for (ii in zz) {
tmp <- data[data[, z] %in% ii, ]
plot(tmp[, x], tmp[, y], xlim = xlim, ylim = ylim)
}
}
facet_wrap(dat, 'position', 'score', 'z', mar = c(5,4,2,2))
facet_wrap(dat, 'position', 'score', 'z', mar = c(5,4,1,2), horiz = FALSE)

Creating a legend, key or color ramp for the color gradient in R's image()

In an R session, given:
foo <- matrix(1:25, 5, 5)
image(foo)
What is the best way for me to add a legend or key for the color gradient used on this image() plot of foo?
This is for a large dataset of global precipitation values so hacking something together with legend() doesn't seem to be a viable option. filled.contour() has a number of side effects that I'm not happy with. I'm using image() because it is the simplest plotting method to layer or add onto.
For the moment, my biggest issue with filled.contour() is that I'm trying to add contours from a different dataset via contour() to the plot. With filled.contour(), the contours would need to be adjusted to account for the default gradient key on the side of the plot, though I suppose this would also be the case if I added a key to the image() plot.
Thank you kindly for your time.
For future reference:
When using filled.contour(), you can call contour() and/or map() in addition to any other function you like by assigning it to filled.contour()'s plot.axes argument. It might be helpful to remember that you can stack multiple lines of code with braces.
Here's some code adapted from the zernike package. You can use it all, or just pull out the piece which creates a gradient key.
# written 13 April 2011 by CGWitthoft. Watch for updates from the
# owner of the zernike package.
pupilplot <- function (wf, cp = NULL, col = topo.colors(256), addContours = FALSE,
cscale = TRUE, ...)
{
if (cscale) {
mar.orig <- (par.orig <- par(c("mar", "las", "mfrow")))$mar
on.exit(par(par.orig))
w <- (3 + mar.orig[2]) * par("csi") * 2.54
layout(matrix(c(2, 1), ncol = 2), widths = c(1, lcm(w)))
par(las = 1)
mar <- mar.orig
mar[4] <- mar[2]
mar[2] <- 1
par(mar = mar)
thelist <- list(...)
findz <- which(names(thelist) == 'zlim')
if (length(findz) > 0 ) {
zlim <- thelist$zlim
}else{
zlim <- range(wf, finite = TRUE) #the original line
}
# end of my hack
levels <- seq(zlim[1], zlim[2], length = length(col))
plot.new()
plot.window(xlim = c(0, 1), ylim = range(levels), xaxs = "i", yaxs = "i")
rect(0, levels[-length(levels)], 1, levels[-1], col = col, density = NA)
axis(4)
box()
mar <- mar.orig
mar[4] <- 0
par(mar = mar)
}
if (is.null(cp)) {
axis1 <- 1:nrow(wf)
axis2 <- 1:ncol(wf)
}
else {
axis1 <- ((1:nrow(wf)) - cp$xc)/cp$rx
axis2 <- ((1:ncol(wf)) - cp$yc)/cp$ry
}
image(axis1, axis2, wf, col = col, asp = 1, xlab = "X", ylab = "Y", ...)
if (addContours)
contour(axis1, axis2, wf, add = TRUE)
}

Resources