Set axis tick and label to include maximum value on bar plot

Viewed 256

I am using base R to create multiple side-by-side bar plots (I prefer to avoid using ggplot because I am often limited in functionality). I find the result not visually pleasing because the axis ticks don't line up at all : sometimes the top of the axis is well below the maximum value and sometimes it is above. Is there a way to set it so that the final axis tick value includes the maximum value? It can't align perfectly because each graph has its own range and thus its own set of ticks values but I would like to at least have it so that the style is consistent across 12 graphs.

I am creating the series using a function (and a loop) so I would prefer an automated solution (rather than adjusting each graph with setting the maximum limit axis() separately)

Here is a simplified sample with the iris dataset. The issue that comes up is that the axis ends at 6 in the first panel, which is below the maximum value including error bars (7.2238), whereas in the second panel the axis ends above the maximum value.

library(vegan)
data(iris)

x1_mean<-tapply(iris$Sepal.Length, iris$Species, FUN=mean)
x1_sd<-tapply(iris$Sepal.Length, iris$Species, FUN=sd)


x2_mean<-tapply(iris$Petal.Width, iris$Species, FUN=mean)
x2_sd<-tapply(iris$Petal.Width, iris$Species, FUN=sd)

par(mfrow=c(1,2))
br1=barplot(x1_mean, ylim=c(0, (max(x1_mean)+max(x1_sd))*1.1))
errbar(x = br1, y = x1_mean, 
                     yplus = x1_mean+x1_sd, 
                     yminus = x1_mean-x1_sd, add = T, cex = 0)


br2=barplot(x2_mean, ylim=c(0, (max(x2_mean)+max(x2_sd))*1.1))
errbar(x = br2, y = x2_mean, 
       yplus = x2_mean+x2_sd, 
       yminus = x2_mean-x2_sd, add = T, cex = 0)

enter image description here

EDIT/PROGRESS:

I have managed to extract the maximum value of the axis using par("yaxp") and used that to add an extra tick so that the last tick value is greater than the maximum value on the graph. But, it forces me to actually plot the default graph, which creates two graphs. I've also included a simplified version of the function (using iris as a sample dataset) that I am trying to build, which will be perhaps clearer about my aims.

library(vegan)
data(iris)

barplot_adjust<- function(data, metric,...) {
  x_means<-tapply(data[,metric], list(data$Species), FUN=mean)
  x_sd<-tapply(data[,metric], list(data$Species), FUN=sd)
  br1 <- barplot(height = x_means, names.arg = names(x_sd), ylim = c(0, (max(x_means+x_sd))*1.1), 
                 main=metric, las=0,plot=TRUE,
                 ...)
   if( par("yaxp")[2]<(max(x_means+x_sd))*1.1 )
  {ymx=par("yaxp")[2]+par("yaxp")[2]/par("yaxp")[3]}else{ymx=par("yaxp")[2]}
  br1 <- barplot(height = x_means, names.arg = names(x_sd), ylim = c(0, ymx), 
                 main=metric, las=0,plot=TRUE,
                 ...)
  print(ymx)
  errbar(x = br1, y = x_means, yplus = x_means+x_sd, yminus = x_means-x_sd, add = T, cex = 0)
}

par(mfrow=c(2,2))
barplot_adjust(data=iris, metric="Sepal.Length")
barplot_adjust(data=iris, metric="Petal.Width")
2 Answers

What about something like this:

library(vegan)
data(iris)

x1_mean<-tapply(iris$Sepal.Length, iris$Species, FUN=mean)
x1_sd<-tapply(iris$Sepal.Length, iris$Species, FUN=sd)
yl1 <- max((x1_mean + x1_sd))

x2_mean<-tapply(iris$Petal.Width, iris$Species, FUN=mean)
x2_sd<-tapply(iris$Petal.Width, iris$Species, FUN=sd)
yl2 <- max((x2_mean + x2_sd))


library(Hmisc)
par(mfrow=c(1,2))
br1=barplot(x1_mean, ylim=c(0, (max(x1_mean)+max(x1_sd))), axes=FALSE)
axis(2, at=round(seq(0,yl1, length=5), 1))
errbar(x = br1, y = x1_mean, 
       yplus = x1_mean+x1_sd, 
       yminus = x1_mean-x1_sd, add = T, cex = 0)


br2=barplot(x2_mean, ylim=c(0, (max(x2_mean)+max(x2_sd))), axes=FALSE)
axis(2, at=round(seq(0,yl2, length=5), 1))
errbar(x = br2, y = x2_mean, 
       yplus = x2_mean+x2_sd, 
       yminus = x2_mean-x2_sd, add = T, cex = 0)

enter image description here

Axes end at the maximum, the rest is actually a box. BTW, regarding the four tapplys, you may do them in one aggregate. (I use confidence intervals here that I think are more appropriate, but you can also use sd())

FUN <- function(x) c(mean=mean(x), q=quantile(x, c(.025, .975)))
r <- do.call(data.frame, aggregate(cbind(Sepal.Length, Petal.Width) ~ Species, iris, FUN))
r <- t(`rownames<-`(r[-1], r[,1]))

If you like base R, you may want to use arrows instead of Hmisc::errbar.

op <- par(mfrow=c(1, 2))  ## set par and store defaults
br1 <- barplot(r[1,], ylim=c(0, max(r[1:3,]))*1.1)
arrows(br1, y0=r[2,], y1=r[3,], code=3, angle=90, length=.05)
box()
br2 <- barplot(r[4,], ylim=c(0, max(r[4:6,]))*1.1)
arrows(br2, y0=r[5,], y1=r[6,], code=3, angle=90, length=.05)
box()
par(op)  ## restore par defaults

enter image description here

I'm not very sure what you mean by "axis ticks don't line up", probably you want the same ylim for both graphs?

op <- par(mfrow=c(1, 2))
br1 <- barplot(r[1,], ylim=c(0, max(r))*1.1)
arrows(br1, y0=r[2,], y1=r[3,], code=3, angle=90, length=.05)
box()
br2 <- barplot(r[4,], ylim=c(0, max(r))*1.1)
arrows(br2, y0=r[5,], y1=r[6,], code=3, angle=90, length=.05)
box()
par(op)

enter image description here

Related