Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change the y-axis alignment of two faceted plots #312

Closed
LisaNicvert opened this issue Nov 17, 2022 · 8 comments
Closed

Change the y-axis alignment of two faceted plots #312

LisaNicvert opened this issue Nov 17, 2022 · 8 comments

Comments

@LisaNicvert
Copy link

Hello,
I am using the patchwork package (fantastic package, thank you!) to combine 2 ggplots which are faceted using facet_grid. Patchwork works perfectly, and aligns both x- and y-axes.
However, when one of the plots is only faceted by columns, and the other by columns but also by rows, in some cases it can make sense to align only the x-axes. Indeed, aligning both plots y-axes causes a space to be added between the y-axis and the plot with only columns as facets (when switch = y) (red square on the image). Below is a reproducible example:

# load useful libraries
library(ggplot2)
library(patchwork)

# load data
data(iris)

# Add dummy factor variable
iris$dummy <- rep(c("A", "B", "C"), 50)

# First plot (facets = cols only)
g1 <- ggplot(iris) + 
  geom_density(aes(x = Petal.Width)) +
  facet_grid(cols = vars(Species))

# Second plot (facets = cols and rows)
g2 <- ggplot(iris) +
  geom_point(aes(x = Petal.Width, y = Petal.Length)) +
  facet_grid(cols = vars(Species),
             rows = vars(dummy),
             switch = "y")

# Patchwork
g1 / g2 + plot_layout(widths = c(1, 3))
# I would like the y axis on the top plot to be directly
# next to the plotting area

plot_annotated

Is there a way to control the placement of the y-axes (in that case, move the y-axis of the top plot closer to the plot area, as indicated by the red arrow)?

Thanks!

@mschilli87
Copy link

mschilli87 commented Nov 17, 2022

I don't think this is currently possible but IIRC ggplot2 allows to control the placement of the faceting 'boxes' and legends independently. So I'd expect than by moving the A/B/C boxes in the lower to the right but leaving the y-axis on the left for both plots, patchwork would keep aligning both y-axes underneath each other but at the position indicated by your red arrow. Additionally, you might be able to drop the x axis of the upper plot as well as the species boxes from the lower plot completely, as they contain redundant information, to get an even cleaner plot.


edit (I): I just realized that you were explicitly asking about having the boxes on the left (switch = "y"). So feel free to ignore my suggestion.


edit (II): Alternatively, I thought of adding theme(strip.placement = "outside") to g2 since that would move the y-axis of the lower plot to where you want it in the upper plot. However, while this works on its own, it results in a subscript out of bounds error when combining the two plots using patchwork.


edit (III): Just for completeness, in case it is helpful for anybody, here is the cleanest version of the plot I could come up with without using the broken strip.placement option:

# load useful libraries
library(ggplot2)
library(patchwork)

# load data
data(iris)

# Add dummy factor variable
iris$dummy <- rep(c("A", "B", "C"), 50)

# First plot (facets = cols only)
g1 <- ggplot(iris) + 
  geom_density(aes(x = Petal.Width)) +
  facet_grid(cols = vars(Species))

# Second plot (facets = cols and rows)
g2 <- ggplot(iris) +
  geom_point(aes(x = Petal.Width, y = Petal.Length)) +
  facet_grid(cols = vars(Species),
             rows = vars(dummy),
#
# CHANGES START HERE!
#
             switch = "x")

# Patchwork
(g1 + theme(axis.text.x = element_blank(),
            axis.ticks.x = element_blank(),
            axis.title.x = element_blank(),
            strip.text.x = element_blank())) /
  (g2 + theme(strip.text.x = element_text(angle = 0))) +
  plot_layout(heights = c(1, 3))

plot


edit (IV): Finally, the best version I could come up with with the faceting boxes on top and left instead of bottom and right (adding a descriptive 'dummy' dummy (no pun intended) value for the density plot to cover up the gap instead of moving the y axis):

# load useful libraries
library(ggplot2)
library(patchwork)

# load data
data(iris)

# Add dummy factor variable
iris$dummy <- rep(c("A", "B", "C"), 50)

# First plot (facets = cols only)
g1 <- ggplot(iris) + 
  geom_density(aes(x = Petal.Width)) +
#
# CHANGES START HERE!
#
  facet_grid("pooled" ~ Species,
             switch = "y")

# Second plot (facets = cols and rows)
g2 <- ggplot(iris) +
  geom_point(aes(x = Petal.Width, y = Petal.Length)) +
  facet_grid(cols = vars(Species),
             rows = vars(dummy),
             switch = "both")

(g1 + theme(axis.text.x = element_blank(),
            axis.ticks.x = element_blank(),
            axis.title.x = element_blank())) /
  (g2 + theme(strip.text.y = element_text(angle = 0),
              strip.text.x = element_blank())) +
  plot_layout(heights = c(1, 3))

plot

@LisaNicvert
Copy link
Author

Hi @mschilli87, thank you for your answer! I appreciate your comments regarding discarding redundant x-axis and x-strips, so I will keep those suggestions.

Adding a "dummy" value as a faceting variable for the top plot can do the trick. However, if anyone has a solution that doesn't involve adding this dummy faceting variable, it would be better for my use-case (and I guess more generic).

Unfortunately, this solution doesn't work if the y-strips widths are different, which is something I would like for the particular problem at hand. See for example what happens if the strip text is horizontal:

# load useful libraries
library(ggplot2)
library(patchwork)

# load data
data(iris)

# Add dummy factor variable
iris$dummy <- rep(c("A", "B", "C"), 50)

# First plot (facets = cols only)
g1 <- ggplot(iris) + 
  geom_density(aes(x = Petal.Width)) +
  facet_grid("pooled" ~ Species, # Add dummy facet
             switch = "y") +
  # theme personnalization to remove redundant x-axis text
  theme(axis.text.x = element_blank(),
        axis.ticks.x = element_blank(),
        axis.title.x = element_blank())

# Second plot (facets = cols and rows)
g2 <- ggplot(iris) +
  geom_point(aes(x = Petal.Width, y = Petal.Length)) +
  facet_grid(cols = vars(Species),
             rows = vars(dummy),
             switch = "y") +
  # theme personnalization to remove redundant column strips
  theme(strip.text.y = element_blank())

# Patchwork
(g1 / g2) + plot_layout(heights = c(1, 3)) &
  theme(strip.text.y.left = element_text(angle = 0)) # This causes a problem
  
# The proposed solution works well unless the strip text on y-axis is written horizontally 
# (then strip width are different)

plot2

Does anyone have an idea to constrain both strips to the same width?

@mschilli87
Copy link

mschilli87 commented Nov 18, 2022

@LisaNicvert: I understand your issue. IMHO the 'best' solution would be setting strip.placement = "outside" which should work but clearly doesn't. I am not sure if this is a known bug or a new one and how complicated fixing it would be. Would that (i.e. swapping the y-axis and the strips be an acceptable solution for you or do you really want non-aligned y-axes as a (new?) feature of patchwork?


PS: I really like your idea of determining the width/height of the strips by the corresponding maximum across plots. Are you aware of a way to control those in ggplot2 so that adding this feature to patchwork would require work here only? Otherwise this would probably require upstreaming a new feature to ggplot2 first which is likely more work.

@LisaNicvert
Copy link
Author

Hi @mschilli87! Following your remark, I tried setting strip.placement = "outside" for the first plot and it worked (i.e. resulted on the y-axis being not aligned as required). It introduces a small space between the x strips and the plot area in the top plot, and the y-axis titles are no more aligned, but the solution is acceptable to me. Below are the corresponding code and output:

# load useful libraries
library(ggplot2)
library(patchwork)

# load data
data(iris)

# Add dummy factor variable
iris$dummy <- rep(c("A", "B", "C"), 50)

# First plot (facets = cols only)
g1 <- ggplot(iris) + 
  geom_density(aes(x = Petal.Width)) +
  facet_grid(cols = vars(Species)) +
  # theme personnalization to remove redundant x-axis text
  theme(axis.text.x = element_blank(),
        axis.ticks.x = element_blank(),
        axis.title.x = element_blank(),
        # Fix
        strip.placement = "outside") 

# Second plot (facets = cols and rows)
g2 <- ggplot(iris) +
  geom_point(aes(x = Petal.Width, y = Petal.Length)) +
  facet_grid(cols = vars(Species),
             rows = vars(dummy),
             switch = "y") +
  # theme personnalization to remove redundant column strips
  # and write row strips labels horizontally
  theme(strip.text.y.left = element_text(angle = 0),
        strip.text.x = element_blank())

# Patchwork
(g1 / g2) + plot_layout(heights = c(1, 3))

solution

I don't know why it would not work for you. I am using patchwork 1.1.1 and ggplot2 3.3.6. Below is the output of my sessionInfo in case it is enlightening:

> sessionInfo()
R version 4.1.3 (2022-03-10)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 20.04.5 LTS

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.9.0
LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.9.0

locale:
 [1] LC_CTYPE=fr_FR.UTF-8       LC_NUMERIC=C              
 [3] LC_TIME=fr_FR.UTF-8        LC_COLLATE=fr_FR.UTF-8    
 [5] LC_MONETARY=fr_FR.UTF-8    LC_MESSAGES=fr_FR.UTF-8   
 [7] LC_PAPER=fr_FR.UTF-8       LC_NAME=C                 
 [9] LC_ADDRESS=C               LC_TELEPHONE=C            
[11] LC_MEASUREMENT=fr_FR.UTF-8 LC_IDENTIFICATION=C       

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods  
[7] base     

other attached packages:
[1] patchwork_1.1.1 ggplot2_3.3.6  

loaded via a namespace (and not attached):
 [1] magrittr_2.0.2   tidyselect_1.1.2 munsell_0.5.0   
 [4] colorspace_2.0-3 R6_2.5.1         rlang_1.0.1     
 [7] fansi_1.0.2      dplyr_1.0.8      tools_4.1.3     
[10] grid_4.1.3       gtable_0.3.0     utf8_1.2.2      
[13] cli_3.4.0        DBI_1.1.1        withr_2.4.3     
[16] ellipsis_0.3.2   digest_0.6.29    assertthat_0.2.1
[19] tibble_3.1.6     lifecycle_1.0.1  crayon_1.5.0    
[22] farver_2.1.0     purrr_0.3.4      vctrs_0.3.8     
[25] glue_1.6.2       labeling_0.4.2   compiler_4.1.3  
[28] pillar_1.7.0     generics_0.1.2   scales_1.1.1    
[31] pkgconfig_2.0.3

Anyway, this works for me and I guess the issue can be closed if you manage to reproduce this result or if you find a valuable reason why this would not work in your install.


PS: regardless, I also think it could be useful to be able to set the strips heights or widths manually in ggplot2, but I am not aware of any built-in way to do this. The fixes I saw involved converting the ggplot object to a grob and setting widths and heights of the strips directly there (see here for instance), but it is not "native" ggplot2 code.

@mschilli87
Copy link

@LisaNicvert: I didn't even think of setting the strip placement for the first plot. I tried setting it on the second one, so its y-axis would be aligned with the upper one while placing the strip to the left of it. This is where I am getting the error when combining both plots with patchwork (v. 1.1.2 with ggplot2 v. 3.4.0 under R v. 4.2.1). Anyway, I am happy you found a workaround that suffices for you.

@LisaNicvert
Copy link
Author

Great! I'm closing the issue then. Thank you for your help @mschilli87, I hadn't thought of changing strip.placement :)

@mschilli87
Copy link

@thomasp85: Could you maybe comment on the subscript out of bounds error I got? Is this a known bug? Should I open a separate (new) issue for it?

@selkamand
Copy link

selkamand commented Jan 21, 2024

Hi, just flagging this workaround doesn't seem to work for me (same code as described here).

Does anyone know the best way to solve the OPs issue in current versions of patchwork?

# load useful libraries
library(ggplot2)
library(patchwork)

# load data
data(iris)

# Add dummy factor variable
iris$dummy <- rep(c("A", "B", "C"), 50)

# First plot (facets = cols only)
g1 <- ggplot(iris) + 
  geom_density(aes(x = Petal.Width)) +
  facet_grid(cols = vars(Species)) +
  # theme personnalization to remove redundant x-axis text
  theme(axis.text.x = element_blank(),
        axis.ticks.x = element_blank(),
        axis.title.x = element_blank(),
        # Fix
        strip.placement = "outside") 

# Second plot (facets = cols and rows)
g2 <- ggplot(iris) +
  geom_point(aes(x = Petal.Width, y = Petal.Length)) +
  facet_grid(cols = vars(Species),
             rows = vars(dummy),
             switch = "y") +
  # theme personnalization to remove redundant column strips
  # and write row strips labels horizontally
  theme(strip.text.y.left = element_text(angle = 0),
        strip.text.x = element_blank())

# Patchwork
(g1 / g2) + plot_layout(heights = c(1, 3))

sessionInfo()
#> R version 4.3.2 (2023-10-31)
#> Platform: x86_64-pc-linux-gnu (64-bit)
#> Running under: Pop!_OS 22.04 LTS
#> 
#> Matrix products: default
#> BLAS:   /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.10.0 
#> LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.10.0
#> 
#> locale:
#>  [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
#>  [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
#>  [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
#>  [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                 
#>  [9] LC_ADDRESS=C               LC_TELEPHONE=C            
#> [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       
#> 
#> time zone: Australia/Sydney
#> tzcode source: system (glibc)
#> 
#> attached base packages:
#> [1] stats     graphics  grDevices utils     datasets  methods   base     
#> 
#> other attached packages:
#> [1] patchwork_1.2.0.9000 ggplot2_3.4.4       
#> 
#> loaded via a namespace (and not attached):
#>  [1] gtable_0.3.4      dplyr_1.1.4       compiler_4.3.2    reprex_2.1.0     
#>  [5] tidyselect_1.2.0  scales_1.3.0      yaml_2.3.8        fastmap_1.1.1    
#>  [9] R6_2.5.1          labeling_0.4.3    generics_0.1.3    knitr_1.45       
#> [13] tibble_3.2.1      munsell_0.5.0     R.cache_0.16.0    pillar_1.9.0     
#> [17] R.utils_2.12.3    rlang_1.1.3       utf8_1.2.4        xfun_0.41        
#> [21] fs_1.6.3          cli_3.6.2         withr_3.0.0       magrittr_2.0.3   
#> [25] digest_0.6.34     grid_4.3.2        rstudioapi_0.15.0 lifecycle_1.0.4  
#> [29] R.methodsS3_1.8.2 R.oo_1.25.0       vctrs_0.6.5       evaluate_0.23    
#> [33] glue_1.7.0        farver_2.1.1      styler_1.10.2     fansi_1.0.6      
#> [37] colorspace_2.1-0  rmarkdown_2.25    purrr_1.0.2       tools_4.3.2      
#> [41] pkgconfig_2.0.3   htmltools_0.5.7

Created on 2024-01-21 with reprex v2.1.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants