aligning distinct non-facet plots in ggplot2 using Rpy2 in Python

前端 未结 3 1454
广开言路
广开言路 2021-01-02 01:55

I am combining two distinct plots into a grid layout with grid as suggested by @lgautier in rpy2 using python. The top plot is a density and and the bottom a ba

相关标签:
3条回答
  • 2021-01-02 02:14

    Aligning two plots becomes much trickier when facets are involved. I don't know if there is a general solution, even in R. Consider this scenario,

    p1 <- ggplot(mtcars, aes(mpg, wt)) + geom_point() + 
      facet_wrap(~ cyl, ncol=2,scales="free")
    p2 <- p1 + facet_null() + aes(colour=am) + ylab("this\nis taller")
    
    gridExtra::grid.arrange(p1, p2)
    

    enter image description here

    With some work, you can compare the widths for the left axis, and the legends (which may or may not be present on the right side).

    library(gtable)
    
    # legend, if it exists, may be the second last item on the right, 
    # unless it's not on the right side.
    locate_guide <- function(g){
      right <- max(g$layout$r)
      gg <- subset(g$layout, (grepl("guide", g$layout$name) & r == right - 1L) | 
                     r == right)
      sort(gg$r)
    }
    
    compare_left <- function(g1, g2){
    
      w1 <- g1$widths[1:3]
      w2 <- g2$widths[1:3]
      unit.pmax(w1, w2)
    }
    
    align_lr <- function(g1, g2){
    
      # align the left side 
      left <- compare_left(g1, g2)
      g1$widths[1:3] <- g2$widths[1:3] <- left
    
      # now deal with the right side
    
      gl1 <- locate_guide(g1)
      gl2 <- locate_guide(g2)
    
      if(length(gl1) < length(gl2)){
        g1$widths[[gl1]] <- max(g1$widths[gl1], g2$widths[gl2[2]]) +
          g2$widths[gl2[1]]
      }
      if(length(gl2) < length(gl1)){
        g2$widths[[gl2]] <- max(g2$widths[gl2], g1$widths[gl1[2]]) +
          g1$widths[gl1[1]]
      }
      if(length(gl1) == length(gl2)){
        g1$widths[[gl1]] <-  g2$widths[[gl2]] <- unit.pmax(g1$widths[gl1], g2$widths[gl2])
      }
    
      grid.arrange(g1, g2)
    }
    
    align_lr(g1, g2)
    

    enter image description here

    Note that I haven't tested other cases; I'm sure it's very easy to break. As far as I understand from the docs, rpy2 provides a mechanism to use an arbitrary piece of R code, so the conversion should not be a problem.

    0 讨论(0)
  • 2021-01-02 02:20

    Split the legends from the plots (see ggplot separate legend and plot) , then use grid.arrange

    library(gridExtra)
    g_legend <- 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
     }
     legend1 <- g_legend(p1)
     legend2 <- g_legend(p2)
    
    grid.arrange(p1 + theme(legend.position = 'none'), legend1, 
                 p2 + theme(legend.position = 'none'), legend2,
                ncol=2, widths = c(5/6,1/6))
    

    This is obviously the R implementation.

    0 讨论(0)
  • 2021-01-02 02:23

    Untested translation of the answer using gridExtra's grid.arrange(). The left sides of the plots (where the labels for the y-axis are) might not always be aligned though.

    from rpy2.robjects.packages import importr
    gridextra = importr('gridExtra')
    from rpy2.robjects.lib import ggplot2
    _ggplot2 = ggplot2.ggplot2
    def dollar(x, name): # should be included in rpy2.robjects, may be...
        return x[x.index(name)]
    
    def g_legend(a_gplot):
        tmp = _ggplot2.ggplot_gtable(_ggplot2.ggplot_build(a_gplot))
        leg = [dollar(x, 'name')[0] for x in dollar(tmp, 'grobs')].index('guide-box')
        legend = dollar(tmp, 'grobs')[leg]
        return legend
    legend1 = g_legend(p1)
    legend2 = g_legend(p2)
    nolegend = ggplot2.theme(**{'legend.position': 'none'})
    gridexta.grid_arrange(p1 + nolegend, legend1, 
                          p2 + nolegend, legend2,
                          ncol=2, widths = FloatVector((5.0/6,1.0/6)))
    
    0 讨论(0)
提交回复
热议问题