36
votes

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.

4
annotation_custom() and code from here might help you. See below.Sandy Muspratt
See my answer below. It is definitely simpler than the accepted answer.Taylor C. White
Recent versions of ggplot2 now have this functionality built in as per the answer below: stackoverflow.com/a/36036479/170352Brandon Bertelsen

4 Answers

23
votes

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.

21
votes

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)

enter image description here

18
votes

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)

enter image description here

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"))

enter image description here

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)

enter image description here

5
votes

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."))