Modifying aspect ratio of a plot in R - r

Here is my first plot
par(bg="white")
image(m, main = paste("generation: ",generation), ylab="", col=heat.colors(100), xaxt="n", yaxt="n", xlab="")
It is a square, and I'd like it to be a rectangle. So I did
par(bg="white", mar=c(16,1,16,1))
image(m, main = paste("generation: ",generation), ylab="", col=heat.colors(100), xaxt="n", yaxt="n", xlab="")
But then the title (main) is really far away from the plot. So I did
par(bg="white", mar=c(16,1,16,1))
image(m, ylab="", col=heat.colors(100), xaxt="n", yaxt="n", xlab="")
legend(0.32, 3.5, paste("Generation: ", IntFormat(generation, 4)), border="white", xpd=TRUE, box.col="white", cex=1.5)
Well.. I wouldn't say it is splendid, but I was satisfied. So let's put that into a .png
png(paste0(folder.images, "pg_",IntFormat(generation,4),".png"))
par(bg="white", mar=c(16,1,16,1))
image(m, ylab="", col=heat.colors(100), xaxt="n", yaxt="n", xlab="")
legend(0.32, 3.5, paste("Generation: ", IntFormat(generation, 4)), border="white", xpd=TRUE, box.col="white", cex=1.5)
dev.off()
and here is what the .png looks like!
Is there a better solution to make a rectangle out of my square than using the parameter mar that force me to add a title with legend and to search by trial and error where the center to the picture is for the title.
Why is the .png different from what is shown in the R window?

I could achieve this:
png("test.png", res = 150, width = 8, height = 1, units = "in")
par(mar = c(1,1,1,1))
image(matrix(1:10, ncol=1), ylab="", col=heat.colors(100), xaxt="n",
yaxt="n", xlab="", main="Generation: 0001")
dev.off()

Related

Saving plots from R to use with grid.exchange

I would like to make a composite graphic which is 3 panels wide by 8 panels tall. Inside each of the panel would be a graphic that takes multiple R commands to create, like the 1st 3 graphics might be
#1
plot(c(2:20), c(2:20), ylab="", xlab="", axes=FALSE)
par(new=TRUE)
plot(c(1:18, c(1:18), xact="n"), type="l", xlab="", ylab="")
#2
plot(c(2:20), c(2:20), ylab="", xlab="", axes=FALSE)
par(new=TRUE)
plot(c(1:18, c(1:18), xact="n"), type="l", xlab="", ylab="", col=2)
#3
plot(c(2:20), c(2:20), ylab="", xlab="", axes=FALSE, col=3)
par(new=TRUE)
plot(c(1:18, c(1:18), xact="n"), type="l", xlab="", ylab="")
and then I want to use a command like this to display those 3 side by side:
grid.arrange(plot1, plot2, plot3)
I don't understand how to save #1, #2, and #3 each to be plot1, plot2 and plot3. Examples of how to save plots all seem to be based on a single plot command, not multiple commands to make the plot. The help page says grob but doesn't explain what an acceptable grob might be, or how to make it. Should I screenshot each of those and save it as a jpeg file?
thanks for any help.
You can set par(mfrow = c(8, 3)). This will draw each plot in a single panel of an 8 x 3 grid. And will only move on to draw the next plot when plot.new is (implicitly) called.
par(mfrow = c(8, 3))
par(mar = c(0.5, 0.5, 0.5, 0.5)) # Otherwise margins are too large for 24 plots
for(i in 1:24) {
plot(c(2:20), c(2:20), ylab = "", xlab = "", axes = FALSE, col = 3)
par(new = TRUE)
plot(c(1:18), c(1:18), xaxt = "n", type = "l", xlab = "", ylab = "")
}
Update & solution:
Don't use grid.arrange(), don't use jpeg(), recordplot(), pdf() or ggplot...
The below code worked by adding features to the original plots. Then it helps (within RStudio) exporting results as high resolution PNG. I can label the row/columns separately well enough in MS Word.
par(mfrow=c(1,3))
#1
plot1=plot(c(2:20), c(2:20), ylab="", xlab="", axes=FALSE)
par(new=TRUE)
plot1= plot1 + plot(c(1:18, c(1:18), xact="n"), type="l", xlab="", ylab="")
#2
plot2 = plot(c(2:20), c(2:20), ylab="", xlab="", axes=FALSE)
par(new=TRUE)
plot2 = plot2 + plot(c(1:18, c(1:18), xact="n"), type="l", xlab="", ylab="", col=2)
#3
plot3 = plot(c(2:20), c(2:20), ylab="", xlab="", axes=FALSE, col=3)
par(new=TRUE)
plot3 = plot3 + plot(c(1:18, c(1:18), xact="n"), type="l", xlab="", ylab="")

"axis" won't add x-axis to boxplot

I'm trying to make a boxplot with custom axis labels, but I can't seem to add an x-axis to the plot.
For example:
test <- data.frame(year=as.integer(rep(1963:2014, each=10)),
response=rnorm(520))
boxplot(response~year, data=test, ylim=c(-3,3), xlab="", ylab="",
range=0, xaxt="n", yaxt="n")
responselabs <- as.numeric(c(-3:3, by=1))
yearlabs <- as.integer(seq(1965,2015, by=5))
axis(2, at=responselabs, tck=0.03, las=1)
axis(1, at=yearlabs, tck=0.03)
returns the boxplot, but no x-axis labels:
Trying to hack it the other way by making an empty plot first, I can get the axes, but it won't add the boxplot:
plot(NA, ylim=c(-3, 3), xlim=c(1962, 2015), xaxt="n", yaxt="n", ylab="", xlab="")
axis(2, at=responselabs, tck=0.03, las=1)
axis(1, at=yearlabs, tck=0.03)
boxplot(response~year, data=test, ylim=c(-3,3), xlab="", ylab="",
range=0, xaxt="n", yaxt="n", add=T)
What's going on here?
I think what's happening is that boxplot converts year to a factor. We can get around this by using the labels argument in axis:
boxplot(response~year, data=test, ylim=c(-3,3), xlab="", ylab="",
range=0, xaxt="n", yaxt="n")
responselabs <- as.numeric(c(-3:3, by=1))
yearlabs <- as.integer(seq(1965,2015, by=5))
axis(2, at=responselabs, tck=0.03, las=1)
axis(1, at = yearlabs - 1962, labels = yearlabs)
Why not use ggplot2?
library(ggplot2)
p<-ggplot(test,aes(x=year,y=response,group=year))+
geom_boxplot()+
scale_x_continuous(breaks=round(seq(min(test$year),max(test$year),by=5),1))
If you want to be rounded to the nearest 5 then the code is fairly easy to adjust in the scale_x_continuous() argument.
p<-ggplot(test,aes(x=year,y=response,group=year))+
geom_boxplot()+
scale_x_continuous(breaks = round(seq(round(min(test$year)/5,0)*5,round(max(test$year)/5,0)*5, by = 5),1))
Like others have stated, you may have confused the values underlying the x-axis. Playing around with abline() reveals that abline(v=2010) does not show up, but something like abline(v=50) will show up.
Here's a plot you're after, I believe (with some extra modifications):
year_vals <- as.integer(rep(1963:2014, each=10)) # pulling out of next line for easy reference
test <- data.frame(year=year_vals,response=rnorm(520))
boxplot(response~year, data=test, ylim=c(-3,3), xlab="", ylab="", range=0, xaxt="n", yaxt="n")
responselabs <- as.numeric(c(-3:3, by=1))
yearlabs <- year_vals[year_vals%%5==0] # alternate, more general definition; note that 2015 wasn't in the original 'test' data.frame #as.integer(seq(1965,2015, by=5))
axis(2, at=responselabs, tck=0.03, las=1)
# axis(1, at=1:length(yearlabs), tck=0.03)
axis(1, at=which(unique(year_vals)%in%yearlabs), labels=unique(yearlabs), tck=0.03)

How to plot horizontal y label

I tried to plot a horizontal y label using the following code:
require(grDevices)
par(mfrow=c(2,1), mar=c(0,3,0,0.5)+0.1, oma=c(3,0,0.5,0)+0.1,
mgp=c(2,1,0), cex=tcex)
par(las=1)
#pl-d001
plot(1, type="n", yaxt='n', xaxt='n', xaxs="i", yaxs="i", xlab="xlabel",
ylab=expression("axis "*italic(r[infinity])), xlim=c(0, 0.4), ylim=c(0, 1))
But, I only get a normal (vertical) Y label. What is wrong here?
As #rawr said in the comments, your best chance is with mtext:
plot(1, type="n", yaxt='n', xaxt='n', xaxs="i", yaxs="i",
xlab="xlabel", ylab="", xlim=c(0, 0.4), ylim=c(0, 1))
mtext(expression("axis "*italic(r[infinity])),side=2,las=1,line=1)
The reason why you can't do that directly from plot is that, internally, plot uses title to draw those labels and title (for some reason) doesn't take las or srt as arguments.

How to avoid wired ylab error when plotting in R

I need a two y-axes figure. hrbrmstr suggested to use simple plots. But when adapting the graph to my setting I observed I cannot add the ylab on the right hand side, getting a wired error:
Error in axis(4, ylim = c(0, 1), col = "black", col.axis = "black", las = 1, :
'labels' is supplied and not 'at'
Is this avoidable?
look at the code the bottom line fpr SOURCE OF ERROR
featPerf <- data.frame( expS=c("1", "2", "3", "4"),
exp1=c(1000, 0, 0, 0),
exp2=c(1000, 5000, 0, 0),
exp3=c(1000, 5000, 10000, 0),
exp4=c(1000, 5000, 10000,20000),
accuracy=c(0.4, 0.5, 0.65, 0.9) )
# make room for both axes ; adjust as necessary
par(mar=c(5, 5, 5, 7) + 0.2)
# plot the bars first with no annotations and specify limits for y
#barplot(as.matrix(featPerf[,2:5]), axes=FALSE, xlab="", ylab="", ylim=c(0, max(colSums(featPerf[2:5]))))
barplot(as.matrix(featPerf[,2:5]), axes=FALSE, xlab="", ylab="", beside=TRUE)
# make the bounding box (or not...it might not make sense for your plot)
#box()
# now make the left axis
axis(2, ylim=c(0, max(colSums(featPerf[2:5]))), col="black", las=1)
# start a new plot
par(new=TRUE)
# plot the line; adjust lwd as necessary
plot(x=1:4, y=featPerf[,6], xlab="Experiments", ylab="Abs. # of Features", axes=FALSE, type="l", ylim=c(0,1), lwd=5)
# annotate the second axis -- SOURCE OF ERROR -> VVVVVVVVVVVVVVVVVV
axis(4, ylim=c(0,1), col="black", col.axis="black", las=1, labels="Accuracy")
Like this?
par(mar=c(4,4,1,4) + 0.2)
barplot(as.matrix(featPerf[,2:5]), axes=FALSE, xlab="", ylab="", beside=TRUE)
axis(2, ylim=c(0, max(colSums(featPerf[2:5]))), col="black", las=1)
par(new=TRUE)
plot(x=1:4, y=featPerf[,6], xlab="Experiments", ylab="Abs. # of Features", axes=FALSE, type="l", ylim=c(0,1), lwd=5, col="blue")
axis(4, ylim=c(0,1), col="blue", col.axis="blue", las=1)
mtext("Accuracy",4,line=2, col="blue")
For the record, it is never a good idea to stack plots on top of each other this way (with two axes). I've made the line and the axis the same color in an attempt to draw attention to what you are doing, but this is still a very bad idea.
First of all it is not advisable to use two Y-axes in a same plot.
If you add at argument to the axis call, you get the name "Accuracy" on the right hand side of the plot.
axis(4, ylim=c(0,1), col="black", col.axis="black", las=1, labels="Accuracy",
at = .5)

Add legend at the top of a plot

My legend is cropped in my plot. To solve this, I know I have to deal with margins but I do not know how. And the use of xpd=TRUE does not seems to work.
My code has this structure:
plot(x,y1)
par(new=TRUE)
plot(x,y2)
par(new=TRUE)
plot(x,y3)
...
par(xpd=TRUE)
legend(...)
The entire code:
time<-c(18,19, 21, 25, 26)
layer_0<-c(0.73,0.78,0.95,0.83,0.77)
layer_0_sd<-c(0.04,0.04,0.03,0.13,0.19)
layer_1<-c(0.89,0.9,0.61,0.28,0.08)
layer_1_sd<-c(0.03,0.01,0.14,0.14,0.09)
layer_2<-c(0.66,0.6,0.21,0.01,0)
layer_2_sd<-c(0.06,0,0.12,0.01,0)
layer_3<-c(0.23,0.13,0.05,0,0)
layer_3_sd<-c(0.07,0,0.03,0,0)
layer_4<-c(0.03,0.01,0.01,0,0)
layer_4_sd<-c(0.01,0,0.01,0,0)
layer_5<-c(0,0,0,0,0)
layer_5_sd<-c(0,0,0,0,0)
epsilon=0.02
plot(time, layer_0, ylim=c(0,1), type="o", lty=1, lwd=2,ylab="Longitudinal fCOVER", xlab="Days after seeding", cex.lab=1.5, cex.axis=1.5, cex.main=1.5, cex.sub=1.5)
segments(time, layer_0-layer_0_sd,time, layer_0+layer_0_sd, lwd=2)
segments(time-epsilon,layer_0-layer_0_sd,time+epsilon,layer_0-layer_0_sd, lwd=2)
segments(time-epsilon,layer_0+layer_0_sd,time+epsilon,layer_0+layer_0_sd, lwd=2)
for (i in c(1:5)){
par(new=TRUE)
eval(parse(text=paste("plot(time, layer_",i,", ylim=c(0,1), type='o', xlab='', ylab='', xaxt='n', yaxt='n',lwd=2, lty=",i+1,", col=",i+1,")+
segments(time, layer_",i,"-layer_",i,"_sd,time, layer_",i,"+layer_",i,"_sd, col=",i+1,",lwd=2)+
segments(time-epsilon,layer_",i,"-layer_",i,"_sd,time+epsilon,layer_",i,"-layer_",i,"_sd, col=",i+1,",lwd=2)+
segments(time-epsilon,layer_",i,"+layer_",i,"_sd,time+epsilon,layer_",i,"+layer_",i,"_sd, col=",i+1,",lwd=2)", sep="")))
}
par(xpd=TRUE)
legend(x=17.65, y=1.3, c("0-5 cm","5-10 cm", "10-15 cm", "15-20 cm", "20-25 cm","25-30 cm"), lty=c(1,2,3,4,5,6), col=c(1,2,3,4,5,6),ncol=3, lwd=2, cex=1.5)
Just as an alternative, the use of the parameter inset makes this very easy. A similar duscussion can be found here.
for your example:
par(mar=c(5, 5, 6, 3), xpd=TRUE)
plot(time, layer_0, ylim=c(0,1), type="o", lty=1, lwd=2,ylab="Longitudinal fCOVER", xlab="Days after seeding", cex.lab=1.5, cex.axis=1.5, cex.main=1.5, cex.sub=1.5)
segments(time, layer_0-layer_0_sd,time, layer_0+layer_0_sd, lwd=2)
segments(time-epsilon,layer_0-layer_0_sd,time+epsilon,layer_0-layer_0_sd, lwd=2)
segments(time-epsilon,layer_0+layer_0_sd,time+epsilon,layer_0+layer_0_sd, lwd=2)
for (i in c(1:5)){
par(new=TRUE)
eval(parse(text=paste("plot(time, layer_",i,", ylim=c(0,1), type='o', xlab='', ylab='', xaxt='n', yaxt='n',lwd=2, lty=",i+1,", col=",i+1,")+
segments(time, layer_",i,"-layer_",i,"_sd,time, layer_",i,"+layer_",i,"_sd, col=",i+1,",lwd=2)+
segments(time-epsilon,layer_",i,"-layer_",i,"_sd,time+epsilon,layer_",i,"-layer_",i,"_sd, col=",i+1,",lwd=2)+
segments(time-epsilon,layer_",i,"+layer_",i,"_sd,time+epsilon,layer_",i,"+layer_",i,"_sd, col=",i+1,",lwd=2)", sep="")))
}
legend("top", inset = c(0, -0.25), legend=c("0-5 cm","5-10 cm", "10-15 cm", "15-20 cm", "20-25 cm","25-30 cm"), lty=c(1,2,3,4,5,6), col=c(1,2,3,4,5,6),ncol=3, lwd=2, cex=1.5)
If I have to place a legend outside a plot I generally do it with a separate panel, as I find it much easier to control positions and sizes then.
layout(1:2, heights=c(1, 5))
# Legend panel
par(mar=rep(0,4))
plot(0, 0, type="n", ann=FALSE, axes=FALSE)
legend("center", c("5-10 cm", "15-20 cm", "25-30 cm"), horiz=TRUE,
lty=2:4, col=1:3)
# Plot panel
par(mar=c(5,4,0,2))
plot(1:20, cumsum(rnorm(20)))
Or if you want to follow the theme you started using par(new=TRUE) you could do this
par(mar=c(5,4,5,2))
plot(1:20, cumsum(rnorm(20)))
par(new=TRUE, mar=c(0,0,1,0))
plot(0, 0, type="n", ann=FALSE, axes=FALSE)
legend("top", c("5-10 cm", "15-20 cm", "25-30 cm"), horiz=TRUE,
lty=2:4, col=1:3)
Both give the result below.

Resources