Setting x/y lim in facet_grid

4,277 views
Skip to first unread message

Stavros Macrakis

unread,
Dec 30, 2009, 11:44:41 AM12/30/09
to ggplot2
If what I'm asking for below can't be done with facet_grid(scales="free"), is it possible to set  coord_cartesian-type limits independently in each row and column of a facet_grid?

On Thu, Dec 24, 2009 at 11:12 PM, Stavros Macrakis <macr...@post.harvard.edu> wrote:
I have some data which is inherently positive, and would like to ensure that all graphs in a facet-grid start with y=0, but scale appropriately on the high end.

For example, suppose I'm comparing the growth of hamburger and hot dog sales from year to year:

fr <-
  data.frame(
             year=rep(1:10,2),                                         # 2 runs of 10
             quant=(c(1:10,1:10*3)+rnorm(20,0,3))^2,     # some variation in dependent var
             type=rep(c('hamburgers','hot dogs'),each=10))     # first 10 and second 10 are separate series

and plot it with:

ggplot(fr,aes(x=year,y=quant)) +
  geom_point() +
  geom_smooth() +
  facet_grid(type~.,scales="free")

This is fine, except that the geom_smooth overflows into (meaningless) negative values.

So I'd like to do something like

   +  coord_cartesian(ylim=c(0,NA))    # causes internal error

to ensure that the lower y limit is always at 0 to make the graphs comparable.  I do, however, want to let the upper y limit be free.

How do I do this in ggplot?

              -s



hadley wickham

unread,
Jan 2, 2010, 5:10:07 PM1/2/10
to Stavros Macrakis, ggplot2
> If what I'm asking for below can't be done with facet_grid(scales="free"),
> is it possible to set  coord_cartesian-type limits independently in each row
> and column of a facet_grid?

Not currently, unfortunately. If you could suggest some possible
syntaxes I can think about how to incorporate it into the back end.

Hadley

> --
> You received this message because you are subscribed to the ggplot2 mailing
> list.
> To post to this group, send email to ggp...@googlegroups.com
> To unsubscribe from this group, send email to
> ggplot2+u...@googlegroups.com
> For more options, visit this group at
> http://groups.google.com/group/ggplot2

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

baptiste auguie

unread,
Jan 3, 2010, 6:16:47 AM1/3/10
to hadley wickham, Stavros Macrakis, ggplot2
Hi,

Lattice has the following approach: the default scales are calculated
automatically but the user can override this with custom limits in the
form of a list. The list elements are recycled if necessary. The
following example illustrates a possible syntax,

set.seed(123)
xx = stats::runif(10)
yy = stats::runif(10, min=-0.2, max=1.2)

library(grid)
grid.panel <- function(ii=1,
x = xx,
y = yy,
xscale = extendrange(x),
yscale = extendrange(y),
vp=viewport(layout.pos.row=ii, layout.pos.col=1)) {

## Inf means the full range
yscale[yscale == Inf] <- max(yy)
yscale[yscale == -Inf] <- min(yy)

pushViewport(vp)

plot.vp <- viewport(xscale=xscale, yscale=yscale, height=0.6, clip=T)
pushViewport(plot.vp)
grid.points(x, y, gp=gpar(col="blue"), pch="+")
grid.rect()
upViewport()

surr.vp <- viewport(xscale=xscale, yscale=yscale, height=0.6, clip=F)
pushViewport(surr.vp)
grid.yaxis(name="y")
grid.xaxis(name="x")
upViewport()

upViewport()
}

plot.multipanel <- function(ylim=NULL){
if(is.null(ylim))
ylim <- replicate(4, extendrange(yy), simplify=FALSE) else
ylim <- rep(ylim, length=4)

pushViewport(viewport(layout=grid.layout(4, 1), width=0.8, height=0.8))
grid.panel(1, yscale=ylim[[1]])
grid.panel(2, yscale=ylim[[2]])
grid.panel(3, yscale=ylim[[3]])
grid.panel(4, yscale=ylim[[4]])
upViewport()

}

# set all scales to [0, 1]
grid.newpage()
plot.multipanel(ylim=list(c(0, 1)))

# set every odd/even scale
grid.newpage()
plot.multipanel(ylim=list(range(yy), c(0, 1)))

# set every scale
grid.newpage()
plot.multipanel(ylim=list(extendrange(yy), c(-Inf, Inf), c(0, 1), c(0, Inf)))


All the best,

baptiste

hadley wickham

unread,
Jan 3, 2010, 4:28:14 PM1/3/10
to baptiste auguie, Stavros Macrakis, ggplot2
Hi Baptiste,

I don't find that syntax at all appealing because it loses the
connection between the data and panels - the panels are not just a
meaningless list but have variables associated with them.

Hadley

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

baptiste auguie

unread,
Jan 4, 2010, 4:31:39 AM1/4/10
to hadley wickham, ggplot2
Hi,

True, a list isn't very meaningful but it's the most straight-forward
way I could think of. If I follow you it would be better to provide a
data.frame with the same structure as the plot. To this aim, it would
be nice to have a way to extract a minimal data set from a ggplot2
object that reproduces the plot structure (with flattened layers).
Say, I have the following plot,

p = qplot(mpg, wt, data=mtcars) +
facet_grid(cyl ~ vs) # + maybe other layers with different data

I would call,

s = extractStructure(p)

and get a result like,

s = subset(mtcars, select = c("mpg","wt","cyl","vs"))
s = s[ !duplicated(subset(s, select=c("cyl","vs"))) , ]

s[, c("mpg","wt") ] <- NA
# maybe these variables are not needed at all
# or perhaps they should be replaced with their range in each panel

str(s)
'data.frame': 5 obs. of 4 variables:
$ mpg: logi NA NA NA NA NA
$ wt : logi NA NA NA NA NA
$ cyl: num 6 4 6 8 4
$ vs : num 0 1 1 0 0

From this minimal data.frame the user can identify each combination of
factors that is present in the plot and override the default scales,

s$default.y.scale <- list(c(-Inf, Inf))
s$default.x.scale <- list(c(-Inf, Inf))

s$default.x.scale[with(s, cyl == 6 & vs == 0)] <- list(c(0, Inf))

(of course the syntax should be made easier)

mpg wt cyl vs default.y.scale default.x.scale
Mazda RX4 NA NA 6 0 -Inf, Inf 0, Inf
Datsun 710 NA NA 4 1 -Inf, Inf -Inf, Inf
Hornet 4 Drive NA NA 6 1 -Inf, Inf -Inf, Inf
Hornet Sportabout NA NA 8 0 -Inf, Inf -Inf, Inf
Porsche 914-2 NA NA 4 0 -Inf, Inf -Inf, Inf

At this point it is similar in spirit to the recent expand_range()
function, in fact one could imagine that several parameters could be
adjusted this way:

- limits
- axis breaks and axis labels
- panel and inter-panel sizes
- strip labels
- ... (maybe one day panel.background color, other crazy things)


Best,

baptiste

hadley wickham

unread,
Jan 4, 2010, 10:40:25 AM1/4/10
to baptiste auguie, ggplot2
Hmmmm. I think we're getting closer - thanks providing suggestions
that I keep objecting too - it really helps me think about the
problem!

I think development version of ggplot2, the facetting system now
creates a data frame that looks something like this:

PANEL ROW COL SCALE_X SCALE_Y vs cyl
1 1 1 1 1 1 0 4
2 2 2 1 1 1 1 4
3 3 1 2 1 2 0 6
4 4 2 2 1 2 1 6
5 5 1 3 1 3 0 8
6 6 2 3 1 3 1 8

(from qplot(mpg, wt, data = mtcars) + facet_grid(vs ~ cyl, scales="free_y"))

this describes the relationship between the data and the panels, as
well as describing the constraints imposed on the scales (I think this
is the info you'd want from extractStructure - the complication is
that it's only generated just prior to plotting). Any way of manually
specifying breaks and limits needs to keep these constraints in mind
by either telling the user what they're doing is impossible and
raising an error (you can't set different x limits in this case),
updating the scale specification (to force differences even though) or
automatically combining in some way.

I think specification of varying scale parameters must come in through
the facetter, because this is what coordinates the data, scales,
coordinate system and panelling specification to produce the final
plot. I like the idea of using a data frame, but it makes it hard to
pass in vector valued variables, because you have to jump through
hoops to get them into a data frame. I'm currently working on an
alternative to the data frame, the data list, which makes this easier,
but it's not ready yet and I'm not sure whether having yet another
function to learn is a good idea.

I could add scale.x.limits, scale.y.limits, scale.x.breaks,
scale.y.breaks, scale.x.labels, scale.y.labels as arguments to the
facetters. This would be easy to do, but I don't like the explosion
of arguments, and it's not very maintainable if I add more parameters
to the scales. I think scale_params = list(x = list(), y = list())
would be more maintainable, but I don't like breaking the connection
between the parameters and the data.

Hadley

PS. Making it possible to change the background colour of individual
panels becomes fairly easy in this paradigm I just do something along
the lines of:

background <- rep(theme$panel.background, length = max(panels$PANEL)
background[panels$PANEL]

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

baptiste auguie

unread,
Jan 5, 2010, 7:01:24 AM1/5/10
to hadley wickham, ggplot2
That sounds good! I'm not sure what you mean by "data list", is it
simply a list or a new class altogether?

Best,

baptiste

hadley wickham

unread,
Jan 5, 2010, 8:14:36 AM1/5/10
to baptiste auguie, ggplot2
It's a list with the constraint that all elements must have the same
"length" (i.e. length(x) = n or nrow(x) = n). It's like a data frame
but without all the auto-coercions, and so is a bit faster, and is
also easier to use with m*ply.

Hadley

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

ignacio

unread,
Sep 30, 2014, 3:36:16 PM9/30/14
to ggp...@googlegroups.com, bapt...@googlemail.com
Hi,

is this capability implemented?

Thanks,

Ignacio

On Wednesday, June 12, 2013 5:16:12 PM UTC-4, Malcolm Cook wrote:
Hi,

Am I correct that this capability has not been implemented?   That is what I understand from my reading of the docs (http://docs.ggplot2.org/current/facet_grid.html)

If it has not, are there any 'clever' workarounds/hacks?

Thanks,

Malcolm
Reply all
Reply to author
Forward
0 new messages