Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Perfect fit of ggplot2 plot in plot

I want to plot a restricted cubic spline as main plot and add a box-and-whisker plot to show the variation of the X variable. However, the lower hinge (x=42), the median (x=51), and the upper hinge(x=61) did not fit perfectly with the corresponding grid line of the main plot.

library(Hmisc)
library(rms)
library(ggplot2)
library(gridExtra)

data(pbc)
d <- pbc
rm(pbc)
d$status <- ifelse(d$status != 0, 1, 0)

dd = datadist(d)
options(datadist='dd')

f <- cph(Surv(time, status) ~  rcs(age, 4), data=d)
p <- Predict(f, fun=exp)
df <- data.frame(age=p$age, yhat=p$yhat, lower=p$lower, upper=p$upper)

### 1st PLOT: main plot
(g <- ggplot(data=df, aes(x=age, y=yhat)) + geom_line(size=1))

# CI
(g <- g + geom_ribbon(data=df, aes(ymin=lower, ymax=upper), alpha=0.5, linetype=0, fill='#FFC000'))

# white background
(g <- g + theme_bw())

# X-axis
(breaks <- round(boxplot.stats(p[,"age"])$stats))
(g <- g + scale_x_continuous(breaks=breaks, limits=range(p[,"age"]), labels=round(breaks)))
(g <- g + xlab("Age"))

# Y-Achse
(g <- g + ylab("Hazard Ratio"))

# size and color of axis
(g <- g + theme(axis.line = element_line(color='black', size=1)))
(g <- g + theme(axis.ticks = element_line(color='black', size=1)))

(g <- g + theme( plot.background = element_blank() ))
#(g <- g + theme( panel.grid.major = element_blank() ))
(g <- g + theme( panel.grid.minor = element_blank() ))
(g <- g + theme( panel.border = element_blank() ))

### 2nd PLOT: box whisker plot
describe(df$age, digits=0)
round(range(df$age))
(gg <- ggplot(data=df, aes(x=1, y=age)) + geom_boxplot(outlier.shape=NA, size=1) + coord_flip())
(gg <- gg + theme( axis.line=element_blank() )) #
(gg <- gg + theme( axis.text.x=element_blank() ))
(gg <- gg + theme( axis.text.y=element_blank() ))
(gg <- gg + theme( axis.ticks=element_blank() ))
(gg <- gg + theme( axis.title.x=element_blank() ))
(gg <- gg + theme( axis.title.y=element_blank() ))
(gg <- gg + theme( panel.background=element_blank() ))
(gg <- gg + theme( panel.border=element_blank() )) #
(gg <- gg + theme( legend.position="none" )) #
(gg <- gg + theme( panel.grid.major=element_blank() )) #
(gg <- gg + theme( panel.grid.minor=element_blank() ))
(gg <- gg + theme( plot.background=element_blank() ))
(gg <- gg + theme( plot.margin = unit( c(0,0,0,0), "in" ) ))

(gg <- gg + scale_x_continuous(breaks=c(70,77,84), expand=c(0,0)) )

### FINAL PLOT: put box whisker plot in main plot
(final.gg <- g + annotation_custom(ggplotGrob(gg),  ymin=2.4, ymax=2.6))
  1. What do I have to change for the perfect fit?
  2. Is there a better for a automatized alignment of the y-position of the box-and-whisker?

enter image description here

UPDATE #1 Thanks for your answer! Below you can see my example with your code. However, as you can see, the lower hinge, the median, and the upper hinge still do not fit. What is going wrong?

library(Hmisc)
library(rms)
library(ggplot2)
library(gridExtra)

data(pbc)
d <- pbc
rm(pbc, pbcseq)
d$status <- ifelse(d$status != 0, 1, 0)

dd = datadist(d)
options(datadist='dd')

f <- cph(Surv(time, status) ~  rcs(age, 4), data=d)
p <- Predict(f, fun=exp)
df <- data.frame(age=p$age, yhat=p$yhat, lower=p$lower, upper=p$upper)

### 1st PLOT: main plot
(breaks <- boxplot.stats(p[,"age"])$stats)
g <- ggplot(data=df, aes(x=age, y=yhat)) + geom_line(size=1) +
     geom_ribbon(data=df, aes(ymin=lower, ymax=upper), alpha=0.5, linetype=0, fill='#FFC000') +
     theme_bw() +
     scale_x_continuous(breaks=breaks) +
     xlab("Age") +
     ylab("Hazard Ratio") +
     theme(axis.line = element_line(color='black', size=1),
           axis.ticks = element_line(color='black', size=1),
           plot.background = element_blank(),
           # panel.border = element_blank(),
           panel.grid.minor = element_blank())

### 2nd PLOT: box whisker plot
gg <- ggplot(data=df, aes(x=1, y=age)) + 
     geom_boxplot(outlier.shape=NA, size=1) + 
     scale_y_continuous(breaks=breaks) +
     ylab(NULL) +
     coord_flip()  + 
     # theme_bw() +
     theme(axis.line=element_blank(), 
           # axis.text.x=element_blank(),
           axis.text.y=element_blank(),
           axis.ticks.y=element_blank(), 
           axis.title=element_blank(),
           # panel.background=element_blank(),
           panel.border=element_blank(),
           # panel.grid.major=element_blank(),
           panel.grid.minor=element_blank(),
           # plot.background=element_blank(),
           plot.margin = unit( c(0,0,0,0), "in" ), 
           axis.ticks.margin = unit(0, "lines"),
           axis.ticks.length = unit(0, "cm"))

### FINAL PLOT: put box whisker plot in main plot
(final.gg <- g + annotation_custom(ggplotGrob(gg),  ymin=2.4, ymax=2.6))

enter image description here

like image 865
Gurkenhals Avatar asked Jul 23 '15 12:07

Gurkenhals


1 Answers

Minor edit: Updating to ggplot2 2.0.0 axis.ticks.margin is deprecated

In the boxplot, even though you have set various elements to element_blank and margins to zero, default spaces remain resulting in the misalignment. These spaces belong to:

  • axis.ticks.length
  • xlab

In the code below, I've re-arranged your code somewhat (I hope that's okay), and commented out some lines of code so that it can be seen that the two plots do align. I've also set the breaks in the two plot to un-rounded breaks (min and max values, hinges, and the median).

# X-axis
(breaks <- boxplot.stats(p[,"age"])$stats)

### 1st PLOT: main plot
g <- ggplot(data=df, aes(x=age, y=yhat)) + geom_line(size=1) +
     geom_ribbon(data=df, aes(ymin=lower, ymax=upper), alpha=0.5, linetype=0, fill='#FFC000') +
     theme_bw() +
     scale_x_continuous(breaks=breaks) +
     xlab("Age") +
     ylab("Hazard Ratio") +
     theme(axis.line = element_line(color='black', size=1),
       axis.ticks = element_line(color='black', size=1),
       plot.background = element_blank(),
      # panel.border = element_blank(),
       panel.grid.minor = element_blank())

### 2nd PLOT: box whisker plot
gg <- ggplot(data=df, aes(x=1, y=age)) + 
      geom_boxplot(outlier.shape=NA, size=1) + 
      scale_y_continuous(breaks=breaks) +
      xlab(NULL) +
      coord_flip()  + 
     # theme_bw() +
      theme(axis.line=element_blank(), 
         # axis.text.x=element_blank(),
          axis.text.y=element_blank(),
          axis.ticks.y=element_blank(), 
          axis.title=element_blank(),
         # panel.background=element_blank(),
          panel.border=element_blank(),
         # panel.grid.major=element_blank(),
          panel.grid.minor=element_blank(),
         # plot.background=element_blank(),
          plot.margin = unit( c(0,0,0,0), "in" ), 
         # axis.ticks.margin = unit(0, "lines"),
          axis.ticks.length = unit(0, "cm"))

### FINAL PLOT: put box whisker plot in main plot
(final.gg <- g + annotation_custom(ggplotGrob(gg),  ymin=2.4, ymax=2.6))

enter image description here

You should note that ggplot's method for calculating the hinges differs slightly from the method used by boxplot.stats.

# ggplot's hinges
bp = ggplot(data=df, aes(x=1, y=age)) + 
      geom_boxplot(outlier.shape=NA, size=1)
bpData = ggplot_build(bp)
bpData$data[[1]][1:5]
like image 162
Sandy Muspratt Avatar answered Sep 30 '22 13:09

Sandy Muspratt