Search code examples
ggplot2sizefaceteggggh4x

Set same panel size for diferent faceted ggplot figures


I have several lists, each of them containing a diferent number of datasets (minimal example of a list below):

library(tidyverse)


 mylist = list()
 mylist[[1]] = mtcars 
 mylist[[2]] = mtcars |> filter(carb %in% 1:2)
 mylist[[3]] = mtcars |> filter(carb == 1)

For each dataset in a list, I want to make a separate figure showing faceted data, and have all the figures in an RMarkdown or Quarto document rendered as html. To do this, I used a loop to generate all the figures and, in order to have the same size for all the the panels across all figures, I first tried to configure fig.width and fig.height in the corresponding chunk with appropiate numeric vectors so that the total size of each figure is dependent on the number of panels it contains. However, I failed to get the same size for all panels across figures:

{r, fig.width= c(2,4,6), fig.height= c(3,3,6)}

library(ggplot2)
for(i in 1:length(mylist)){
   p1 = ggplot(mylist[[i]], aes(group= gear, y = wt)) + 
        geom_boxplot() + 
        facet_wrap(~carb) 
   
   print(p1)
   
   cat("\n \n")

 } 

enter image description here

I then tried set_panel_size from the egg package. Although it seems to do the job, the figures are too far apart from each other. I also get some text about the grobs that I do not wish displayed on the html document:

```{r}
library(egg)
 
for(i in 1: length(mylist)){
   p1 = ggplot(mylist[[i]], aes(group= gear, y = wt)) +
        geom_boxplot() +
        facet_wrap(~carb, ncol = 3)

  print( grid.arrange(grobs = list(set_panel_size(p = p1,
                                                  g = ggplot2::ggplotGrob(p1),
                                                  width = unit(2, "in"),                                               
                                                  height = unit(5,"cm")))))
 }
```

enter image description here

I next tried force_panelsizes from the ggh4xpackage, and have a similar problem regarding distance among figures:

library(ggh4x)

for(i in 1:length(mylist)){
   p1 = ggplot(mylist[[i]], aes(group= gear, y = wt)) +
        geom_boxplot() +
        facet_wrap(~carb) +
        force_panelsizes(rows = unit(5, "cm"), cols = unit(2, "in"))

   print(p1)
 }

enter image description here

I also tried facet_wrap2 from the same package, but the result was similar to the first try using size options in the chunk.

So, how can I make the size of each panel to be the same across figures with diferent numbers of panels without having the figures so fart apart from each other?

Thanks in advance!


Solution

  • One option would be to use a child document to render plots where I sticked with your ggh4x approach to fix the panel sizes. Doing so allows to have each plot in a separate chunk with an individual plot height.

    ---
    title: gg-loop
    output: html_document
    date: "2024-01-17"
    ---
    
    ```{r include=FALSE}
    library(tidyverse)
    library(ggh4x)
    mylist <- list()
    mylist[[1]] <- mtcars
    mylist[[2]] <- mtcars |> filter(carb %in% 1:2)
    mylist[[3]] <- mtcars |> filter(carb == 1)
    ```
    
    ```{r results='asis', echo = FALSE}
    res <- lapply(seq_along(mylist), \(i) {
      knitr::knit_child("gg-child.Rmd", envir = environment(), quiet = TRUE)
    })
    
    cat(unlist(res), sep = "\n")
    ```
    

    gg-child.Rmd

    ```{r echo=FALSE, fig.height= 6.5 * (if (i == 1) 2 else 1) / 2.54}
    ggplot(mylist[[i]], aes(group = gear, y = wt)) +
      geom_boxplot() +
      facet_wrap(~carb) +
      force_panelsizes(rows = unit(5, "cm"), cols = unit(2, "in"))
    ```
    

    enter image description here