Discussion:
Positioning two legends independently in a faceted ggplot2 plot
Faheem Mitha
2013-05-12 11:48:37 UTC
Permalink
Hello,

I've posted a ggplot2 question to Stack Overflow,
http://stackoverflow.com/q/16501999/350713. I've reproduced the text of
that question below. The SO question also contains an image.

Is there some way to disable email sending by Google groups? I can
read replies via Gmane.

Regards, Faheem

###################################################################

I have a plot generated by ggplot2, which contains two legends. The
placing of the legends is not ideal, so I would like to adjust
them. I've been trying to imitate the method shown in [the answer to
"How do I position two legends independently in
ggplot"](http://stackoverflow.com/a/13327793/350713). The example
shown in that answer works. However, I can't get the method described
to work for my situation.

'm using R 2.15.3 (2013-03-01), ggplot2_0.9.3.1, lattice_0.20-13, gtable_0.1.2, gridExtra_0.9.1 on Debian squeeze.

Consider the plot generated by `minimal.R`. This is similar to
my actual plot.

########################
minimal.R
########################

get_stat <- function()
{
n = 20
q1 = qnorm(seq(3, 17)/20, 14, 5)
q2 = qnorm(seq(1, 19)/20, 65, 10)
Stat = data.frame(value = c(q1, q2),
pvalue = c(dnorm(q1, 14, 5)/max(dnorm(q1, 14, 5)), d = dnorm(q2, 65, 10)/max(dnorm(q2, 65, 10))),
variable = c(rep('true', length(q1)), rep('data', length(q2))))
return(Stat)
}

stat_all<- function()
{
library(ggplot2)
library(gridExtra)
stathuman = get_stat()
stathuman$dataset = "human"
statmouse = get_stat()
statmouse$dataset = "mouse"
stat = merge(stathuman, statmouse, all=TRUE)
return(stat)
}

simplot <- function()
{
Stat = stat_all()
Pvalue = subset(Stat, variable=="true")
pdf(file = "CDF.pdf", width = 5.5, height = 2.7)
stat = ggplot() + stat_ecdf(data=Stat, n=1000, aes(x=value, colour = variable)) +
theme(legend.key = element_blank(), legend.background = element_blank(), legend.position=c(.9, .25), legend.title = element_text(face = "bold")) +
scale_x_continuous("Negative log likelihood") +
scale_y_continuous("Proportion $<$ x") +
facet_grid(~ dataset, scales='free') +
scale_colour_manual(values = c("blue", "red"), name="Data type",
labels=c("Gene segments", "Model"), guide=guide_legend(override.aes = list(size = 2))) +
geom_area(data=Pvalue, aes(x=value, y=pvalue, fill=variable), position="identity", alpha=0.5) +
scale_fill_manual(values = c("gray"), name="Pvalue", labels=c(""))
print(stat)
dev.off()
}

simplot()

This results in the following plot. As can be seen, the `Data type`
and `Pvalue` legends are not well positioned. I modified this code to
`minimal2.R`.

With Version 1, which should put the legend on the top, the code runs
without error, but no legend is shown.

EDIT: There are two boxes displayed, one on top of the other. The top
one is blank. If I do not set the heights in grid.arrange(), as
suggeted by @baptiste, then the legend and the plot are both placed in
the bottom box. If I set the height as shown, then I don't see the
legend.

With Version 2, I get this error.

Error in UseMethod("grid.draw") :
no applicable method for 'grid.draw' applied to an object of class "c('gg', 'ggplot')"
Calls: simplot -> grid.draw

EDIT: If I use print(plotNew) as suggested by @baptiste, then I get the following error

Error in if (empty(data)) { : missing value where TRUE/FALSE needed
Calls: simplot ... facet_map_layout -> facet_map_layout.grid ->
locate_grid.

I tried to figure out what is going on here, but I could not find much
relevant information.

NOTES:

1. I'm not sure why I'm getting the staircase effect for the empirical
CDF. I'm sure there is an obvious explanation. Please enlighten me if
you know.

2. I'm willing to consider alternatives to this code and even ggplot2 for
producing this graph, if anyone can suggest alternatives,
e.g. matplotlib, which I have never seriously experimented with.

3. Adding

print(ggplot_gtable(ggplot_build(stat2)))

to `minimal2.R` gives me

TableGrob (7 x 7) "layout": 12 grobs
z cells name grob
1 0 (1-7,1-7) background rect[plot.background.rect.186]
2 1 (3-3,4-4) strip-top absoluteGrob[strip.absoluteGrob.135]
3 2 (3-3,6-6) strip-top absoluteGrob[strip.absoluteGrob.141]
4 5 (4-4,3-3) axis-l absoluteGrob[GRID.absoluteGrob.129]
5 3 (4-4,4-4) panel gTree[GRID.gTree.155]
6 4 (4-4,6-6) panel gTree[GRID.gTree.169]
7 6 (5-5,4-4) axis-b absoluteGrob[GRID.absoluteGrob.117]
8 7 (5-5,6-6) axis-b absoluteGrob[GRID.absoluteGrob.123]
9 8 (6-6,4-6) xlab text[axis.title.x.text.171]
10 9 (4-4,2-2) ylab text[axis.title.y.text.173]
11 10 (4-4,4-6) guide-box gtable[guide-box]
12 11 (2-2,4-6) title text[plot.title.text.184]

I don't understand this breakdown. Can anyone explain? Does
guide-box correspond to the legend, and how does one know this?

########################
minimal2.R
########################

get_stat <- function()
{
n = 20
q1 = qnorm(seq(3, 17)/20, 14, 5)
q2 = qnorm(seq(1, 19)/20, 65, 10)
Stat = data.frame(value = c(q1, q2),
pvalue = c(dnorm(q1, 14, 5)/max(dnorm(q1, 14, 5)), d = dnorm(q2, 65, 10)/max(dnorm(q2, 65, 10))),
variable = c(rep('true', length(q1)), rep('data', length(q2))))
return(Stat)
}

stat_all<- function()
{
library(ggplot2)
library(gridExtra)
library(gtable)
stathuman = get_stat()
stathuman$dataset = "human"
statmouse = get_stat()
statmouse$dataset = "mouse"
stat = merge(stathuman, statmouse, all=TRUE)
return(stat)
}

simplot <- function()
{
Stat = stat_all()
Pvalue = subset(Stat, variable=="true")
pdf(file = "CDF.pdf", width = 5.5, height = 2.7)

## only include data type legend
stat1 = ggplot() + stat_ecdf(data=Stat, n=1000, aes(x=value, colour = variable)) +
theme(legend.key = element_blank(), legend.background = element_blank(), legend.position=c(.9, .25), legend.title = element_text(face = "bold")) +
scale_x_continuous("Negative log likelihood") +
scale_y_continuous("Proportion $<$ x") +
facet_grid(~ dataset, scales='free') +
scale_colour_manual(values = c("blue", "red"), name="Data type", labels=c("Gene segments", "Model"), guide=guide_legend(override.aes = list(size = 2))) +
geom_area(data=Pvalue, aes(x=value, y=pvalue, fill=variable), position="identity", alpha=0.5) +
scale_fill_manual(values = c("gray"), name="Pvalue", labels=c(""), guide=FALSE)

## Extract data type legend
dataleg <- gtable_filter(ggplot_gtable(ggplot_build(stat1)), "guide-box")

## only include pvalue legend
stat2 = ggplot() + stat_ecdf(data=Stat, n=1000, aes(x=value, colour = variable)) +
theme(legend.key = element_blank(), legend.background = element_blank(), legend.position=c(.9, .25), legend.title = element_text(face = "bold")) +
scale_x_continuous("Negative log likelihood") +
scale_y_continuous("Proportion $<$ x") +
facet_grid(~ dataset, scales='free') +
scale_colour_manual(values = c("blue", "red"), name="Data type", labels=c("Gene segments", "Model"), guide=FALSE) +
geom_area(data=Pvalue, aes(x=value, y=pvalue, fill=variable), position="identity", alpha=0.5) +
scale_fill_manual(values = c("gray"), name="Pvalue", labels=c(""))

## Extract pvalue legend
pvalleg <- gtable_filter(ggplot_gtable(ggplot_build(stat2)), "guide-box")

## no legends
stat = ggplot() + stat_ecdf(data=Stat, n=1000, aes(x=value, colour = variable)) +
theme(legend.key = element_blank(), legend.background = element_blank(), legend.position=c(.9, .25), legend.title = element_text(face = "bold")) +
scale_x_continuous("Negative log likelihood") +
scale_y_continuous("Proportion $<$ x") +
facet_grid(~ dataset, scales='free') +
scale_colour_manual(values = c("blue", "red"), name="Data type", labels=c("Gene segments", "Model"), guide=FALSE) +
geom_area(data=Pvalue, aes(x=value, y=pvalue, fill=variable), position="identity", alpha=0.5) +
scale_fill_manual(values = c("gray"), name="Pvalue", labels=c(""), guide=FALSE)

## Add data type legend: version 1 (data type legend should be on top)
## plotNew <- arrangeGrob(dataleg, stat, heights = unit.c(dataleg$height, unit(1, "npc") - dataleg$height), ncol = 1)

## Add data type legend: version 2 (data type legend should be somewhere in the interior)
## plotNew <- stat + annotation_custom(grob = dataleg, xmin = 7, xmax = 10, ymin = 0, ymax = 4)

grid.newpage()
grid.draw(plotNew)
dev.off()
}

simplot()

--
Faheem Mitha
2013-05-13 19:47:15 UTC
Permalink
Post by Faheem Mitha
Hello,
I've posted a ggplot2 question to Stack Overflow,
http://stackoverflow.com/q/16501999/350713. I've reproduced the text of that
question below. The SO question also contains an image.
In my question, the issue with annotation_custom (what I called version 2)
seems to be similar, if not identical, to that described by Bryan Hanson
in https://github.com/hadley/ggplot2/issues/756.

Version 1 seems to be user error. I don't see a problem here.

Does anyone have any idea how the annotation_custom issue could be fixed?
It seems baptiste was the original author of annotation_custom, so CCing.
Post by Faheem Mitha
Is there some way to disable email sending by Google groups? I can
read replies via Gmane.
Regards, Faheem
###################################################################
I have a plot generated by ggplot2, which contains two legends. The
placing of the legends is not ideal, so I would like to adjust
them. I've been trying to imitate the method shown in [the answer to
"How do I position two legends independently in
ggplot"](http://stackoverflow.com/a/13327793/350713). The example
shown in that answer works. However, I can't get the method described
to work for my situation.
'm using R 2.15.3 (2013-03-01), ggplot2_0.9.3.1, lattice_0.20-13,
gtable_0.1.2, gridExtra_0.9.1 on Debian squeeze.
Consider the plot generated by `minimal.R`. This is similar to
my actual plot.
########################
minimal.R
########################
get_stat <- function()
{
n = 20
q1 = qnorm(seq(3, 17)/20, 14, 5)
q2 = qnorm(seq(1, 19)/20, 65, 10)
Stat = data.frame(value = c(q1, q2),
pvalue = c(dnorm(q1, 14, 5)/max(dnorm(q1, 14, 5)), d = dnorm(q2,
65, 10)/max(dnorm(q2, 65, 10))),
variable = c(rep('true', length(q1)), rep('data', length(q2))))
return(Stat)
}
stat_all<- function()
{
library(ggplot2)
library(gridExtra)
stathuman = get_stat()
stathuman$dataset = "human"
statmouse = get_stat()
statmouse$dataset = "mouse"
stat = merge(stathuman, statmouse, all=TRUE)
return(stat)
}
simplot <- function()
{
Stat = stat_all()
Pvalue = subset(Stat, variable=="true")
pdf(file = "CDF.pdf", width = 5.5, height = 2.7)
stat = ggplot() + stat_ecdf(data=Stat, n=1000, aes(x=value, colour = variable)) +
theme(legend.key = element_blank(), legend.background =
element_blank(), legend.position=c(.9, .25), legend.title = element_text(face
= "bold")) +
scale_x_continuous("Negative log likelihood") +
scale_y_continuous("Proportion $<$ x") +
facet_grid(~ dataset, scales='free') +
scale_colour_manual(values = c("blue", "red"), name="Data type",
labels=c("Gene segments", "Model"),
guide=guide_legend(override.aes = list(size = 2))) +
geom_area(data=Pvalue, aes(x=value, y=pvalue,
fill=variable), position="identity", alpha=0.5) +
scale_fill_manual(values = c("gray"), name="Pvalue", labels=c(""))
print(stat)
dev.off()
}
simplot()
This results in the following plot. As can be seen, the `Data type`
and `Pvalue` legends are not well positioned. I modified this code to
`minimal2.R`.
With Version 1, which should put the legend on the top, the code runs
without error, but no legend is shown.
EDIT: There are two boxes displayed, one on top of the other. The top
one is blank. If I do not set the heights in grid.arrange(), as
the bottom box. If I set the height as shown, then I don't see the
legend.
With Version 2, I get this error.
no applicable method for 'grid.draw' applied to an object of class "c('gg', 'ggplot')"
Calls: simplot -> grid.draw
Error in if (empty(data)) { : missing value where TRUE/FALSE needed
Calls: simplot ... facet_map_layout -> facet_map_layout.grid ->
locate_grid.
I tried to figure out what is going on here, but I could not find much
relevant information.
1. I'm not sure why I'm getting the staircase effect for the empirical
CDF. I'm sure there is an obvious explanation. Please enlighten me if
you know.
2. I'm willing to consider alternatives to this code and even ggplot2 for
producing this graph, if anyone can suggest alternatives,
e.g. matplotlib, which I have never seriously experimented with.
3. Adding
print(ggplot_gtable(ggplot_build(stat2)))
to `minimal2.R` gives me
TableGrob (7 x 7) "layout": 12 grobs
z cells name grob
1 0 (1-7,1-7) background rect[plot.background.rect.186]
2 1 (3-3,4-4) strip-top absoluteGrob[strip.absoluteGrob.135]
3 2 (3-3,6-6) strip-top absoluteGrob[strip.absoluteGrob.141]
4 5 (4-4,3-3) axis-l absoluteGrob[GRID.absoluteGrob.129]
5 3 (4-4,4-4) panel gTree[GRID.gTree.155]
6 4 (4-4,6-6) panel gTree[GRID.gTree.169]
7 6 (5-5,4-4) axis-b absoluteGrob[GRID.absoluteGrob.117]
8 7 (5-5,6-6) axis-b absoluteGrob[GRID.absoluteGrob.123]
9 8 (6-6,4-6) xlab text[axis.title.x.text.171]
10 9 (4-4,2-2) ylab text[axis.title.y.text.173]
11 10 (4-4,4-6) guide-box gtable[guide-box]
12 11 (2-2,4-6) title text[plot.title.text.184]
I don't understand this breakdown. Can anyone explain? Does
guide-box correspond to the legend, and how does one know this?
########################
minimal2.R
########################
get_stat <- function()
{
n = 20
q1 = qnorm(seq(3, 17)/20, 14, 5)
q2 = qnorm(seq(1, 19)/20, 65, 10)
Stat = data.frame(value = c(q1, q2),
pvalue = c(dnorm(q1, 14, 5)/max(dnorm(q1, 14, 5)), d = dnorm(q2,
65, 10)/max(dnorm(q2, 65, 10))),
variable = c(rep('true', length(q1)), rep('data', length(q2))))
return(Stat)
}
stat_all<- function()
{
library(ggplot2)
library(gridExtra)
library(gtable)
stathuman = get_stat()
stathuman$dataset = "human"
statmouse = get_stat()
statmouse$dataset = "mouse"
stat = merge(stathuman, statmouse, all=TRUE)
return(stat)
}
simplot <- function()
{
Stat = stat_all()
Pvalue = subset(Stat, variable=="true")
pdf(file = "CDF.pdf", width = 5.5, height = 2.7)
## only include data type legend
stat1 = ggplot() + stat_ecdf(data=Stat, n=1000, aes(x=value, colour = variable)) +
theme(legend.key = element_blank(), legend.background =
element_blank(), legend.position=c(.9, .25), legend.title = element_text(face
= "bold")) +
scale_x_continuous("Negative log likelihood") +
scale_y_continuous("Proportion $<$ x") +
facet_grid(~ dataset, scales='free') +
scale_colour_manual(values = c("blue", "red"), name="Data
type", labels=c("Gene segments", "Model"), guide=guide_legend(override.aes =
list(size = 2))) +
geom_area(data=Pvalue, aes(x=value, y=pvalue,
fill=variable), position="identity", alpha=0.5) +
scale_fill_manual(values = c("gray"), name="Pvalue",
labels=c(""), guide=FALSE)
## Extract data type legend
dataleg <- gtable_filter(ggplot_gtable(ggplot_build(stat1)), "guide-box")
## only include pvalue legend
stat2 = ggplot() + stat_ecdf(data=Stat, n=1000, aes(x=value, colour = variable)) +
theme(legend.key = element_blank(), legend.background =
element_blank(), legend.position=c(.9, .25), legend.title = element_text(face
= "bold")) +
scale_x_continuous("Negative log likelihood") +
scale_y_continuous("Proportion $<$ x") +
facet_grid(~ dataset, scales='free') +
scale_colour_manual(values = c("blue", "red"), name="Data
type", labels=c("Gene segments", "Model"), guide=FALSE) +
geom_area(data=Pvalue, aes(x=value, y=pvalue,
fill=variable), position="identity", alpha=0.5) +
scale_fill_manual(values = c("gray"), name="Pvalue", labels=c(""))
## Extract pvalue legend
pvalleg <- gtable_filter(ggplot_gtable(ggplot_build(stat2)), "guide-box")
## no legends
stat = ggplot() + stat_ecdf(data=Stat, n=1000, aes(x=value, colour = variable)) +
theme(legend.key = element_blank(), legend.background =
element_blank(), legend.position=c(.9, .25), legend.title = element_text(face
= "bold")) +
scale_x_continuous("Negative log likelihood") +
scale_y_continuous("Proportion $<$ x") +
facet_grid(~ dataset, scales='free') +
scale_colour_manual(values = c("blue", "red"), name="Data
type", labels=c("Gene segments", "Model"), guide=FALSE) +
geom_area(data=Pvalue, aes(x=value, y=pvalue,
fill=variable), position="identity", alpha=0.5) +
scale_fill_manual(values = c("gray"), name="Pvalue",
labels=c(""), guide=FALSE)
## Add data type legend: version 1 (data type legend should be on top)
## plotNew <- arrangeGrob(dataleg, stat, heights =
unit.c(dataleg$height, unit(1, "npc") - dataleg$height), ncol = 1)
## Add data type legend: version 2 (data type legend should be
somewhere in the interior)
## plotNew <- stat + annotation_custom(grob = dataleg, xmin = 7, xmax
= 10, ymin = 0, ymax = 4)
grid.newpage()
grid.draw(plotNew)
dev.off()
}
simplot()
Under the two moons of Hydrot, and under the eternal stars, the
two-inch wooden spaceship and its microscopic cargo toiled down the
slope toward the drying little rivulet.
--
James Blish - Surface Tension

--

Loading...