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