R - how to allocate screen space to complex ggplot images

后端 未结 1 1157
隐瞒了意图╮
隐瞒了意图╮ 2021-02-08 05:40

I am trying to write a script that produces four different plots in a single image. Specifically, I want to recreate this graphic as closely as possible:

My cur

相关标签:
1条回答
  • 2021-02-08 06:13

    Here are the steps to get the layout you describe:

    1) Extract the legend as a separate grob ("graphical object"). We can then lay out the legend separately from the plots.

    2) Left-align the edges of the four plots so that the left edges and the x-scales line up properly. The code to do that comes from this SO answer. That answer has a function to align an arbitrary number of plots, but I wasn't able to get that to work when I also wanted to change the proportional space allotted to each plot, so I ended up doing it the "long way" by adjusting each plot separately.

    3) Lay out the plots and the legend using grid.arrange and arrangeGrob. The heights argument allocates different proportions of the total vertical space to each plot. We also use the widths argument to allocate horizontal space to the plots in one wide column and the legend in another narrow column.

    4) Plot to a device in whatever size you desire. This is how you get a particular shape or aspect ratio.

    library(gridExtra)
    library(grid)
    
    # Function to extract the legend from a ggplot graph as a separate grob
    # Source: https://stackoverflow.com/a/12539820/496488
    get_leg = 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]]
      legend
    }
    
    # Get legend as a separate grob
    leg = get_leg(site_status)
    
    # Add a theme element to change the plot margins to remove white space between the plots
    thm = theme(plot.margin=unit(c(0,0,-0.5,0),"lines"))
    
    # Left-align the four plots 
    # Adapted from: https://stackoverflow.com/a/13295880/496488
    gA <- ggplotGrob(bar + thm)
    gB <- ggplotGrob(smoke_status + thm)
    gC <- ggplotGrob(hpv_status + thm)
    gD <- ggplotGrob(site_status + theme(plot.margin=unit(c(0,0,0,0), "lines")) + 
                      guides(fill=FALSE))
    
    maxWidth = grid::unit.pmax(gA$widths[2:5], gB$widths[2:5], gC$widths[2:5], gD$widths[2:5])
    gA$widths[2:5] <- as.list(maxWidth)
    gB$widths[2:5] <- as.list(maxWidth)
    gC$widths[2:5] <- as.list(maxWidth)
    gD$widths[2:5] <- as.list(maxWidth)
    
    # Lay out plots and legend
    p = grid.arrange(arrangeGrob(gA,gB,gC,gD, heights=c(0.5,0.15,0.15,0.21)),
                     leg, ncol=2, widths=c(0.8,0.2))
    

    You can then determine the shape or aspect ratio of the final plot by setting the parameters of the output device. (You may have to adjust font sizes when you create the underlying plots in order to get the final layout to look the way you want it.) The plot pasted in below is a png saved directly from the RStudio graph window. Here's how you would save the plot as PDF file (but there are many other "devices" you can use (e.g., png, jpeg, etc.) to save in different formats):

    pdf("myPlot.pdf", width=10, height=5)
    p
    dev.off()
    

    You also asked about more efficient code. One thing you can do is create a list of plot elements that you use multiple times and then just add the name of the list object to each plot. For example:

    my_gg = list(geom_bar(stat="identity", fill="red"),
                 theme(legend.position = "none", 
                       axis.title.x = element_blank(), 
                       axis.ticks.x = element_blank(), 
                       axis.text.x = element_blank()),
                       plot.margin = unit(c(0,0,-0.5,0), "lines"))
    
    smoke_status = ggplot(data, aes(x=pt_id, y=smoke)) + 
                      labs(y="Smoking Status") +
                      my_gg
    
    0 讨论(0)
提交回复
热议问题