ggplot2/ grid equivalents for mfrow and mtext

1,006 views
Skip to first unread message

Will

unread,
Sep 3, 2009, 2:55:30 PM9/3/09
to ggplot2
Hi -

I'm finally moving from the base graphics to ggplot2, but have
encountered some difficulty in migrating multiple plot (and multiple
page) layouts on a pdf device. Using the base package, this is as easy
as the following (repeated for each page and assumes the pdf() device
is active):

x<-1:10; y<-rnorm(x); # Dummy data
old.par <- par(no.readonly = TRUE); # Store par()
par(mfrow=c(2,3),oma=1*c(1,1,1,1)); # Set layout for a single page
for (i in 1:6) # Loop through the plots
plot(x,y); # Plot
mtext('My title of titles',side=3,outer=TRUE); # Set the page title
mtext('My footer',side=1,outer=TRUE); # Set the page footer
par(old.par); # Restore par()

So far, I have been unable to find a similar straightforward method to
achieve a multiple-graphic page with headers and footers using
ggplot2. Any suggestions?

Thanks -

R version 2.9.2 (2009-08-24)
Linux 2.6.27.29-170.2.78.fc10.x86_64

baptiste auguie

unread,
Sep 3, 2009, 3:53:45 PM9/3/09
to Will, ggplot2
Hi,

I've just tried to improve a little helper function for this purpose (*),

arrange <- function(..., nrow=NULL, ncol=NULL, as.table=FALSE,
main=NULL, sub=NULL, plot=TRUE) {
dots <- list(...)
n <- length(dots)
if(is.null(nrow) & is.null(ncol)) { nrow = floor(n/2) ; ncol = ceiling(n/nrow)}
if(is.null(nrow)) { nrow = ceiling(n/ncol)}
if(is.null(ncol)) { ncol = ceiling(n/nrow)}
## NOTE see n2mfrow in grDevices for possible alternative

fg <- frameGrob(layout=grid.layout(nrow,ncol))

ii.p <- 1
for(ii.row in seq(1, nrow)){
ii.table.row <- ii.row
if(as.table) {ii.table.row <- nrow - ii.table.row + 1}
for(ii.col in seq(1, ncol)){
ii.table <- ii.p
if(ii.p > n) break
fg <- placeGrob(fg, ggplotGrob(dots[[ii.table]]),
row=ii.table.row, col=ii.col)
ii.p <- ii.p + 1
}
}

if(!is.null(main) | !is.null(sub)){
g <- frameGrob() # large frame to place title(s) and content
g <- packGrob(g, fg)
if (!is.null(main))
g <- packGrob(g, textGrob(main), side="top")
if (!is.null(sub))
g <- packGrob(g, textGrob(sub), side="bottom")
} else {
g <- fg
}

if(plot) grid.draw(g)
invisible(g)
}


library(ggplot2)
plots = llply(1:5, function(.x) qplot(1:10,rnorm(10),main=paste("plot",.x)))

arrange(plots[[1]],plots[[2]],plots[[3]], nrow=2, as.table=TRUE,
main="test main", sub="subtitle test")



A few improvements that I'd like to make in the future:

- ideally one could mix 'regular' grobs and ggplot2 objects, but I'm
not sure how to deal with a input list of mixed classes

- it is painfully slow, probably because of the use of ggplotGrob()
and grid.pack (the latter could be easily circumvented)

- I guess one would want some options regarding the formatting of the
main and sub titles

ideas are welcome


HTH,

baptiste

(*) this code will eventually appear in the gridextra package at
http://code.google.com/p/gridextra/
--
_____________________________

Baptiste Auguié

School of Physics
University of Exeter
Stocker Road,
Exeter, Devon,
EX4 4QL, UK

http://newton.ex.ac.uk/research/emag
______________________________

Will

unread,
Sep 3, 2009, 4:33:20 PM9/3/09
to ggplot2


On Sep 3, 3:53 pm, baptiste auguie <bapt4...@googlemail.com> wrote:
> I've just tried to improve a little helper function for this purpose (*),
>
> A few improvements that I'd like to make in the future:
>
> - ideally one could mix 'regular' grobs and ggplot2 objects, but I'm
> not sure how to deal with a input list of mixed classes
>
> - it is painfully slow, probably because of the use of ggplotGrob()
> and grid.pack (the latter could be easily circumvented)
>
> - I guess one would want some options regarding the formatting of the
> main and sub titles
>
> ideas are welcome
>
> HTH,
>
> baptiste
>
> (*) this code will eventually appear in the gridextra package at http://code.google.com/p/gridextra/

I think this helper function is an excellent idea and the grid.* calls
are very helpful. W/r/t mixed inputs, an if-else on the class may help
i.e.
if ("ggplot" %in% class(dots[[ii.table]])) {
# Process as ggplot object
} else {
# Process as grob object
}


Thanks!

baptiste auguie

unread,
Sep 3, 2009, 5:07:35 PM9/3/09
to Will, ggplot2
The code is getting (even more) ugly so I've renamed it as arrange2.r for now,

http://gridextra.googlecode.com/svn-history/r21/trunk/R/arrange2.r

Best,

baptiste

Will

unread,
Sep 5, 2009, 3:15:02 PM9/5/09
to ggplot2
On Sep 3, 3:53 pm, baptiste auguie <bapt4...@googlemail.com> wrote:
> - it is painfully slow, probably because of the use of ggplotGrob()
> and grid.pack (the latter could be easily circumvented)

I have looked into this a bit today, and think much of the time
results from the re-rendering of each plot instead of placing
everything into a gTree then rendering.

The following call stack appears to trigger the initial render
(ConvertX() onward lie in the grid package environment):
ggplotGrob() -> panelGrob() -> <proto>$facet$add_guides() -> max2 ->
to_cm -> ConvertX -> ConvertUnit -> grid.Call -> .Call(L_gridDirty)

I would suggest amending the ggplotGrob() function (or more
specifically, the "max2"/"to_cm" function -- as well as any other
similar functions) to permit the creation of a gTree object without
any rendering so that users may quickly aggregate multiple ggplots
(though I suspect this is the intent of the ggplot::grid* functions
currently under development??). I am uncertain as to whether or not
the grid package really needs to activate a display device to perform
the unit conversion.

HTH

Will

baptiste auguie

unread,
Sep 5, 2009, 3:55:32 PM9/5/09
to Will, ggplot2
I hadn't spotted that print.ggplot actually calls ggplotGrob() as
well. So, given the limited scope of this arrange() function, I don't
think I can improve things much further. (except that the code could
be prettier at places). If in the course of your experiments you wrote
some layout functions, please do feel free to add /improve code in
this gridextra package.

I vaguely remember seeing that convertX() does indeed require an open
device (I stared at L_convert in grid.c but couldn't find this stated
explicitly).

Thanks,

baptiste

hadley wickham

unread,
Sep 6, 2009, 9:37:37 AM9/6/09
to Will, ggplot2
> I would suggest amending the ggplotGrob() function (or more
> specifically, the "max2"/"to_cm" function -- as well as any other
> similar functions) to permit the creation of a gTree object without
> any rendering so that users may quickly aggregate multiple ggplots
> (though I suspect this is the intent of the ggplot::grid* functions
> currently under development??). I am uncertain as to whether or not
> the grid package really needs to activate a display device to perform
> the unit conversion.

That's how ggplotGrob does work (hence the name) - it does no
rendering, just creates a gTree object for later rendering. As
Baptiste says, I think the active device is needed for some of the
unit conversions that I do.

Some of the slowness is because I'm fighting the usual grid way of
doing things - I don't use gpar, I don't use grid's native coordinates
and I know how big everything on the plot should be (apart from the
panels). I convert many of the units to cm because my feeling is that
should speed things up - instead of having to perform unit conversions
multiple times, I force grid to do them once (OTOH I'm not sure this
actually helps).

The gridGrob family of functions is my first (slow) attempt to
generalise the layout pattern I'm using. Any suggestions for making
it faster would be gratefully received.

Hadley

--
http://had.co.nz/

Will

unread,
Sep 7, 2009, 10:15:51 AM9/7/09
to ggplot2
On Sep 6, 9:37 am, hadley wickham <h.wick...@gmail.com> wrote:
> > I would suggest amending the ggplotGrob() function (or more
> > specifically, the "max2"/"to_cm" function --
>
> That's how ggplotGrob does work (hence the name) - it does no
> rendering, just creates a gTree object for later rendering.  As
> Baptiste says, I think the active device is needed for some of the
> unit conversions that I do.
>
> Some of the slowness is because I'm fighting the usual grid way of
> doing things - I don't use gpar, I don't use grid's native coordinates
> and I know how big everything on the plot should be (apart from the
> panels).  I convert many of the units to cm because my feeling is that
> should speed things up - instead of having to perform unit conversions
> multiple times, I force grid to do them once (OTOH I'm not sure this
> actually helps).
>
> The gridGrob family of functions is my first (slow) attempt to
> generalise the layout pattern I'm using.  Any suggestions for making
> it faster would be gratefully received.
>
> Hadley
>
> --http://had.co.nz/

@ Baptiste and Hadley
Thanks - I have a couple immediate projects to work through, but I
will certainly pass along any thoughts/ code patches that might help
as they become available. I should have more time to dedicate in the
near future.

I should have referenced the device activation (as opposed to
rendering) w/r/t ggplotGrob. In a specialised case, it was just
slowing down the creation of gTrees for later output on a user-defined
device.

Thanks again!
Reply all
Reply to author
Forward
0 new messages