Make a rectangular legend, with rows and columns labeled, in grid - r

I've got a ggplot where I'm mapping factors to both fill and alpha, like this:
set.seed(47)
the_data <- data.frame(value = rpois(6, lambda=20),
cat1 = rep(c("A", "B"), each = 3),
cat2 = rep(c("X", "Y", "Z"), 2))
ggplot(the_data, aes(y = value, x = cat2, alpha = cat1, fill = cat2)) +
geom_bar(stat = "identity", position = "dodge") +
scale_alpha_discrete(range = c(0.5, 1)) +
theme_bw()
The people I'm producing it for don't find the legend for alpha very clear. I think a good alternative would be something like this (which I hacked together in base graphics):
I know I can't generate a legend like that with high-level ggplot commands, but can I do it in grid and put it on top of my plot?

Here is one possible starting point. I create two different plots which have the appropriate legends - a 'bright' and a 'pale'. Extract the legends from the plot objects. Then use grid viewports, one for the plot, and one for each legend, to put the pieces together.
library(grid)
library(gtable)
# create plot with legend with alpha = 1
g1 <- ggplot(the_data, aes(y = value, x = cat2, alpha = cat1, fill = cat2)) +
geom_bar(stat = "identity", position = "dodge") +
scale_alpha_discrete(range = c(0.5, 1)) +
theme_bw() +
guides(fill = guide_legend(title = "A",
title.hjust = 0.4),
alpha = FALSE) +
theme_bw() +
theme(legend.text = element_blank())
g1
# grab legend
legend_g1 <- gtable_filter(ggplot_gtable(ggplot_build(g1)), "guide-box")
# create plot with 'pale' legend
g2 <- ggplot(the_data, aes(y = value, x = cat2, alpha = cat1, fill = cat2)) +
geom_bar(stat = "identity", position = "dodge") +
scale_alpha_discrete(range = c(0.5, 1)) +
guides(fill = guide_legend(override.aes = list(alpha = 0.5),
title = "B",
title.hjust = 0.3),
alpha = FALSE) +
theme_bw()
g2
# grab legend
legend_g2 <- gtable_filter(ggplot_gtable(ggplot_build(g2)), "guide-box")
# arrange plot and legends
# legends to the right
# define plotting regions (viewports)
vp_plot <- viewport(x = 0.4, y = 0.5,
width = 0.8, height = 1)
vp_legend_g1 <- viewport(x = 0.85, y = 0.5,
width = 0.4, height = 0.4)
vp_legend_g2 <- viewport(x = 0.90, y = 0.5,
width = 0.4, height = 0.4)
# clear current device
grid.newpage()
# add objects to the viewports
# plot without legend
print(g1 + theme(legend.position = "none"), vp = vp_plot)
upViewport(0)
pushViewport(vp_legend_g1)
grid.draw(legend_g1)
upViewport(0)
pushViewport(vp_legend_g2)
grid.draw(legend_g2)
# legends on top
vp_plot <- viewport(x = 0.5, y = 0.4,
width = 1, height = 0.85)
vp_legend_g1 <- viewport(x = 0.5, y = 0.9,
width = 0.4, height = 0.4)
vp_legend_g2 <- viewport(x = 0.55, y = 0.9,
width = 0.4, height = 0.4)
grid.newpage()
print(g1 + theme(legend.position = "none"), vp = vp_plot)
upViewport(0)
pushViewport(vp_legend_g1)
grid.draw(legend_g1)
upViewport(0)
pushViewport(vp_legend_g2)
grid.draw(legend_g2)

#Henrik
This might be a little easier,
g1 <- ggplotGrob(p1)
g2 <- ggplotGrob(p2)
leg1 <- gtable_filter(g1, "guide-box")
leg2 <- gtable_filter(g2, "guide-box")
leg <- gtable:::cbind_gtable(leg1[["grobs"]][[1]], leg2[["grobs"]][[1]], "first")
g1$grobs[g1$layout$name == "guide-box"][[1]] <- leg
g1$widths[max(subset(g1$layout, name == "guide-box")[["r"]])] <- list(leg1$width + leg2$width)
grid.newpage()
grid.draw(g1)

Related

Trouble with ggplot2 multiple legend alignment with grid.arrange

I'm trying to add two legends to the same ggplot2 graph and I'm having a terrible time with the alignment. I've got points representing some data and then fitted regression lines as well, so I want the legend for colors to be split up so that it's clear which are just for the points and which are for the fitted lines. Here's my best attempt so far:
library(ggplot2)
library(gridExtra)
StudyResults <- data.frame(TreatmentArm = rep(c("A", "B"), each = 10),
SubjectID = rep(1:10, each = 2),
Glucose = rnorm(20, 50, 10),
Insulin = rnorm(20, 0.15, 0.05),
StudyDay = rep(c("SD1", "SD2"), 10))
Trend <- data.frame(Gender = rep(c("F", "M"), each = 50),
Glucose = seq(20, 80, length = 50),
Insulin = NA)
Trend$Insulin[Trend$Gender == "F"] <- 2/Trend$Glucose[Trend$Gender == "F"]
Trend$Insulin[Trend$Gender == "M"] <- 5/Trend$Glucose[Trend$Gender == "M"]
PlotTrend <- ggplot(Trend, aes(x = Glucose, y = Insulin, color = Gender)) +
geom_line() + scale_color_manual(values = c("red", "blue"))
PlotStudy <- ggplot(StudyResults, aes(x = Glucose, y = Insulin, shape = StudyDay,
color = TreatmentArm, group = SubjectID)) +
geom_point() + geom_line() +
scale_color_manual(values = c("green", "black"))
g_legend <- function(a.gplot){
tmp <- ggplot_gtable(ggplot_build(a.gplot))
leg <- which(sapply(tmp$grobs, function(x) x$name) == "guide-box")
legend <- tmp$grobs[[leg]]
return(legend)}
LegendTrend <- g_legend(PlotTrend)
LegendStudy <- g_legend(PlotStudy)
PlotMain <- ggplot(StudyResults, aes(x = Glucose, y = Insulin, shape = StudyDay,
color = TreatmentArm, group = SubjectID)) +
geom_point() + geom_line() +
scale_color_manual(values = c("green", "black")) +
geom_line(data = Trend[Trend$Gender == "F", ],
aes(x = Glucose, y = Insulin),
inherit.aes = FALSE, color = "red") +
geom_line(data = Trend[Trend$Gender == "M", ],
aes(x = Glucose, y = Insulin),
inherit.aes = FALSE, color = "blue") +
theme(legend.position = "none")
grid.arrange(PlotMain,
arrangeGrob(LegendStudy, LegendTrend, nrow = 2,
heights = c(unit(0.5, "npc"),
unit(0.5, "npc")),
widths = unit(0.5, "npc")),
ncol = 2, widths = c(10, 3))
But the positioning of the legends is TERRIBLE:
I don't know how to make the legend for "Gender" be aligned on the left with the other two, and I don't want there to be so much white space in the middle.
I also fiddled with gtable and grid packages' commands like viewPort but am completely clueless about how to use them. (Suggestions for decent tutorials would be much appreciated; I haven't ever found any.) I tried
library(gtable)
library(grid)
grid.newpage()
pushViewport(vp = viewport())
vp1 <- viewport(width = 0.8, height = 1, x = 0, y = 0)
grid.draw(PlotMain)
vp2 <- viewport(width = 0.2, x = 0.9, y = 0.6)
pushViewport(vp2)
grid.draw(LegendStudy)
pushViewport(vp3 = viewport(width = 0.2, x = 0.9, y = 0.4))
grid.draw(LegendTrend)
but I clearly have no idea how to use this because the legends overlapped the main plot and were positioned in what seems to me a completely random (i.e., unrelated to the x and y coordinates I thought I was specifying) way.
it's unclear what the OP wants but the next option would be adding the second legend's gtable to the first,
library(gtable)
leg2 <- LegendTrend$grobs[[1]]
leg <- gtable_add_rows(LegendStudy, pos = nrow(LegendStudy) - 1,
heights = sum(leg2$heights))
leg <- gtable_add_grob(leg, leg2, t = nrow(leg) - 1, l = 3)
grid.arrange(PlotMain, right = leg)
For a tutorial on grid viewports, there's the R graphics book, but ggplot2's design has drifted substantially from base grid with the introduction of gtable as an intermediate framework to place graphic elements. ggplot2 legends are a complex structure of nested gtables, that few people understand. gtable is not documented, and its development stopped early on, so the best source of information is the code itself.
leg <- gtable_rbind(LegendStudy, LegendTrend, size = "first")
grid.arrange(PlotMain, right = leg)

How to simulate passing an aesthetic to panel background in ggplot2?

I read in this stack overflow question a clever way to simulate setting an aesthetic to panel background using geom_rect.
Conditionally change panel background with facet_grid?
Unfortunately, it doesn't work if you want to put other colors in the plot. The colors mix and the legend gets polluted. Instead, I would prefer that the color only applies to the background and doesn't get mixed. My other question is: is there an approach that would work in polar coordinates?
For a reproducible example, see the code below:
pies <- data_frame(pie = c(rep("hawaiian", 3), rep("pepperoni", 2)),
fraction = c(c(0.3, 0.2, 0.5), c(0.4, 0.6)),
ingredient = c("cheese", "pineapple", "ham",
"peperroni", "cheese"),
deepdish = c(rep(TRUE, 3), rep(FALSE, 2)))
p <- pies %>%
ggplot() +
geom_bar(aes(x = factor(1),
y = fraction,
fill = ingredient),
width = 0.6,
stat = "identity",
position = "fill") +
facet_wrap(~ pie) +
geom_rect(mapping = aes(fill = deepdish),
alpha = 0.1,
xmin = -Inf, xmax = Inf,
ymin=-Inf, ymax=Inf,
show.legend = FALSE)
p
p + coord_polar(theta = "y")
pies <- data_frame(pie = c(rep("hawaiian", 3), rep("pepperoni", 2)),
fraction = c(c(0.3, 0.2, 0.5), c(0.4, 0.6)),
ingredient = c("cheese", "pineapple", "ham",
"peperroni", "cheese"),
deepdish = c(rep(TRUE, 3), rep(FALSE, 2)))
library(ggplot2)
library(dplyr)
p <- pies %>%
ggplot() +
geom_bar(aes(x = factor(1), y = fraction, fill = ingredient),
width = 0.6, stat = "identity", position = "fill") +
facet_wrap(~ pie) + coord_polar(theta = "y")
g <- ggplotGrob(p)
# Set manually the background color for each panel
g$grobs[[2]]$children[[1]]$children[[1]]$gp$fill <- "#88334466"
g$grobs[[3]]$children[[1]]$children[[1]]$gp$fill <- "#44338866"
library(grid)
grid.draw(g)
library(egg)
library(grid)
pies <- data.frame(pie = c(rep("hawaiian", 3), rep("pepperoni", 2)),
fraction = c(c(0.3, 0.2, 0.5), c(0.4, 0.6)),
ingredient = c("cheese", "pineapple", "ham",
"peperroni", "cheese"))
dummy <- data.frame(x = 0, y = 0,
pie = c("hawaiian","pepperoni"),
deepdish = c("green","yellow"), stringsAsFactors = FALSE)
p <- ggplot(pies) +
facet_wrap(~ pie) +
geom_custom(data= dummy, mapping = aes(x = factor(0),
y = y,
data = deepdish),
grob_fun = function(x) rectGrob(gp=gpar(fill=x,col=NA)), inherit.aes = TRUE) +
geom_bar(aes(x = factor(1),
y = fraction,
fill = ingredient),
width = 0.6,
stat = "identity",
position = "fill")
p + coord_polar(theta = "y")

Positioning x-axis text/label along x-axis based on another field in the data using ggplot

I would like to place each x-axis text/label based on another field. Is there a native way in ggplot2 to achieve this? Presently I am doing it through geom_text. Here are my data and the plot.I have two issues with this approach -
Labels are falling inside the plot area
For a facet the labels should only appear at the bottom-most subplots as below
not in all subplots as is the case below (my plot). (The above image was taken from here)
library(ggplot2)
library(magrittr)
mydata = data.frame(expand.grid(Tag = c('A','B','C'),
Year = 2010:2011,PNo = paste0("X-",1:4)),Value = round(runif(24,1,20)))
mydata$dist = ifelse(mydata$Tag == 'A',0,ifelse(mydata$Tag=='B',2,7))
mydata %>% ggplot(aes(x = dist,y = Value,fill = factor(Year))) +
geom_bar(stat='summary',position = 'dodge',fun.y='mean',width=1) +
facet_wrap(~PNo,ncol=2) +
theme(axis.text.x = element_blank(),axis.ticks.x = element_blank()) +
geom_text(aes(x = dist,label = Tag),color = 'black',size=4,angle = 0,show.legend = F)
I would like to place Tag labels based on dist.
I notice that you have accepted an answer elsewhere, and that you have answered you own question here. But they don't quite answer your original question. In particular, the labels are still inside the plot panel. I offer two possibilities, but neither being straightforward.
The first uses a version of annotation_custom. The default annotation_custom draws the annotation in all panels. But with a small alteration (taken from here), it can be made to draw annotations in selected panels - for your plot, the lower two panels.
library(ggplot2)
library(magrittr)
mydata = data.frame(expand.grid(Tag = c('A', 'B', 'C'),
Year = 2010:2011, PNo = paste0("X-", 1:4)), Value = round(runif(24,1,20)))
mydata$dist = ifelse(mydata$Tag == 'A', 0, ifelse(mydata$Tag == 'B', 2, 7))
# The bar plot. Note extra margin above x-axis title.
# This gives space for the annotations between the panel and the title.
p1 = mydata %>% ggplot() +
geom_bar(aes(x = dist, y = Value, fill = factor(Year)),
width = 1, stat = 'identity', position = "dodge") +
facet_wrap(~PNo, ncol = 2) +
theme(axis.text.x = element_blank(),
axis.ticks.x = element_blank(),
axis.title.x = element_text(margin = margin(t = 2, unit = "lines")))
# Baptiste's modification to annotation_custom
annotation_custom2 =
function (grob, xmin = -Inf, xmax = Inf, ymin = -Inf, ymax = Inf, data) {
layer(data = data, stat = StatIdentity, position = PositionIdentity,
geom = ggplot2:::GeomCustomAnn,
inherit.aes = TRUE, params = list(grob = grob,
xmin = xmin, xmax = xmax,
ymin = ymin, ymax = ymax))
}
# The plot with annotations. (ymin and ymax set to -Inf
# draws the annotation at the bottom of the panel.
# vjust = 1.5 drops them below the panel).
for (i in 1:length(unique(mydata$Tag))) {
p1 = p1 + annotation_custom2(
grob = textGrob(label = unique(mydata$Tag)[i], vjust = 1.5,
gp = gpar(col = 'red', cex = 1)),
xmin = unique(mydata$dist)[i],
xmax = unique(mydata$dist)[i],
ymin = -Inf,
ymax = -Inf,
data=data.frame(PNo=c("X-3", "X-4") )) # The two bottom panels
}
# The annotations are placed outside the panels.
# Therefore, have to turn off clipping to the panels.
g1 = ggplotGrob(p1)
g1$layout$clip[grepl("panel", g1$layout$name)] = "off"
# Draw the chart
grid.newpage()
grid.draw(g1)
The second draws two charts: p1 is your bar plot, and p2 contains the labels only. The trick is to get the x-axes in the two charts to be the same. Then, plot panels are extracted from p2, and placed into a p1, but into a new row just below p1's plot panel.
library(ggplot2)
library(magrittr)
mydata = data.frame(expand.grid(Tag = c('A', 'B', 'C'),
Year = 2010:2011,PNo = paste0("X-", 1:4)),Value = round(runif(24, 1, 20)))
mydata$dist = ifelse(mydata$Tag == 'A', 0, ifelse(mydata$Tag == 'B', 2, 7))
# The bar plot
p1 = mydata %>% ggplot(aes(x = dist, y = Value, fill = factor(Year))) +
geom_bar(stat = 'summary', position = 'dodge',fun.y = 'mean', width = 1) +
facet_wrap(~PNo, ncol = 2) +
theme(axis.text.x = element_blank(), axis.ticks.x = element_blank())
# To get the range of x values -
# so that the extent of the x-axis in p1 and in the following p2 are the same
gd = ggplot_build(p1)
xrange = gd$layout$panel_params[[1]]$x.range # xrange used in p2 (see below)
# Plot with labels (A, B, and C) only
p2 = mydata %>% ggplot(aes(x = dist, y = Value)) +
facet_wrap(~PNo, ncol = 2) +
geom_label(aes(x = dist, y = 0, label = Tag), size = 6, inherit.aes = F, color = 'red') +
### geom_text(aes(x = dist, y = 0, label = Tag), size=6, color = 'red') + ### Alternative style for labels
scale_x_continuous(lim = xrange, expand = c(0,0)) +
theme_bw() +
theme(panel.grid = element_blank(),
panel.border = element_rect(colour = NA))
# Grab a plot panel from p2
g2 = ggplotGrob(p2)
panels = subset(g2$layout, grepl("panel", g2$layout$name), t:r)
panels = subset(panels, t == min(t))
g2 = g2[unique(panels$t), min(panels$l):max(panels$r)]
# Add a row to p1 to take the plot panels
library(gtable)
library(grid)
g1 <- ggplotGrob(p1)
pos = max(subset(g1$layout, grepl("panel", g1$layout$name), t))
g1 = gtable_add_rows(g1, height = unit(2, "lines"), pos = pos)
# Add the panel (g2) to the new row
g1 = gtable_add_grob(g1,g2, t = pos + 1, l = min(panels$l), r = max(panels$r))
# Draw the chart
grid.newpage()
grid.draw(g1)
I tried to solve the problem myself but was facing some issue. I posted another question on SO here. Together the answer and question solves this question to some extent. Here is a possible solution.
p <- mydata %>% ggplot(aes(x = dist,y = Value,fill = factor(Year))) +geom_bar(stat='summary',position = 'dodge',fun.y='mean',width = 1) +
facet_wrap(~PNo,ncol=2) +
theme(axis.text.x = element_blank(),axis.ticks.x = element_blank()) +
geom_label(data = mydata %>% dplyr::filter(PNo %in% c('X-3','X-4')),aes(x = dist,y=0,label = Tag),size=6,inherit.aes=F,color = 'red')
library(grid)
gt <- ggplot_gtable(ggplot_build(p))
gt$layout$clip[grep("panel-2-\\d+", gt$layout$name)] <- "off"
grid.draw(gt)

ggplot2: manually add a legend

How can I map any (unrelated) legend to an existing ggplot?
Disclaimer: please don't hate me. I know the best way to create a legend with 'ggplot2' is to map your data right and I do it 99% of the time. Here however I am asking for something that in general can give me any legend I want.
As an example I have a plot that looks somewhat like this:
created from this code:
set.seed(42)
temp1 = cbind.data.frame(begin = rnorm(10, 0, 1), end = rnorm(10, 2, 1), y1 = 1:10, y2 = 1:10, id = as.character(1:10))
temp2 = cbind.data.frame(x = 0:2, y = 1:3*2)
temp3 = cbind.data.frame(x = seq(0.5, 1.5, 0.33))
temp = c()
plot1 = ggplot(data = temp, aes(x = x)) +
geom_vline(data = temp3, aes(xintercept = x), color = "red", linetype = "longdash") +
geom_segment(data = temp1, aes(y = y1, yend = y2, x = begin, xend = end, color = id)) +
geom_point(data = temp2, aes(x = x, y = y), shape = 4, size = 4) +
scale_color_discrete(guide = F)
plot1
and I want to add a legend that contains:
a red, longdashed vertical line called "l1"
a black, solid horizontal line called "l2"
a green filled block called "l3"
ideally I would produce that somewhat like this (pseudo-code ahead):
plot2 = plot1 + guide(elements = list(list(type = "line", color = "red", linetype = "longdash", direction = "vertical", label = "l1"), list(type = "line", label = "l2"), list(type = "rect", fill = "green", label = "l3"))
my best guess how to approach this would be to create some auxiliary pseudo-data temp that is plotted/mapped somewhere invisible on the plot and then used to create the legend, but I was not successful in getting anything like this to plot me a legend.
Once more, the idea is how can I add any unrelated legend to an existing plot, i.e. without clever mapping of the original data to the plot variables?
A legend can be constructed from scratch: use grid to construct the elements of legend; then use gtable to position the elements within the legend, and the legend within the plot. This is a bit crude, but gives the general idea.
set.seed(42)
temp1 = cbind.data.frame(begin = rnorm(10, 0, 1), end = rnorm(10, 2, 1), y1 = 1:10, y2 = 1:10, id = as.character(1:10))
temp2 = cbind.data.frame(x = 0:2, y = 1:3*2)
temp3 = cbind.data.frame(x = seq(0.5, 1.5, 0.33))
temp = c()
library(ggplot2)
library(grid)
library(gtable)
plot1 = ggplot(data = temp, aes(x = x)) +
geom_vline(data = temp3, aes(xintercept = x), color = "red", linetype = "longdash") +
geom_segment(data = temp1, aes(y = y1, yend = y2, x = begin, xend = end, color = id)) +
geom_point(data = temp2, aes(x = x, y = y), shape = 4, size = 4) +
scale_color_discrete(guide = F)
# Construct the six grobs - three symbols and three labels
L1 = linesGrob(x = unit(c(.5, .5), "npc"), y = unit(c(.25, .75), "npc"),
gp = gpar(col = "red", lty = "longdash"))
L2 = linesGrob(x = unit(c(.25, .75), "npc"), y = unit(c(.5, .5), "npc"))
L3 = rectGrob(height = .5, width = .5, gp = gpar(fill = "green", col = NA))
T1 = textGrob("l1", x = .2, just = "left")
T2 = textGrob("l2", x = .2, just = "left")
T3 = textGrob("l3", x = .2, just = "left")
# Construct a gtable - 2 columns X 4 rows
leg = gtable(width = unit(c(1,1), "cm"), height = unit(c(1,1,1,1), "cm"))
leg = gtable_add_grob(leg, rectGrob(gp = gpar(fill = NA, col = "black")), t=2,l=1,b=4,r=2)
# Place the six grob into the table
leg = gtable_add_grob(leg, L1, t=2, l=1)
leg = gtable_add_grob(leg, L2, t=3, l=1)
leg = gtable_add_grob(leg, L3, t=4, l=1)
leg = gtable_add_grob(leg, T1, t=2, l=2)
leg = gtable_add_grob(leg, T2, t=3, l=2)
leg = gtable_add_grob(leg, T3, t=4, l=2)
# Give it a title (if needed)
leg = gtable_add_grob(leg, textGrob("Legend"), t=1, l=1, r=2)
# Get the ggplot grob for plot1
g = ggplotGrob(plot1)
# Get the position of the panel,
# add a column to the right of the panel,
# put the legend into that column,
# and then add another spacing column
pos = g$layout[grepl("panel", g$layout$name), c('t', 'l')]
g = gtable_add_cols(g, sum(leg$widths), pos$l)
g = gtable_add_grob(g, leg, t = pos$t, l = pos$l + 1)
g = gtable_add_cols(g, unit(6, "pt"), pos$l)
# Draw it
grid.newpage()
grid.draw(g)

R how to add facet labels for pyramid like plot in ggplot2

I have created pyramid like plot and I want to add labels for each side of the plot (something like facet labels).
My data:
dt <- data.frame(Answer = factor(x = rep(x = c(1:3), times = 2),
labels = c("Yes", "No", "Maybe")),
Gender = factor(x = rep(x = c(1:2), each = 3),
labels = c("Female", "Male")),
Prc = c(74.4, 25.0, 0.6, 61.3, 35.5, 3.2),
label = c("74.4%", "25.0%", "0.6%", "61.3%", "35.5%", "3.2%"))
My plot:
My code for plot generation:
xmi <- -70
xma <- 80
library(ggplot2)
ggplot(data = dt, aes(x = Answer, fill = Gender)) +
geom_bar(stat = "identity", subset = .(Gender == "Female"), aes(y = Prc)) +
geom_text(subset = .(Gender == "Female"), aes(y = Prc, label = label), size = 4, hjust = -0.1) +
geom_bar(stat = "identity", subset = .(Gender == "Male"), aes(y=Prc * (-1)) ) +
geom_text(subset = .(Gender == "Male"), aes(y = Prc * (-1), label = label), size = 4, hjust = 1) +
scale_y_continuous(limits = c(xmi, xma), breaks=seq(xmi, xma,10),labels=abs(seq(xmi, xma,10))) +
theme(axis.text = element_text(colour = "black"),
plot.title = element_text(lineheight=.8) ) +
coord_flip() +
annotate("text", x = 3.3, y = -50, label = "Male", fontfacet = "bold") +
annotate("text", x = 3.3, y = 50, label = "Female", fontfacet = "bold") +
ylab("") + xlab("") + guides(fill=FALSE)
rm(xmi, xma)
And the facet labels labels example:
And the question is:
1. How to add facet labels to the pyramid like plot;
OR
2. Maybe there are the better way to make pyramid like plots.
A few possibilities. The first two construct a strip (i.e., facet labels) from scratch. The two differ in the way they position the strip grob. The third is a pyramid plot, similar to the one constructed here, but with a little more tidying up.
library(ggplot2)
dt <- data.frame(Answer = factor(x = rep(x = c(1:3), times = 2),
labels = c("Yes", "No", "Maybe")),
Gender = factor(x = rep(x = c(1:2), each = 3),
labels = c("Female", "Male")),
Prc = c(74.4, 25.0, 0.6, 61.3, 35.5, 3.2),
label = c("74.4%", "25.0%", "0.6%", "61.3%", "35.5%", "3.2%"))
xmi <- -100
xma <- 100
p = ggplot(data = dt, aes(x = Answer, fill = Gender)) +
geom_bar(stat = "identity", data = subset(dt, Gender == "Female"), aes(y = Prc)) +
geom_text(data = subset(dt, Gender == "Female"), aes(y = Prc, label = label),
size = 4, hjust = -0.1) +
geom_bar(stat = "identity", data = subset(dt, Gender == "Male"), aes(y=Prc * (-1)) ) +
geom_text(data = subset(dt, Gender == "Male"), aes(y = Prc * (-1), label = label),
size = 4, hjust = 1.1) +
scale_y_continuous(limits = c(xmi, xma), breaks = seq(xmi, xma, 10), labels = abs(seq(xmi, xma, 10))) +
theme(axis.text = element_text(colour = "black")) +
coord_flip() +
ylab("") + xlab("") + guides(fill = FALSE) +
theme(plot.margin = unit(c(2, 1, 1, 1), "lines"))
## Method 1
# Construct the strip
library(grid)
strip = gTree(name = "Strip",
children = gList(
rectGrob(gp = gpar(col = NA, fill = "grey85")),
textGrob("Female", x = .75, gp = gpar(fontsize = 8.8, col = "grey10")),
textGrob("Male", x = .25, gp = gpar(fontsize = 8.8, col = "grey10")),
linesGrob(x = .5, gp = gpar(col = "grey95"))))
# Position strip using annotation_custom
p1 = p + annotation_custom(strip, xmin = Inf, xmax = 3.75, ymax = Inf, ymin = -Inf)
g = ggplotGrob(p1)
# The strip is positioned outside the panel,
# therefore turn off clipping to the panel.
g$layout[g$layout$name=='panel', "clip"] = "off"
# Draw it
grid.newpage()
grid.draw(g)
## Method 2
# Construct the strip
# Note the viewport; in particular its position and justification
library(gtable)
fontsize = 8.8
gp = gpar(fontsize = fontsize, col = "grey10")
textGrobF = textGrob("Female", x = .75, gp = gp)
textGrobM = textGrob("Male", x = .25, gp = gp)
strip = gTree(name = "Strip",
vp = viewport(y = 1, just = "bottom", height = unit(2.5, "grobheight", textGrobF)),
children = gList(
rectGrob(gp = gpar(col = NA, fill = "grey85")),
textGrobF,
textGrobM,
linesGrob(x = .5, gp = gpar(col = "grey95"))))
g = ggplotGrob(p)
# Position strip using the gtable function, gtable_add_grob
# Strip is positioned in the plot panel,
# but because of the justification of strip's viewport,
# the strip is drawn outside the panel
# First, get the panel's position in the layout
pos = g$layout[grepl("panel", g$layout$name), c("t","l")]
g = gtable_add_grob(g, strip, t = pos$t, l = pos$l, clip = "off")
grid.newpage()
grid.draw(g)
## Method 3
# Pyramid plot
library(ggplot2)
library(scales)
library(stringr)
library(gtable)
library(grid)
df = dt
# Common theme
theme = theme(panel.grid.minor = element_blank(),
panel.grid.major = element_blank(),
axis.text.y = element_blank(),
axis.title.y = element_blank(),
plot.title = element_text(size = 10, hjust=0.5))
#### 1. "male" plot - to appear on the right
ggM <- ggplot(data = subset(df, Gender == 'Male'), aes(x = Answer)) +
geom_bar(aes(y = .01*Prc), stat = "identity", fill = "skyblue", width = .5) +
geom_text(data = subset(dt, Gender == "Male"), aes(y = .01*Prc, label = label), hjust = -.1, size = 4) +
scale_y_continuous('', limits = c(0, 1), expand = c(0, 0), labels = percent) +
labs(x = NULL) +
ggtitle("Male") +
coord_flip() + theme +
theme(plot.margin= unit(c(1, 1, 0, 0), "lines"))
# get ggplot grob
gtM <- ggplotGrob(ggM)
#### 2. "female" plot - to appear on the left -
# reverse the 'Percent' axis using trans = "reverse"
ggF <- ggplot(data = subset(df, Gender == 'Female'), aes(x = Answer)) +
geom_bar(aes(y = .01*Prc), stat = "identity", fill = "salmon", width = .5) +
geom_text(data = subset(dt, Gender == "Female"), aes(y = .01*Prc, label = label), hjust = 1.1, size = 4) +
scale_y_continuous('', limits = c(1, 0), trans = "reverse", expand = c(0, 0), labels = percent) +
labs(x = NULL) +
ggtitle("Female") +
coord_flip() + theme +
theme(plot.margin= unit(c(1, 0, 0, 1), "lines"))
# get ggplot grob
gtF <- ggplotGrob(ggF)
## Swap the tick marks to the right side of the plot panel
# Get the row number of the left axis in the layout
rn <- which(gtF$layout$name == "axis-l")
# Extract the axis (tick marks and axis text)
axis.grob <- gtF$grobs[[rn]]
axisl <- axis.grob$children[[2]] # Two children - get the second
# axisl # Note: two grobs - text and tick marks
# Get the tick marks - NOTE: tick marks are second
yaxis = axisl$grobs[[2]]
yaxis$x = yaxis$x - unit(1, "npc") + unit(2.75, "pt") # Reverse them
# Add them to the right side of the panel
# Add a column to the gtable
gtF <- gtable_add_cols(gtF, gtF$widths[3], length(gtF$widths) - 1)
# Add the grob
pos = gtF$layout[grepl("panel", gtF$layout$name), "t"]
gtF <- gtable_add_grob(gtF, yaxis, t = pos, length(gtF$widths) - 1)
# Remove original left axis
gtF = gtF[,-c(2,3)]
#### 3. Answer labels - create a plot using geom_text - to appear down the middle
fontsize = 3
ggC <- ggplot(data = subset(df, Gender == 'Male'), aes(x=Answer)) +
geom_bar(stat = "identity", aes(y = 0)) +
geom_text(aes(y = 0, label = Answer), size = fontsize) +
ggtitle("Answer") +
coord_flip() + theme_bw() + theme +
theme(panel.border = element_rect(colour = NA))
# get ggplot grob
gtC <- ggplotGrob(ggC)
# Get the title
Title = gtC$grobs[[which(gtC$layout$name == "title")]]
# Get the plot panel
gtC = gtC$grobs[[which(gtC$layout$name == "panel")]]
#### 4. Arrange the components
## First, combine "female" and "male" plots
gt = cbind(gtF, gtM, size = "first")
## Second, add the labels (gtC) down the middle
# Add column to gtable
maxlab = df$Answer[which(str_length(df$Answer) == max(str_length(df$Answer)))]
gt = gtable_add_cols(gt, sum(unit(1, "grobwidth", textGrob(maxlab, gp = gpar(fontsize = fontsize*72.27/25.4))), unit(5, "mm")),
pos = length(gtF$widths))
# Add the Answer grob
gt = gtable_add_grob(gt, gtC, t = pos, l = length(gtF$widths) + 1)
# Add the title; ie the label 'Answer'
gt = gtable_add_grob(gt, Title, t = 3, l = length(gtF$widths) + 1)
### 5. Draw the plot
grid.newpage()
grid.draw(gt)

Resources