alineando distintos gráficos no facetarios en ggplot2 usando Rpy2 en Python

Estoy combinando dos plots distintas en un diseño de grid con grid como lo sugiere @lgautier en rpy2 usando python. La gráfica superior es una densidad y la gráfica de barras inferior:

 iris = r('iris') import pandas # define layout lt = grid.layout(2, 1) vp = grid.viewport(layout = lt) vp.push() # first plot vp_p = grid.viewport(**{'layout.pos.row': 1, 'layout.pos.col':1}) p1 = ggplot2.ggplot(iris) + \ ggplot2.geom_density(aes_string(x="Sepal.Width", colour="Species")) + \ ggplot2.facet_wrap(Formula("~ Species")) p1.plot(vp = vp_p) # second plot mean_df = pandas.DataFrame({"Species": ["setosa", "virginica", "versicolor"], "X": [10, 2, 30], "Y": [5, 3, 4]}) mean_df = pandas.melt(mean_df, id_vars=["Species"]) r_mean_df = get_r_dataframe(mean_df) p2 = ggplot2.ggplot(r_mean_df) + \ ggplot2.geom_bar(aes_string(x="Species", y="value", group="variable", colour="variable"), position=ggplot2.position_dodge(), stat="identity") vp_p = grid.viewport(**{'layout.pos.row': 2, 'layout.pos.col':1}) p2.plot(vp = vp_p) 

Lo que obtengo está cerca de lo que quiero pero las plots no están alineadas exactamente (se muestran mediante las flechas que agregué):

introduzca la descripción de la imagen aquí

Me gustaría que las regiones de la ttwig (no las leyendas) coincidieran exactamente. ¿Cómo se puede lograr eso? La diferencia aquí no es tan grande, pero a medida que agrega condiciones al gráfico de barras a continuación o las convierte en gráficos de barras esquivados con position_dodge las diferencias pueden llegar a ser muy grandes y los gráficos no están alineados.

La solución ggplot estándar no se puede traducir fácilmente a rpy2:

grid_arrange parece ser grid_arrange en gridExtra :

 >>> gridExtra = importr("gridExtra") >>> gridExtra.grid_arrange  

ggplotGrob no es accesible desde ggplot2 , pero se puede acceder de esta manera:

 >>> ggplot2.ggplot2.ggplotGrob 

Aunque no tengo idea de cómo acceder a grid::unit.pmax :

 >>> grid.unit <bound method type.unit of > >>> grid.unit("pmax") Error in (function (x, units, data = NULL) : argument "units" is missing, with no default rpy2.rinterface.RRuntimeError: Error in (function (x, units, data = NULL) : argument "units" is missing, with no default 

así que no está claro cómo traducir la solución ggplot2 estándar a rpy2.

edit : como otros señalaron grid::unit.pmax es grid.unit_pmax . Sin embargo, aún no sé cómo acceder en rpy2 al parámetro de widths de los objetos grob , que es necesario para establecer los anchos de los gráficos para que sean los de un gráfico más amplio. Yo tengo:

 gA = ggplot2.ggplot2.ggplotGrob(p1) gB = ggplot2.ggplot2.ggplotGrob(p2) g = importr("grid") print "gA: ", gA maxWidth = g.unit_pmax(gA.widths[2:5], gB.widths[2:5]) 

gA.widths no es la syntax correcta. El objeto gA imprime como:

 gA: TableGrob (8 x 13) "layout": 17 grobs z cells name grob 1 0 ( 1- 8, 1-13) background rect[plot.background.rect.350] 2 1 ( 4- 4, 4- 4) panel-1 gTree[panel-1.gTree.239] 3 2 ( 4- 4, 7- 7) panel-2 gTree[panel-2.gTree.254] 4 3 ( 4- 4,10-10) panel-3 gTree[panel-3.gTree.269] 5 4 ( 3- 3, 4- 4) strip_t-1 absoluteGrob[strip.absoluteGrob.305] 6 5 ( 3- 3, 7- 7) strip_t-2 absoluteGrob[strip.absoluteGrob.311] 7 6 ( 3- 3,10-10) strip_t-3 absoluteGrob[strip.absoluteGrob.317] 8 7 ( 4- 4, 3- 3) axis_l-1 absoluteGrob[axis-l-1.absoluteGrob.297] 9 8 ( 4- 4, 6- 6) axis_l-2 zeroGrob[axis-l-2.zeroGrob.298] 10 9 ( 4- 4, 9- 9) axis_l-3 zeroGrob[axis-l-3.zeroGrob.299] 11 10 ( 5- 5, 4- 4) axis_b-1 absoluteGrob[axis-b-1.absoluteGrob.276] 12 11 ( 5- 5, 7- 7) axis_b-2 absoluteGrob[axis-b-2.absoluteGrob.283] 13 12 ( 5- 5,10-10) axis_b-3 absoluteGrob[axis-b-3.absoluteGrob.290] 14 13 ( 7- 7, 4-10) xlab text[axis.title.x.text.319] 15 14 ( 4- 4, 2- 2) ylab text[axis.title.y.text.321] 16 15 ( 4- 4,12-12) guide-box gtable[guide-box] 17 16 ( 2- 2, 4-10) title text[plot.title.text.348] 

actualización: hizo algunos progresos en el acceso a los anchos, pero aún no puede traducir la solución. Para establecer anchos de grobs, tengo:

 # get grobs gA = ggplot2.ggplot2.ggplotGrob(p1) gB = ggplot2.ggplot2.ggplotGrob(p2) g = importr("grid") # get max width maxWidth = g.unit_pmax(gA.rx2("widths")[2:5][0], gB.rx2("widths")[2:5][0]) print gA.rx2("widths")[2:5] wA = gA.rx2("widths")[2:5] wB = gB.rx2("widths")[2:5] print "before: ", wA[0] wA[0] = robj.ListVector(maxWidth) print "After: ", wA[0] print "before: ", wB[0] wB[0] = robj.ListVector(maxWidth) print "after:", wB[0] gridExtra.grid_arrange(gA, gB, ncol=1) 

Se ejecuta pero no funciona. La salida es:

 [[1]] [1] 0.740361111111111cm [[2]] [1] 1null [[3]] [1] 0.127cm before: [1] 0.740361111111111cm After: [1] max(0.740361111111111cm, sum(1grobwidth, 0.15cm+0.1cm)) before: [1] sum(1grobwidth, 0.15cm+0.1cm) after: [1] max(0.740361111111111cm, sum(1grobwidth, 0.15cm+0.1cm)) 

update2: se dio cuenta cuando @baptiste señaló que sería útil mostrar la versión R pura de lo que estoy tratando de reproducir en rpy2. Aquí está la versión R pura:

 df <- data.frame(Species=c("setosa", "virginica", "versicolor"),X=c(1,2,3), Y=c(10,20,30)) p1 <- ggplot(iris) + geom_density(aes(x=Sepal.Width, colour=Species)) p2 <- ggplot(df) + geom_bar(aes(x=Species, y=X, colour=Species)) gA <- ggplotGrob(p1) gB <- ggplotGrob(p2) maxWidth = grid::unit.pmax(gA$widths[2:5], gB$widths[2:5]) gA$widths[2:5] <- as.list(maxWidth) gB$widths[2:5] <- as.list(maxWidth) grid.arrange(gA, gB, ncol=1) 

Creo que esto en general funciona para dos paneles con leyendas que tienen diferentes facetas en ggplot2 y quiero implementar esto en rpy2.

update3: casi lo puso a funcionar, al construir un FloatVector un elemento a la vez:

 maxWidth = [] for x, y in zip(gA.rx2("widths")[2:5], gB.rx2("widths")[2:5]): pmax = g.unit_pmax(x, y) print "PMAX: ", pmax val = pmax[1][0][0] print "VAL->", val maxWidth.append(val) gA[gA.names.index("widths")][2:5] = robj.FloatVector(maxWidth) gridExtra.grid_arrange(gA, gB, ncol=1) 

sin embargo, esto genera un volcado de segfault / core:

 Error: VECTOR_ELT() can only be applied to a 'list', not a 'double' *** longjmp causes uninitialized stack frame ***: python2.7 terminated ======= Backtrace: ========= /lib/x86_64-linux-gnu/libc.so.6(__fortify_fail+0x37)[0x7f83742e2817] /lib/x86_64-linux-gnu/libc.so.6(+0x10a78d)[0x7f83742e278d] /lib/x86_64-linux-gnu/libc.so.6(__longjmp_chk+0x33)[0x7f83742e26f3] ... 7f837591e000-7f8375925000 r--s 00000000 fc:00 1977264 /usr/lib/x86_64-linux-gnu/gconv/gconv-modules.cache 7f8375926000-7f8375927000 rwxp 00000000 00:00 0 7f8375927000-7f8375929000 rw-p 00000000 00:00 0 7f8375929000-7f837592a000 r--p 00022000 fc:00 917959 /lib/x86_64-linux-gnu/ld-2.15.so 7f837592a000-7f837592c000 rw-p 00023000 fc:00 917959 /lib/x86_64-linux-gnu/ld-2.15.so 7ffff4b96000-7ffff4bd6000 rw-p 00000000 00:00 0 [stack] 7ffff4bff000-7ffff4c00000 r-xp 00000000 00:00 0 [vdso] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] Aborted (core dumped) 

Actualización : la recompensa ha terminado. Aprecio las respuestas recibidas, pero ninguna de ellas usa rpy2 y esta es una pregunta rpy2, por lo que técnicamente las respuestas no están relacionadas con el tema Hay una solución de R simple para este problema (incluso si no hay una solución a esto en general como se señaló en @baptiste) y la pregunta es simplemente cómo traducirlo a rpy2

Alinear dos plots se vuelve mucho más complicado cuando se trata de facetas. No sé si hay una solución general, incluso en R. Considere este escenario,

 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) 

introduzca la descripción de la imagen aquí

Con algunos trabajos, puede comparar los anchos para el eje izquierdo y las leyendas (que pueden o no estar presentes en el lado derecho).

 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) 

introduzca la descripción de la imagen aquí

Tenga en cuenta que no he probado otros casos; Estoy seguro de que es muy fácil de romper. Por lo que entiendo de la documentación, rpy2 proporciona un mecanismo para utilizar un código R arbitrario , por lo que la conversión no debería ser un problema.

Divida las leyendas de los gráficos (vea ggplot leyenda y gráfico separados ), luego 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)) 

Esta es obviamente la implementación de R

Traducción no probada de la respuesta usando grid.arrange() . Sin embargo, los lados izquierdos de los gráficos (donde están las tags para el eje y) podrían no estar siempre alineados.

 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)))