Subgroup axes ggplot2 and axis limits - r

Follow up to:
Subgroup axes ggplot2 similar to Excel PivotChart
ggplot2 multiple sub groups of a bar chart
R version 3.1.1 (2014-07-10) Platform: i386-w64-mingw32/i386 (32-bit)
I am working on a plot with ggplot2. The aim is to tweak the axis into a look similar to Excels famous pivot graphs. I know, how I can achieve the look I want, but as soon as I use axis limits, the code is not sufficient any more.
Data:
library(reshape2)
library(ggplot2)
library(grid)
df=data.frame(year=rep(2010:2014,each=4),
quarter=rep(c("Q1","Q2","Q3","Q4"),5),
da=c(46,47,51,50,56.3,53.6,55.8,58.9,61.0,63,58.8,62.5,59.5,61.7,60.6,63.9,68.4,62.2,62,70.4))
df.m <- melt(data = df,id.vars = c("year","quarter"))
g1 <- ggplot(data = df.m, aes(x = interaction(quarter,year), y = value, group = variable)) +
geom_area(fill = "red")+
coord_cartesian(ylim = c(0, 75)) +
annotate(geom = "text", x = seq_len(nrow(df)), y = -1.5, label = df$quarter, size = 2, color = "gray48") +
annotate(geom = "text", x = 2.5 + 4 * (0:4), y = -3, label = unique(df$year), size = 3, color ="gray48") +
theme_grey(base_size = 10)+
theme(line = element_line(size = 0.2),
axis.title.x = element_blank(),
axis.text.x = element_blank(),
legend.position= "none")
#remove clipping of x axis labels
g2 <- ggplot_gtable(ggplot_build(g1))
g2$layout$clip[g2$layout$name == "panel"] <- "off"
grid.draw(g2)
png(filename = "test.png",width = 14/2.54,height = 6/2.54, units = "in",res = 300)
grid.draw(g2)
dev.off()
The plot is fine and the axis lables are as wished. But as soon as you change the limits of the y axis everything is messed up.
I hope you have an idea, how to solve my problem!

Actually, it is plotting exactly what you are asking for. Check ?geom_area, and you will note that the minimum y is 0. So when you turn off clipping, ggplot will show as much of the area as it can within the limits of the lower margin. Instead use geom_ribbon(). It has ymax and ymin. Also, you need to take care setting the y-coordinates in the two annotate() functions.
library(reshape2)
library(ggplot2)
library(grid)
df=data.frame(year=rep(2010:2014,each=4),
quarter=rep(c("Q1","Q2","Q3","Q4"),5),
da=c(46,47,51,50,56.3,53.6,55.8,58.9,61.0,63,58.8,62.5,59.5,61.7,60.6,63.9,68.4,62.2,62,70.4))
df.m <- melt(data = df,id.vars = c("year","quarter"))
ymin <- 40
g1 <- ggplot(data = df.m, aes(x = interaction(quarter,year), ymax = value, group = variable)) +
geom_ribbon(aes(ymin=ymin), fill = "red")+
coord_cartesian(ylim = c(ymin, 75)) +
annotate(geom = "text", x = seq_len(nrow(df)), y = 37.5, label = df$quarter, size = 2, color = "gray48") +
annotate(geom = "text", x = 2.5 + 4 * (0:4), y = 36.5, label = unique(df$year), size = 3, color ="gray48") +
theme_grey(base_size = 10)+
theme(line = element_line(size = 0.2),
axis.title.x = element_blank(),
axis.text.x = element_blank(),
legend.position= "none",
plot.margin = unit(c(1,1,3,1), "lines")) # The bottom margin is exaggerated a little
# turn off clipping of the panel
g2 <- ggplotGrob(g1)
g2$layout$clip[g2$layout$name == "panel"] <- "off"
grid.draw(g2)

Related

How can I make a discontinuous axis in R with ggplot2?

I have a dataframe (dat) with two columns 1) Month and 2) Value. I would like to highlight that the x-axis is not continuous in my boxplot by interrupting the x-axis with two angled lines on the x-axis that are empty between the angled lines.
Example Data and Boxplot
library(ggplot2)
set.seed(321)
dat <- data.frame(matrix(ncol = 2, nrow = 18))
x <- c("Month", "Value")
colnames(dat) <- x
dat$Month <- rep(c(1,2,3,10,11,12),3)
dat$Value <- rnorm(18,20,2)
ggplot(data = dat, aes(x = factor(Month), y = Value)) +
geom_boxplot() +
labs(x = "Month") +
theme_bw() +
theme(panel.grid = element_blank(),
text = element_text(size = 16),
axis.text.x = element_text(size = 14, color = "black"),
axis.text.y = element_text(size = 14, color = "black"))
The ideal figure would look something like below. How can I make this discontinuous axis in ggplot?
You could make use of the extended axis guides in the ggh4x package. Alas, you won't easily be able to create the "separators" without a hack similar to the one suggested by user Zhiqiang Wang
guide_axis_truncated accepts vectors to define lower and upper trunks. This also works for units, by the way, then you have to pass the vector inside the unit function (e.g., trunc_lower = unit(c(0,.45), "npc") !
library(ggplot2)
library(ggh4x)
set.seed(321)
dat <- data.frame(matrix(ncol = 2, nrow = 18))
x <- c("Month", "Value")
colnames(dat) <- x
dat$Month <- rep(c(1,2,3,10,11,12),3)
dat$Value <- rnorm(18,20,2)
# this is to make it slightly more programmatic
x1end <- 3.45
x2start <- 3.55
p <-
ggplot(data = dat, aes(x = factor(Month), y = Value)) +
geom_boxplot() +
labs(x = "Month") +
theme_classic() +
theme(axis.line = element_line(colour = "black"))
p +
guides(x = guide_axis_truncated(
trunc_lower = c(-Inf, x2start),
trunc_upper = c(x1end, Inf)
))
Created on 2021-11-01 by the reprex package (v2.0.1)
The below is taking user Zhiqiang Wang's hack a step further. You will see I am using simple trigonometry to calculate the segment coordinates. in order to make the angle actually look as it is defined in the function, you would need to set coord_equal.
# a simple function to help make the segments
add_separators <- function(x, y = 0, angle = 45, length = .1){
add_y <- length * sin(angle * pi/180)
add_x <- length * cos(angle * pi/180)
## making the list for your segments
myseg <- list(x = x - add_x, xend = x + add_x,
y = rep(y - add_y, length(x)), yend = rep(y + add_y, length(x)))
## this function returns an annotate layer with your segment coordinates
annotate("segment",
x = myseg$x, xend = myseg$xend,
y = myseg$y, yend = myseg$yend)
}
# you will need to set limits for correct positioning of your separators
# I chose 0.05 because this is the expand factor by default
y_sep <- min(dat$Value) -0.05*(min(dat$Value))
p +
guides(x = guide_axis_truncated(
trunc_lower = c(-Inf, x2start),
trunc_upper = c(x1end, Inf)
)) +
add_separators(x = c(x1end, x2start), y = y_sep, angle = 70) +
# you need to set expand to 0
scale_y_continuous(expand = c(0,0)) +
## to make the angle look like specified, you would need to use coord_equal()
coord_cartesian(clip = "off", ylim = c(y_sep, NA))
I think it is possible to get what you want. It may take some work.
Here is your graph:
library(ggplot2)
set.seed(321)
dat <- data.frame(matrix(ncol = 2, nrow = 18))
x <- c("Month", "Value")
colnames(dat) <- x
dat$Month <- rep(c(1,2,3,10,11,12),3)
dat$Value <- rnorm(18,20,2)
p <- ggplot(data = dat, aes(x = factor(Month), y = Value)) +
geom_boxplot() +
labs(x = "Month") +
theme_bw() +
theme(panel.grid = element_blank(),
text = element_text(size = 16),
axis.text.x = element_text(size = 14, color = "black"),
axis.text.y = element_text(size = 14, color = "black"))
Here is my effort:
p + annotate("segment", x = c(3.3, 3.5), xend = c(3.6, 3.8), y = c(14, 14), yend = c(15, 15))+
coord_cartesian(clip = "off", ylim = c(15, 25))
Get something like this:
If you want to go further, it may take several tries to get it right:
p + annotate("segment", x = c(3.3, 3.5), xend = c(3.6, 3.8), y = c(14, 14), yend = c(15, 15))+
annotate("segment", x = c(0, 3.65), xend = c(3.45, 7), y = c(14.55, 14.55), yend = c(14.55, 14.55)) +
coord_cartesian(clip = "off", ylim = c(15, 25)) +
theme_classic()+
theme(axis.line.x = element_blank())
Just replace axis with two new lines. This is a rough idea, it may take some time to make it perfect.
You could use facet_wrap. If you assign the first 3 months to one group, and the other months to another, then you can produce two plots that are side by side and use a single y axis.
It's not exactly what you want, but it will show the data effectively, and highlights the fact that the x axis is not continuous.
dat$group[dat$Month %in% c("1", "2", "3")] <- 1
dat$group[dat$Month %in% c("10", "11", "12")] <- 2
ggplot(data = dat, aes(x = factor(Month), y = Value)) +
geom_boxplot() +
labs(x = "Month") +
theme_bw() +
theme(panel.grid = element_blank(),
text = element_text(size = 16),
axis.text.x = element_text(size = 14, color = "black"),
axis.text.y = element_text(size = 14, color = "black")) +
facet_wrap(~group, scales = "free_x")
* Differences in the plot are likely due to using different versions of R where the set.seed gives different result

ggplot: Add annotations using separate data above faceted chart

I'm trying to add set of markers with text above the top of a faceted chart to indicate certain points of interest in the value of x. Its important that they appear in the right position left to right (as per the main scale), including when the overall ggplot changes size.
Something like this...
However, I'm struggling to:
place it in the right vertical position (above the facets). In my
reprex below (a simplified version of the original), I tried using a
value of the factor (Merc450 SLC), but this causes issues such as adding that to
every facet including when it is not part of that facet and doesn't
actually go high enough. I also tried converting the factor to a number using as.integer, but this causes every facet to include all factor values, when they obviously shouldn't
apply to the chart as a whole, not each
facet
Note that in the full solution, the marker x values are independent of the main data.
I have tried using cowplot to draw it separately and overlay it, but that seems to:
affect the overall scale of the main plot, with the facet titles on the right being cropped
is not reliable in placing the markers at the exact location along the x scale
Any pointers welcome.
library(tidyverse)
mtcars2 <- rownames_to_column(mtcars, var = "car") %>%
mutate(make = stringr::word(car, 1)) %>%
filter(make >= "m" & make < "n")
markers <- data.frame(x = c(max(mtcars2$mpg), rep(runif(nrow(mtcars2), 1, max(mtcars2$mpg))), max(mtcars2$mpg))) %>%
mutate(name = paste0("marker # ", round(x)))
ggplot(mtcars2, aes()) +
# Main Plot
geom_tile(aes(x = mpg, y = car, fill = cyl), color = "white") +
# Add Markers
geom_point(data = markers, aes(x = x, y = "Merc450 SLC"), color = "red") +
# Marker Labels
geom_text(data = markers, aes(x = x, "Merc450 SLC",label = name), angle = 45, size = 2.5, hjust=0, nudge_x = -0.02, nudge_y = 0.15) +
facet_grid(make ~ ., scales = "free", space = "free") +
theme_minimal() +
theme(
# Facets
strip.background = element_rect(fill="Gray90", color = "white"),
panel.background = element_rect(fill="Gray95", color = "white"),
panel.spacing.y = unit(.7, "lines"),
plot.margin = margin(50, 20, 20, 20)
)
Perhaps draw two separate plots and assemble them together with patchwork:
library(patchwork)
p1 <- ggplot(markers, aes(x = x, y = 0)) +
geom_point(color = 'red') +
geom_text(aes(label = name),
angle = 45, size = 2.5, hjust=0, nudge_x = -0.02, nudge_y = 0.02) +
scale_y_continuous(limits = c(-0.01, 0.15), expand = c(0, 0)) +
theme_minimal() +
theme(axis.text = element_blank(),
axis.title = element_blank(),
panel.grid = element_blank())
p2 <- ggplot(mtcars2, aes(x = mpg, y = car, fill = cyl)) +
geom_tile(color = "white") +
facet_grid(make ~ ., scales = "free", space = "free") +
theme_minimal() +
theme(
strip.background = element_rect(fill="Gray90", color = "white"),
panel.background = element_rect(fill="Gray95", color = "white"),
panel.spacing.y = unit(.7, "lines")
)
p1/p2 + plot_layout(heights = c(1, 9))
It required some workaround with plot on different plot and using cowplot alignment function to align them on the same axis. Here is a solution
library(tidyverse)
library(cowplot)
# define a common x_axis to ensure that the plot are on same scales
# This may not needed as cowplot algin_plots also adjust the scale however
# I tended to do this extra step to ensure.
x_axis_common <- c(min(mtcars2$mpg, markers$x) * .8,
max(mtcars2$mpg, markers$x) * 1.1)
# Plot contain only marker
plot_marker <- ggplot() +
geom_point(data = markers, aes(x = x, y = 0), color = "red") +
# Marker Labels
geom_text(data = markers, aes(x = x, y = 0,label = name),
angle = 45, size = 2.5, hjust=0, nudge_x = 0, nudge_y = 0.001) +
# using coord_cartesian to set the zone of plot for some scales
coord_cartesian(xlim = x_axis_common,
ylim = c(-0.005, 0.03), expand = FALSE) +
# using theme_nothing from cow_plot which remove all element
# except the drawing
theme_nothing()
# main plot with facet
main_plot <- ggplot(mtcars2, aes()) +
# Main Plot
geom_tile(aes(x = mpg, y = car, fill = cyl), color = "white") +
coord_cartesian(xlim = x_axis_common, expand = FALSE) +
# Add Markers
facet_grid(make ~ ., scales = "free_y", space = "free") +
theme_minimal() +
theme(
# Facets
strip.background = element_rect(fill="Gray90", color = "white"),
panel.background = element_rect(fill="Gray95", color = "white"),
panel.spacing.y = unit(.7, "lines"),
plot.margin = margin(0, 20, 20, 20)
)
Then align the plot and plot them using cow_plot
# align the plots together
temp <- align_plots(plot_marker, main_plot, axis = "rl",
align = "hv")
# plot them with plot_grid also from cowplot - using rel_heights for some
# adjustment
plot_grid(temp[[1]], temp[[2]], ncol = 1, rel_heights = c(1, 8))
Created on 2021-05-03 by the reprex package (v2.0.0)

Adding Annotations to coord_polar

I'd like to place annotations in the bottom left corner of a plot with polar coordinates.
Is there a way of doing this by using coordinates that are not part of the circular coordinate system (as shown, which doesn't allow me to place them far out) but a normal x and y system?
I don't need the axes to show but have left them in to help show what's going on.
library(tidyverse)
iris2 <- iris %>%
mutate(id = row_number())
ggplot(iris2) +
theme_minimal() +
theme(
legend.position = "none",
axis.title = element_blank()
) +
coord_polar() +
geom_segment(aes(x = id, xend = id,
y = 10,
yend = 10 + Sepal.Length),
size = 1, inherit.aes = FALSE) +
annotate(geom="text", x = 90, y = 10, label="Annotation",
color="Red") +
annotate(geom="text", x = 90, y = 15, label="Annotation",
color="Blue")
You could use the cowplot package to make the desired plot. You can make two text_grob that will be the text labels and then add them on the lower and left side of the plot you already have (appears as p1 in the following code).
library(cowplot)
#X axis text
x <- textGrob(label = "X text",
gp=gpar(fontsize=12,
fontface = "bold"),
hjust = 0.1)
#Y axis text
y <- textGrob(label = "Y text",
#rotate it 90 degrees
rot = 90,
gp=gpar(fontsize=12,
fontface = "bold"))
#Get X axis text and p1 together in a 2 row array
p2<-plot_grid(p1,x,
nrow = 2,
rel_widths = c(4,1),
rel_heights = c(20,1))
#Get Y axis text and p2 together in 2 column array
p3<-plot_grid(y,p2,
ncol = 2,
rel_widths = c(1,20),
rel_heights = c(1,4))
p3
Based on #Aexman's helpful comment to try cowplot, I did and came up with the following. It means you can add two layers together, the second with an independent coordinate system.
Coordinates are between 0 and 1 for both axes, relative the bottom left corner. Absolute positioning is also available.
library(tidyverse)
library(cowplot)
iris2 <- iris %>%
mutate(id = row_number())
g <- ggplot(iris2) +
theme_minimal() +
theme(
legend.position = "none",
axis.title = element_blank()
) +
coord_polar() +
geom_segment(aes(x = id, xend = id,
y = 10,
yend = 10 + Sepal.Length),
size = 1, inherit.aes = FALSE)
ggdraw(g) +
draw_label("Annotation", x = 0.05, y = 0.05, hjust = 0, vjust = 0,
fontfamily = "", fontface = "plain", color = "black", size = 14,
colour = "Red")

How can I add a methodological note to a plot ? [duplicate]

I am trying to display some information about the data below the plot created in ggplot2. I would like to plot the N variable using the X axis coordinate of the plot but the Y coordinate needs to be 10% from the bottom of the screen . In fact, the desired Y coordinates are already in the data frame as y_pos variable.
I can think of 3 approaches using ggplot2:
1) Create an empty plot below the actual plot, use the same scale and then use geom_text to plot the data over the blank plot. This approach sort of works but is extremely complicated.
2) Use geom_text to plot the data but somehow use y coordinate as percent of the screen (10%). This would force the numbers to be displayed below the plot. I can't figure out the proper syntax.
3) Use grid.text to display the text. I can easily set it at the 10% from the bottom of the screen but I can't figure how set the X coordindate to match the plot. I tried to use grconvert to capture the initial X position but could not get that to work as well.
Below is the basic plot with the dummy data:
graphics.off() # close graphics windows
library(car)
library(ggplot2) #load ggplot
library(gridExtra) #load Grid
library(RGraphics) # support of the "R graphics" book, on CRAN
#create dummy data
test= data.frame(
Group = c("A", "B", "A","B", "A", "B"),
x = c(1 ,1,2,2,3,3 ),
y = c(33,25,27,36,43,25),
n=c(71,55,65,58,65,58),
y_pos=c(9,6,9,6,9,6)
)
#create ggplot
p1 <- qplot(x, y, data=test, colour=Group) +
ylab("Mean change from baseline") +
geom_line()+
scale_x_continuous("Weeks", breaks=seq(-1,3, by = 1) ) +
opts(
legend.position=c(.1,0.9))
#display plot
p1
The modified gplot below displays numbers of subjects, however they are displayed WITHIN the plot. They force the Y scale to be extended. I would like to display these numbers BELOW the plot.
p1 <- qplot(x, y, data=test, colour=Group) +
ylab("Mean change from baseline") +
geom_line()+
scale_x_continuous("Weeks", breaks=seq(-1,3, by = 1) ) +
opts( plot.margin = unit(c(0,2,2,1), "lines"),
legend.position=c(.1,0.9))+
geom_text(data = test,aes(x=x,y=y_pos,label=n))
p1
A different approach of displaying the numbers involves creating a dummy plot below the actual plot. Here is the code:
graphics.off() # close graphics windows
library(car)
library(ggplot2) #load ggplot
library(gridExtra) #load Grid
library(RGraphics) # support of the "R graphics" book, on CRAN
#create dummy data
test= data.frame(
group = c("A", "B", "A","B", "A", "B"),
x = c(1 ,1,2,2,3,3 ),
y = c(33,25,27,36,43,25),
n=c(71,55,65,58,65,58),
y_pos=c(15,6,15,6,15,6)
)
p1 <- qplot(x, y, data=test, colour=group) +
ylab("Mean change from baseline") +
opts(plot.margin = unit(c(1,2,-1,1), "lines")) +
geom_line()+
scale_x_continuous("Weeks", breaks=seq(-1,3, by = 1) ) +
opts(legend.position="bottom",
legend.title=theme_blank(),
title.text="Line plot using GGPLOT")
p1
p2 <- qplot(x, y, data=test, geom="blank")+
ylab(" ")+
opts( plot.margin = unit(c(0,2,-2,1), "lines"),
axis.line = theme_blank(),
axis.ticks = theme_segment(colour = "white"),
axis.text.x=theme_text(angle=-90,colour="white"),
axis.text.y=theme_text(angle=-90,colour="white"),
panel.background = theme_rect(fill = "transparent",colour = NA),
panel.grid.minor = theme_blank(),
panel.grid.major = theme_blank()
)+
geom_text(data = test,aes(x=x,y=y_pos,label=n))
p2
grid.arrange(p1, p2, heights = c(8.5, 1.5), nrow=2 )
However, that is very complicated and would be hard to modify for different data. Ideally, I'd like to be able to pass Y coordinates as percent of the screen.
The current version (>2.1) has a + labs(caption = "text"), which displays an annotation below the plot. This is themeable (font properties,... left/right aligned). See https://github.com/hadley/ggplot2/pull/1582 for examples.
Edited opts has been deprecated, replaced by theme; element_blank has replaced theme_blank; and ggtitle() is used in place of opts(title = ...
Sandy- thank you so much!!!! This does exactly what I want. I do wish we could control the clipping in geom.text or geom.annotate.
I put together the following program if anybody else is interested.
rm(list = ls()) # clear objects
graphics.off() # close graphics windows
library(ggplot2)
library(gridExtra)
#create dummy data
test= data.frame(
group = c("Group 1", "Group 1", "Group 1","Group 2", "Group 2", "Group 2"),
x = c(1 ,2,3,1,2,3 ),
y = c(33,25,27,36,23,25),
n=c(71,55,65,58,65,58),
ypos=c(18,18,18,17,17,17)
)
p1 <- qplot(x=x, y=y, data=test, colour=group) +
ylab("Mean change from baseline") +
theme(plot.margin = unit(c(1,3,8,1), "lines")) +
geom_line()+
scale_x_continuous("Visits", breaks=seq(-1,3) ) +
theme(legend.position="bottom",
legend.title=element_blank())+
ggtitle("Line plot")
# Create the textGrobs
for (ii in 1:nrow(test))
{
#display numbers at each visit
p1=p1+ annotation_custom(grob = textGrob(test$n[ii]),
xmin = test$x[ii],
xmax = test$x[ii],
ymin = test$ypos[ii],
ymax = test$ypos[ii])
#display group text
if (ii %in% c(1,4)) #there is probably a better way
{
p1=p1+ annotation_custom(grob = textGrob(test$group[ii]),
xmin = 0.85,
xmax = 0.85,
ymin = test$ypos[ii],
ymax = test$ypos[ii])
}
}
# Code to override clipping
gt <- ggplot_gtable(ggplot_build(p1))
gt$layout$clip[gt$layout$name=="panel"] <- "off"
grid.draw(gt)
Updated opts() has been replaced with theme()
In the code below, a base plot is drawn, with a wider margin at the bottom of the plot. The textGrob is created, then inserted into the plot using annotation_custom(). Except the text is not visible because it is outside the plot panel - the output is clipped to the panel. But using baptiste's code from here, the clipping can be overrridden. The position is in terms of data units, and both text labels are centred.
library(ggplot2)
library(grid)
# Base plot
df = data.frame(x=seq(1:10), y = seq(1:10))
p = ggplot(data = df, aes(x = x, y = y)) + geom_point() + ylim(0,10) +
theme(plot.margin = unit(c(1,1,3,1), "cm"))
p
# Create the textGrobs
Text1 = textGrob(paste("Largest x-value is", round(max(df$x), 2), sep = " "))
Text2 = textGrob(paste("Mean = ", mean(df$x), sep = ""))
p1 = p + annotation_custom(grob = Text1, xmin = 4, xmax = 4, ymin = -3, ymax = -3) +
annotation_custom(grob = Text2, xmin = 8, xmax = 8, ymin = -3, ymax = -3)
p1
# Code to override clipping
gt <- ggplotGrob(p1)
gt$layout$clip[gt$layout$name=="panel"] <- "off"
grid.draw(gt)
Or, using grid functions to create and position the label.
p
grid.text((paste("Largest x-value is", max(df$x), sep = " ")),
x = unit(.2, "npc"), y = unit(.1, "npc"), just = c("left", "bottom"),
gp = gpar(fontface = "bold", fontsize = 18, col = "blue"))
Edit
Or, add text grob using gtable functions.
library(ggplot2)
library(grid)
library(gtable)
# Base plot
df = data.frame(x=seq(1:10), y = seq(1:10))
p = ggplot(data = df, aes(x = x, y = y)) + geom_point() + ylim(0,10)
# Construct the text grob
lab = textGrob((paste("Largest x-value is", max(df$x), sep = " ")),
x = unit(.1, "npc"), just = c("left"),
gp = gpar(fontface = "bold", fontsize = 18, col = "blue"))
gp = ggplotGrob(p)
# Add a row below the 2nd from the bottom
gp = gtable_add_rows(gp, unit(2, "grobheight", lab), -2)
# Add 'lab' grob to that row, under the plot panel
gp = gtable_add_grob(gp, lab, t = -2, l = gp$layout[gp$layout$name == "panel",]$l)
grid.newpage()
grid.draw(gp)
Actually the best answer and easiest solution is to use the cowplot package.
Version 0.5.0 of the cowplot package (on CRAN) handles ggplot2 subtitles using the add_sub function.
Use it like so:
diamondsCubed <-ggplot(aes(carat, price), data = diamonds) +
geom_point() +
scale_x_continuous(trans = cuberoot_trans(), limits = c(0.2, 3),
breaks = c(0.2, 0.5, 1, 2, 3)) +
scale_y_continuous(trans = log10_trans(), limits = c(350, 15000),
breaks = c(350, 1000, 5000, 10000, 15000)) +
ggtitle('Price log10 by Cube-Root of Carat') +
theme_xkcd()
ggdraw(add_sub(diamondsCubed, "This is an annotation.\nAnnotations can span multiple lines."))

Displaying text below the plot generated by ggplot2

I am trying to display some information about the data below the plot created in ggplot2. I would like to plot the N variable using the X axis coordinate of the plot but the Y coordinate needs to be 10% from the bottom of the screen . In fact, the desired Y coordinates are already in the data frame as y_pos variable.
I can think of 3 approaches using ggplot2:
1) Create an empty plot below the actual plot, use the same scale and then use geom_text to plot the data over the blank plot. This approach sort of works but is extremely complicated.
2) Use geom_text to plot the data but somehow use y coordinate as percent of the screen (10%). This would force the numbers to be displayed below the plot. I can't figure out the proper syntax.
3) Use grid.text to display the text. I can easily set it at the 10% from the bottom of the screen but I can't figure how set the X coordindate to match the plot. I tried to use grconvert to capture the initial X position but could not get that to work as well.
Below is the basic plot with the dummy data:
graphics.off() # close graphics windows
library(car)
library(ggplot2) #load ggplot
library(gridExtra) #load Grid
library(RGraphics) # support of the "R graphics" book, on CRAN
#create dummy data
test= data.frame(
Group = c("A", "B", "A","B", "A", "B"),
x = c(1 ,1,2,2,3,3 ),
y = c(33,25,27,36,43,25),
n=c(71,55,65,58,65,58),
y_pos=c(9,6,9,6,9,6)
)
#create ggplot
p1 <- qplot(x, y, data=test, colour=Group) +
ylab("Mean change from baseline") +
geom_line()+
scale_x_continuous("Weeks", breaks=seq(-1,3, by = 1) ) +
opts(
legend.position=c(.1,0.9))
#display plot
p1
The modified gplot below displays numbers of subjects, however they are displayed WITHIN the plot. They force the Y scale to be extended. I would like to display these numbers BELOW the plot.
p1 <- qplot(x, y, data=test, colour=Group) +
ylab("Mean change from baseline") +
geom_line()+
scale_x_continuous("Weeks", breaks=seq(-1,3, by = 1) ) +
opts( plot.margin = unit(c(0,2,2,1), "lines"),
legend.position=c(.1,0.9))+
geom_text(data = test,aes(x=x,y=y_pos,label=n))
p1
A different approach of displaying the numbers involves creating a dummy plot below the actual plot. Here is the code:
graphics.off() # close graphics windows
library(car)
library(ggplot2) #load ggplot
library(gridExtra) #load Grid
library(RGraphics) # support of the "R graphics" book, on CRAN
#create dummy data
test= data.frame(
group = c("A", "B", "A","B", "A", "B"),
x = c(1 ,1,2,2,3,3 ),
y = c(33,25,27,36,43,25),
n=c(71,55,65,58,65,58),
y_pos=c(15,6,15,6,15,6)
)
p1 <- qplot(x, y, data=test, colour=group) +
ylab("Mean change from baseline") +
opts(plot.margin = unit(c(1,2,-1,1), "lines")) +
geom_line()+
scale_x_continuous("Weeks", breaks=seq(-1,3, by = 1) ) +
opts(legend.position="bottom",
legend.title=theme_blank(),
title.text="Line plot using GGPLOT")
p1
p2 <- qplot(x, y, data=test, geom="blank")+
ylab(" ")+
opts( plot.margin = unit(c(0,2,-2,1), "lines"),
axis.line = theme_blank(),
axis.ticks = theme_segment(colour = "white"),
axis.text.x=theme_text(angle=-90,colour="white"),
axis.text.y=theme_text(angle=-90,colour="white"),
panel.background = theme_rect(fill = "transparent",colour = NA),
panel.grid.minor = theme_blank(),
panel.grid.major = theme_blank()
)+
geom_text(data = test,aes(x=x,y=y_pos,label=n))
p2
grid.arrange(p1, p2, heights = c(8.5, 1.5), nrow=2 )
However, that is very complicated and would be hard to modify for different data. Ideally, I'd like to be able to pass Y coordinates as percent of the screen.
The current version (>2.1) has a + labs(caption = "text"), which displays an annotation below the plot. This is themeable (font properties,... left/right aligned). See https://github.com/hadley/ggplot2/pull/1582 for examples.
Edited opts has been deprecated, replaced by theme; element_blank has replaced theme_blank; and ggtitle() is used in place of opts(title = ...
Sandy- thank you so much!!!! This does exactly what I want. I do wish we could control the clipping in geom.text or geom.annotate.
I put together the following program if anybody else is interested.
rm(list = ls()) # clear objects
graphics.off() # close graphics windows
library(ggplot2)
library(gridExtra)
#create dummy data
test= data.frame(
group = c("Group 1", "Group 1", "Group 1","Group 2", "Group 2", "Group 2"),
x = c(1 ,2,3,1,2,3 ),
y = c(33,25,27,36,23,25),
n=c(71,55,65,58,65,58),
ypos=c(18,18,18,17,17,17)
)
p1 <- qplot(x=x, y=y, data=test, colour=group) +
ylab("Mean change from baseline") +
theme(plot.margin = unit(c(1,3,8,1), "lines")) +
geom_line()+
scale_x_continuous("Visits", breaks=seq(-1,3) ) +
theme(legend.position="bottom",
legend.title=element_blank())+
ggtitle("Line plot")
# Create the textGrobs
for (ii in 1:nrow(test))
{
#display numbers at each visit
p1=p1+ annotation_custom(grob = textGrob(test$n[ii]),
xmin = test$x[ii],
xmax = test$x[ii],
ymin = test$ypos[ii],
ymax = test$ypos[ii])
#display group text
if (ii %in% c(1,4)) #there is probably a better way
{
p1=p1+ annotation_custom(grob = textGrob(test$group[ii]),
xmin = 0.85,
xmax = 0.85,
ymin = test$ypos[ii],
ymax = test$ypos[ii])
}
}
# Code to override clipping
gt <- ggplot_gtable(ggplot_build(p1))
gt$layout$clip[gt$layout$name=="panel"] <- "off"
grid.draw(gt)
Updated opts() has been replaced with theme()
In the code below, a base plot is drawn, with a wider margin at the bottom of the plot. The textGrob is created, then inserted into the plot using annotation_custom(). Except the text is not visible because it is outside the plot panel - the output is clipped to the panel. But using baptiste's code from here, the clipping can be overrridden. The position is in terms of data units, and both text labels are centred.
library(ggplot2)
library(grid)
# Base plot
df = data.frame(x=seq(1:10), y = seq(1:10))
p = ggplot(data = df, aes(x = x, y = y)) + geom_point() + ylim(0,10) +
theme(plot.margin = unit(c(1,1,3,1), "cm"))
p
# Create the textGrobs
Text1 = textGrob(paste("Largest x-value is", round(max(df$x), 2), sep = " "))
Text2 = textGrob(paste("Mean = ", mean(df$x), sep = ""))
p1 = p + annotation_custom(grob = Text1, xmin = 4, xmax = 4, ymin = -3, ymax = -3) +
annotation_custom(grob = Text2, xmin = 8, xmax = 8, ymin = -3, ymax = -3)
p1
# Code to override clipping
gt <- ggplotGrob(p1)
gt$layout$clip[gt$layout$name=="panel"] <- "off"
grid.draw(gt)
Or, using grid functions to create and position the label.
p
grid.text((paste("Largest x-value is", max(df$x), sep = " ")),
x = unit(.2, "npc"), y = unit(.1, "npc"), just = c("left", "bottom"),
gp = gpar(fontface = "bold", fontsize = 18, col = "blue"))
Edit
Or, add text grob using gtable functions.
library(ggplot2)
library(grid)
library(gtable)
# Base plot
df = data.frame(x=seq(1:10), y = seq(1:10))
p = ggplot(data = df, aes(x = x, y = y)) + geom_point() + ylim(0,10)
# Construct the text grob
lab = textGrob((paste("Largest x-value is", max(df$x), sep = " ")),
x = unit(.1, "npc"), just = c("left"),
gp = gpar(fontface = "bold", fontsize = 18, col = "blue"))
gp = ggplotGrob(p)
# Add a row below the 2nd from the bottom
gp = gtable_add_rows(gp, unit(2, "grobheight", lab), -2)
# Add 'lab' grob to that row, under the plot panel
gp = gtable_add_grob(gp, lab, t = -2, l = gp$layout[gp$layout$name == "panel",]$l)
grid.newpage()
grid.draw(gp)
Actually the best answer and easiest solution is to use the cowplot package.
Version 0.5.0 of the cowplot package (on CRAN) handles ggplot2 subtitles using the add_sub function.
Use it like so:
diamondsCubed <-ggplot(aes(carat, price), data = diamonds) +
geom_point() +
scale_x_continuous(trans = cuberoot_trans(), limits = c(0.2, 3),
breaks = c(0.2, 0.5, 1, 2, 3)) +
scale_y_continuous(trans = log10_trans(), limits = c(350, 15000),
breaks = c(350, 1000, 5000, 10000, 15000)) +
ggtitle('Price log10 by Cube-Root of Carat') +
theme_xkcd()
ggdraw(add_sub(diamondsCubed, "This is an annotation.\nAnnotations can span multiple lines."))

Resources