# How do I vertically stack two graphs with the same x scale, but a different y scale in R?

Greetings,

Currently I'm doing the following in R:

require(zoo)
cum  = zoo(data$dcomp, as.Date(data$date))
data = zoo(data$compressed, as.Date(data$date))
data <- aggregate(data, identity, tail, 1)
cum  <- aggregate(cum, identity, sum, 1)
days = seq(start(data), end(data), "day")
data2 = na.locf(merge(data, zoo(,days)))

plot(data2,xlab='',ylab='compressed bytes',col=rgb(0.18,0.34,0.55))
lines(cum,type="h",col=rgb(0,0.5,0))


Snip of summary.csv:

date,revision,file,lines,nclass,nattr,nrel,bytes,compressed,diff,dcomp
2007-07-25,16,model.xml,96,11,22,5,4035,991,0,0
2007-07-27,17,model.xml,115,16,26,6,4740,1056,53,777
2007-08-09,18,model.xml,106,16,26,7,4966,1136,47,761
2007-08-10,19,model.xml,106,16,26,7,4968,1150,4,202
2007-09-06,81,model.xml,111,16,26,7,5110,1167,13,258
...


The last two lines plot the information I need, and the result resembles the following: Blue line is the entropy in bytes of the artifact I'm interested. Green lines represent the entropy of the changes.

Now, in this graph, it works well because there isn't a huge difference in scales. But I have other graphs where the green lines become so small one cannot see.

The solution I was looking for, involved two things:

1. To move the green vertical lines to a second graph, just below the first one, with its own y axis, but shared x axis.
2. To provide it a logarithmic scale, since I'm more interested in the "magnitude", than in the specific values.

P.S. If someone can also tell me how could I put "minor ticks" in the x scale referring to the months, I appreciate :-) If these are too much questions for a single post, I can divide them further.

You can use par(new=TRUE) to plot into the same graph using two different y-axes! This should also solve your problem.

Next you will find a simple example that plots two random normal variables, one on mean 0 the other one on mean 100 (both sd s = 1) in the same plot. The first one in red on the left y-axis, the second one in blue on the right y-axis. Then, axis labels are added.

Here you go:

x <- 1:10
y1 <- rnorm(10)
y2 <- rnorm(10)+100

plot(x,y1,pch=0,type="b",col="red",yaxt="n",ylim=c(-8,2))
par(new=TRUE)
plot(x,y2,pch=1,type="b",col="blue",yaxt="n",ylim=c(98,105))

axis(side=2)
axis(side=4)


looks like this then (remember red on left axis, blue on right axis):

UPDATE:
Based on comments I produced an updated version of my graph. Now I dig a little deeper into base graph functionality using par(mar=c(a,b,c,d)) to create a bigger margin around the graph (needed for right axis label), mtext to show the axis labels and and advanced use of the axis function:

x <- 1:100
y1 <- rnorm(100)
y2 <- rnorm(100)+100

par(mar=c(5,5,5,5))

plot(x,y1,pch=0,type="b",col="red",yaxt="n",ylim=c(-8,2),ylab="")
axis(side=2, at=c(-2,0,2))
mtext("red line", side = 2, line=2.5, at=0)

par(new=TRUE)
plot(x,y2,pch=1,type="b",col="blue",yaxt="n",ylim=c(98,108), ylab="")
axis(side=4, at=c(98,100,102), labels=c("98%","100%","102%"))
mtext("blue line", side=4, line=2.5, at=100)


As you see it is pretty straight forward. You can define the position of your data with ylim in the plot function, then use at in the axis function to select which axis ticks you wanna see. Furthermore, you can even provide the labels for the axis ticks (pretty useful for nominal x-axis) via labels in the axis function (done here on the right axis). To add axis labels, use mtext with at for vertical positioning (line for horizontal positioning).

Make sure to check ?plot, ?par, ?axis, and ?mtext for further info.
Great web resources are: Quick-R for Graphs: 1, 2, and 3.

• That's interesting, but how do we tell the reader which scale corresponds to which line? – Hugo Sereno Ferreira Aug 18 '10 at 15:52
• Have a look at this graph: imgur.com/K8BCr.png There, we present y-axis labels and ticks only where they apply to the data (i.e., for the left axis on the top of the graph, as the corresponding data, and for the right axis on the bottom of the graph, as the correspoding data). Additionally we used different colors (as in the example above) and line types and explained it in the caption. You could also use a line chart on the left and a bar chart on the right axis to make the distinction clearer. – Henrik Aug 18 '10 at 16:11
• The example you've given is very good... How did you managed to vertical offset each axis? – Hugo Sereno Ferreira Aug 18 '10 at 20:55
• Really good example. The only issue with your graph, is that both Y variable names are overlapping. In this case you would want one on the left and the other on the right (possibly even in a vertical position). To upgrade your example from "really good" to "perfect", you might wanna use the mtext function from R to do the variable names – Dave Kellen Aug 19 '10 at 9:18
• @Hugo @Dave: See my update for an incorporation of both comments. – Henrik Aug 19 '10 at 10:00

I think you can get what you want using ggplot2. Using the code below, I can produce:

Obviously things like line colours can be changed to what ever you want. On the x-axis I specified major lines on years and minor lines on months.

require(ggplot2)
t = as.Date(0:1000, origin="2008-01-01")
y1 = rexp(1001)
y2 = cumsum(y1)
df = data.frame(t=t, values=c(y2,y1), type=rep(c("Bytes", "Changes"), each=1001))

g = ggplot(data=df, aes(x=t, y=values)) +
geom_line() +
facet_grid(type ~ ., scales="free") +
scale_y_continuous(trans="log10") +
scale_x_date(major="years", minor="months") +
ylab("Log values")
g

• Uh, I tried setting up df = data.frame(t=days, values=c(data2,cum), type=rep(c("Bytes", "Changes"), each=1001)), but it gives an Error in rbind.zoo(...) : indexes overlap – Hugo Sereno Ferreira Aug 18 '10 at 15:36
• That's because data2 and cum are zoo objects. Use as.vector(data2) to get the raw values. Also, I used 1001 because I had 1001 observations. You will need something different. – csgillespie Aug 18 '10 at 15:42
• Noob R user here: Error in data.frame(t = days, values = c(as.vector(data2), as.vector(cum)), : arguments imply differing number of rows: 1063, 1300, 2 – Hugo Sereno Ferreira Aug 18 '10 at 16:01
• Type "days", "data2" and "cum" to look at your data. Then look at "length(days)", etc. You need to match up the time points with the values. – csgillespie Aug 18 '10 at 16:05