arranging plots, lining up axes

1,902 views
Skip to first unread message

Ben Bolker

unread,
May 10, 2011, 1:14:10 AM5/10/11
to ggplot2

I'm wondering if there is a way, with some combination of ggplot2 and
gridExtra and possibly other tools, to make sets of graphs whose
horizontal axes are aligned although their labels and other decorations
may be different sizes.

The idea here is that I have two different kinds of variables that
want to share the same x axes, but possibly have quite different
formatting otherwise. There are basically two routes to take (I think):
one is to abuse ggplot2's model a bit and try to do this with vertical
faceting, with _post hoc_ adjustment of decorations. The second is to
make two separate plots and arrange them on the page accordingly.

The problem with the first approach, besides the fact that it abuses
ggplot2's underlying model, is that it's hard to make different vertical
facets very different. In the example below it's not too bad, but it's
not hard to come up with examples where it gets much harder to hack the
formatting of the two sets.

The problem with the second approach is that it's a great big nuisance
to tweak the plot margins to make the x axes align properly. Presumably
to make this work properly and automatically one would need to get into
the guts of the grid object and sort out the dimensions of the actual
plot region (as opposed to the entire figure region) and make these
match ...

Any ideas welcome.

thanks
Ben Bolker

=============

## make up some data with very different ranges
set.seed(1001)
z <- data.frame(x=runif(300),y=c(runif(150)*1e5,runif(150)),
f=rep(rep(LETTERS[1:3],50),2),
var=rep(letters[1:2],each=150))

library(ggplot2)

## choice 1: use faceting; use grid.text() to add separate axis labels

g1 <- ggplot(z,aes(x=x,y=y))+geom_point()+facet_grid(var~f,scale="free_y")+
opts(panel.margin=unit(0,"inches"))+ ## no gap
labs(y="")

## would like to remove vertical strips: can eliminate text with
## +opts(strip.text.y=theme_blank())
## but don't know how to get rid of them entirely (without eliminating
## background for horizontal strips?

g1
grid.text(c("first axis","second axis"),
x=unit(rep(0.02,2), "npc"),
y=unit(c(0.3,0.7),"npc"),
rot = 90,
gp=gpar(cex=1.2))

## choice 2: create two plots, then arrange using grid.arrange(),
## with painstakingly tweaked plot.margin() to get axes in synch

zz <- split(z,z$var)
g2 <- ggplot(zz[[1]],aes(x=x,y=y))+geom_point()+facet_grid(.~f)+
opts(panel.margin=unit(0,"inches"))+
labs(y="second axis")+
opts(axis.text.x=theme_blank(),axis.title.x=theme_blank())

g3 <- ggplot(zz[[2]],aes(x=x,y=y))+geom_point()+facet_grid(.~f)+
opts(panel.margin=unit(0,"inches"))+
labs(y="first axis")+
opts(strip.text.x=theme_blank(),strip.background=theme_blank())

library(gridExtra)
## By trial and error: it seems that the margins are specified in order
## Top, Right, Bottom, Left -- different from base R standard
## which is Bottom, Left, Top, Right??
grid.arrange(g2,g3+opts(plot.margin=unit(c(0,1,0,1.25),"lines")),nrow=2)

baptiste auguie

unread,
May 10, 2011, 10:00:23 PM5/10/11
to Ben Bolker, ggplot2
Hi,

The ggExtra package has align.plots() implementing your second idea.
Quite an ugly hack, though, I'm afraid.

baptiste

library(ggExtra)
align.plots(g2, g3)

> --
> You received this message because you are subscribed to the ggplot2 mailing list.
> Please provide a reproducible example: http://gist.github.com/270442
>
> To post: email ggp...@googlegroups.com
> To unsubscribe: email ggplot2+u...@googlegroups.com
> More options: http://groups.google.com/group/ggplot2
>

Ben Bolker

unread,
May 10, 2011, 1:58:30 AM5/10/11
to baptiste auguie, ggplot2
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

This is a great start, but unfortunately grid.align() doesn't quite
align my plots. I started to poke into the guts, but couldn't see what
was wrong. Since my plotting objects are fairly complicated, I've saved
them to a file for replicability:

load(url("http://www.math.mcmaster.ca/bolker/R/misc/align-ex.RData"))
library(ggplot2)
library(ggExtra)

theme_set(theme_bw())
align.plots(g2,g1C)

## try removing legend, just in case that's the problem
align.plots(g2,g1C+opts(legend.position="none"))

There's also a fair bit of ugliness in the way the state of the device
gets left after align.plots() -- if you run exactly this code you get
left with some leftover x-axis labels from the previous plot -- but I'm
happy enough to open a new device every time if I can get this to work ...

Any further thoughts ... ? I can't see what differences there would
be between the two objects

cheers
Ben

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.10 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAk3I1AYACgkQc5UpGjwzenM3cwCeJ2KfS7ZSwLiLL2SSq7JivNe6
EBYAn1Q4OQsq3gBus9exMHmFp667vKOa
=wuiz
-----END PGP SIGNATURE-----

baptiste auguie

unread,
May 10, 2011, 10:53:51 PM5/10/11
to Ben Bolker, ggplot2
On Tue, May 10, 2011 at 5:58 PM, Ben Bolker <bbo...@gmail.com> wrote:
> -----BEGIN PGP SIGNED MESSAGE-----
> Hash: SHA1
>
>  This is a great start, but unfortunately grid.align() doesn't quite
> align my plots.  I started to poke into the guts, but couldn't see what
> was wrong.  Since my plotting objects are fairly complicated, I've saved
> them to a file for replicability:
>
> load(url("http://www.math.mcmaster.ca/bolker/R/misc/align-ex.RData"))
> library(ggplot2)
> library(ggExtra)
>
> theme_set(theme_bw())
> align.plots(g2,g1C)
>
> ## try removing legend, just in case that's the problem
> align.plots(g2,g1C+opts(legend.position="none"))
>
>  There's also a fair bit of ugliness in the way the state of the device
> gets left after align.plots() -- if you run exactly this code you get
> left with some leftover x-axis labels from the previous plot -- but I'm
> happy enough to open a new device every time if I can get this to work ...

No need to change the device, grid.newpage() should do the trick;
that's what ggplot2 and lattice call internally so you don't usually
see the "previous mess".

>
>  Any further thoughts ... ?  I can't see what differences there would
> be between the two objects

I haven't looked at the details but a good candidate might be in the
fact that your plot has facets -- i only considered single-panel plots
when I wrote align.plots().

HTH,

baptiste

Ben Bolker

unread,
May 10, 2011, 4:25:29 AM5/10/11
to baptiste auguie, ggplot2, Dennis Murphy

Thanks, Baptiste.

[Dennis: I could 'fix' the problem by making sure the y-axis labels
and title are the same width, but the whole point of this exercise is to
try to make the plots align *without* doing that ... thanks, though.]

I've managed to get a bit farther with this, but not to solve it. It
seems (?) that the problem occurs when one plot is constructed with a
legend and the other isn't -- even when the legend is removed with
opts(legend.position="none"), which implies that a difference in some
horizontal spacing element persists.
The code below is somewhat simplified (doesn't depend on my
particular complicated plots) and shows that faceting is not relevant,
but presence/absence of legends is.
Unfortunately, I don't know enough about digging into grid objects to
have been able to figure out (so far) where the difference lies and what
to do about it ...

On a side note, I was looking at
<http://groups.google.com/group/ggplot2/browse_thread/thread/753bd02a945a0e93>
and can offer the following little bit of code that seems to find
vertical strips (which could then be used, with a little bit work, to
adjust spacing properly in the cases shown in that e-mail) ...

strips <- lapply(dots, function(.g) {
cc <- .g$children$layout$children
vstrips <- cc[grepl("^strip_v",names(cc))]
## assume all strips the same width/height,
## so just use the first one?
if (length(vstrips)>0)
editGrob(vstrips[[1]],vp=NULL)
else ggplot2:::.zeroGrob
})

EXAMPLE:
========================


## make up some data with very different ranges
set.seed(1001)
z <- data.frame(x=runif(300),y=c(runif(150)*1e5,runif(150)),
f=rep(rep(LETTERS[1:3],50),2),
var=rep(letters[1:2],each=150))

library(ggplot2)

zz <- split(z,z$var)

## a variety of different combinations:

## wide y-axis, facets
g1 <- ggplot(zz[[1]],aes(x=x,y=y))+geom_point()+facet_grid(.~f)+
opts(panel.margin=unit(0,"inches"))+
labs(y=expression(log[10]("second axis")))+
opts(axis.text.x=theme_blank(),axis.title.x=theme_blank())

## narrow y-axis, facets, legend
g2 <- ggplot(zz[[2]],aes(x=x,y=y,colour=f))+geom_point()+facet_grid(.~f)+


opts(panel.margin=unit(0,"inches"))+
labs(y="first axis")+
opts(strip.text.x=theme_blank(),strip.background=theme_blank())

## narrow y-axis, facets, no legend
g3 <- ggplot(zz[[2]],aes(x=x,y=y))+geom_point(colour="red")+facet_grid(.~f)+


opts(panel.margin=unit(0,"inches"))+
labs(y="first axis")+
opts(strip.text.x=theme_blank(),strip.background=theme_blank())

## wide y-axis, no facets, no legend
g4 <- ggplot(zz[[1]],aes(x=x,y=y))+geom_point()+
labs(y=expression(log[10]("second axis")))+
opts(axis.text.x=theme_blank(),axis.title.x=theme_blank())

## narrow y-axis, no facets, no legend
g5 <- ggplot(zz[[2]],aes(x=x,y=y))+geom_point(colour="red")+


labs(y="first axis")+
opts(strip.text.x=theme_blank(),strip.background=theme_blank())

## narrow y-axis, no facets, legend
g6 <- ggplot(zz[[2]],aes(x=x,y=y,colour=f))+geom_point()+


labs(y="first axis")+
opts(strip.text.x=theme_blank(),strip.background=theme_blank())


library(gridExtra)
grid.arrange(g1,g2,g3,g4,g5,g6,ncol=3,nrow=2) ## check all plots
library(ggExtra)
theme_set(theme_bw())

grid.newpage()
## align all plots vertically.
## numbers 2 and 6 (the ones with legends, with or without
## facets) are misaligned
align.plots(g1,g2,g3,g4,g5,g6)
grid.newpage()
## this is true even if we suppress the legends
align.plots(g1,g2+opts(legend.position="none"),g3,g4,g5,
g6+opts(legend.position="none"))

## my (so far unsuccessful) attempts at tracking down the
## horizontal spacing differences between #5 and #6
gg5 <- ggplotGrob(g5)
gg6 <- ggplotGrob(g6+opts(legend.position="none"))

gl5 <- lapply(gg5$children$layout$children,grobWidth)
gl5[sapply(gl5,as.numeric)>0]

gg5$children$layout$children$`panel-3-3`$children[[3]]
gg6$children$layout$children$`panel-3-3`$children[[3]]

gl6 <- lapply(gg6$children$layout$children,grobWidth)
gl6[sapply(gl6,as.numeric)>0]


Reply all
Reply to author
Forward
0 new messages