4
votes

I'm writing a function to plot (what is termed in Excel, for example) "combo-charts" of a specific variety: namely bar + line graphs. Here is my code:

plot_combo_chart <- function(df,bar_colour = "green",line_colour = 
"red",y_ticks = 5){

  x_name <- colnames(df)[1]
  bar_name <- colnames(df)[2]
  line_name <- colnames(df)[3]
  plot_colours <- c(bar_colour,line_colour)

  x_min <- min(df[,1])
  x_max <- max(df[,1])
  y_min <- min(df[,2],df[,3])
  y_max <- max(df[,2],df[,3])

  yaxis <- pretty(c(df[,2],df[,3]),n=y_ticks)

  par(xpd=FALSE)
  bp <- barplot(df[,2],col=bar_colour,axes = FALSE,ann=FALSE)
  xaxis <- pretty(df[,1])
  xlabs <- df[,1]

  lines(x=bp,df[,3], col=line_colour,lwd=2)
  axis(side=1,at=xaxis,labels = xaxis,xlim=c(x_min,x_max))
  axis(side=2,at=yaxis,labels = yaxis, ylim = c(y_min,y_max))
  legend(x=max(bp),y=y_max,legend = 
      c(bar_name,line_name),col=plot_colours,pch = c(NA,NA),bty='n', lwd=2,
      lty=c(0,1),xjust=1,yjust=0,fill=c(bar_colour,NA), 
      border=c(bar_colour,NA),xpd=TRUE)
      title(xlab = x_name)
}

I have generated some test data for the function:

test <- data.frame(sample = 1:100, value1 = rnorm(100),value2 = runif(100))

The issue I'm having when I run the script on the test data with plot_combo_chart(test) is that the x-axis is too short and stops when about 20% of the bars are still left to the right:

enter image description here

Things I have tried:

  1. replacing xaxis with bp and setting labels = xlabs in the first call to axis() - this does make the axis span the whole chart, but it adds far too many tick marks;

  2. replacing xaxiswith pretty(bp) - this gives me the right size for my axis and properly spaced tick marks, but the tick labels do not correspond to my dataframe and the rightmost value exceeds the max of the dataframe;

  3. replacing xaxis with pretty(bp) and setting labels = pretty(df[,1],n=length(xaxis)-1,min.n=length(xaxis)-1) (the min.n argument must be used otherwise an error is thrown) - the issue with this approach is that the leftmost x-axis value is -20, which is far outside the actual range;

I'm running out of ideas, but I feel like this is a simple thing and so I must be missing a simple graphical parameter setting that will give me what I want. What is the best way to proceed?

Thanks!

2
Sorry, read too fast...I do think the solution will involve using bp to set the axis, it will just be getting the right combo... - joran

2 Answers

1
votes

Change to

bp <- barplot(df[,2], col=bar_colour, space=0, axes = FALSE, ann=FALSE)

Note space = 0 argument

A few other modifications are required to achieve the proper spacing of tick marks relative to the bars - (as pointed out by user2554330)

xaxis <- pretty(df[,1]) - 0.5     # offset values to midpoint of bar
xlabs <- xaxis + 0.5
axis(side=1,at=xaxis,labels = xlabs,xlim=c(x_min,x_max))  # you originally had labels = xaxis

The modified function is below

    plot_combo_chart <- function(df,bar_colour = "green",line_colour = 
"red",y_ticks = 5){

  x_name <- colnames(df)[1]
  bar_name <- colnames(df)[2]
  line_name <- colnames(df)[3]
  plot_colours <- c(bar_colour,line_colour)

  x_min <- min(df[,1])
  x_max <- max(df[,1])
  y_min <- min(df[,2],df[,3])
  y_max <- max(df[,2],df[,3])

  yaxis <- pretty(c(df[,2],df[,3]),n=y_ticks)

  par(xpd=FALSE)
  bp <- barplot(df[,2],col=bar_colour,space=0,axes = FALSE,ann=FALSE)
  xaxis <- pretty(df[,1])-0.5
  xlabs <- xaxis+0.5

  lines(x=bp,df[,3], col=line_colour,lwd=2)
  axis(side=1,at=xaxis,labels = xlabs,xlim=c(x_min,x_max))
  axis(side=2,at=yaxis,labels = yaxis, ylim = c(y_min,y_max))
  legend(x=max(bp),y=y_max,legend = 
      c(bar_name,line_name),col=plot_colours,pch = c(NA,NA),bty='n', lwd=2,
      lty=c(0,1),xjust=1,yjust=0,fill=c(bar_colour,NA), 
      border=c(bar_colour,NA),xpd=TRUE)
      title(xlab = x_name)
}
1
votes

@joran is right, you need to use bp to set the axis, but it's a little complicated because you want to rescale things properly. Instead of this block:

  bp <- barplot(df[,2],col=bar_colour,axes = FALSE,ann=FALSE)
  xaxis <- pretty(df[,1])
  xlabs <- df[,1]

  lines(x=bp,df[,3], col=line_colour,lwd=2)
  axis(side=1,at=xaxis,labels = xaxis,xlim=c(x_min,x_max))

you need to work out the relation between the bp values and the df[,1] values. I'm assuming that there's a linear relation between them (because both are equally spaced from smallest to largest). We use the original calculation for the labels, but place them at offset + scale*xaxis instead of at xaxis: this code does the conversion:

  bp <- barplot(df[,2],col=bar_colour,axes = FALSE,ann=FALSE)
  b_min <- min(bp) # new
  b_max <- max(bp) # new
  xaxis <- pretty(df[,1])
  scale <- (b_max - b_min)/(x_max - x_min) # new
  offset <- b_min - x_min*scale            # new
  xlabs <- df[,1]

  lines(x=bp,df[,3], col=line_colour,lwd=2)
  axis(side=1,at=offset + scale*xaxis,labels = xaxis,xlim=c(b_min,b_max)) # changed

Here's the full function with the change in it:

plot_combo_chart <- function(df, bar_colour = "green", line_colour = "red", 
                             y_ticks = 5){

  x_name <- colnames(df)[1]
  bar_name <- colnames(df)[2]
  line_name <- colnames(df)[3]
  plot_colours <- c(bar_colour,line_colour)

  x_min <- min(df[,1])
  x_max <- max(df[,1])
  y_min <- min(df[,2],df[,3])
  y_max <- max(df[,2],df[,3])

  yaxis <- pretty(c(df[,2],df[,3]),n=y_ticks)

  par(xpd=FALSE)
  bp <- barplot(df[,2],col=bar_colour,axes = FALSE,ann=FALSE)
  b_min <- min(bp)
  b_max <- max(bp)
  xaxis <- pretty(df[,1])
  scale <- (b_max - b_min)/(x_max - x_min)
  offset <- b_min - x_min*scale
  xlabs <- df[,1]

  lines(x=bp,df[,3], col=line_colour,lwd=2)
  axis(side=1,at=offset + scale*xaxis,labels = xaxis,xlim=c(b_min,b_max))
  axis(side=2,at=yaxis,labels = yaxis, ylim = c(y_min,y_max))
  legend(x=max(bp),y=y_max,legend = 
      c(bar_name,line_name),col=plot_colours,pch = c(NA,NA),bty='n', lwd=2,
      lty=c(0,1),xjust=1,yjust=0,fill=c(bar_colour,NA), 
      border=c(bar_colour,NA),xpd=TRUE)
      title(xlab = x_name)
}

Then plot_combo_chart(test) produces this:

enter image description here