12 python r ggplot2 rpy2 gtable
我正在将两个不同的图组合成一个网格布局,grid正如@lgautier在rpy2中使用python所建议的那样.顶部的图是密度,底部是条形图:
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)
Run Code Online (Sandbox Code Playgroud)
我得到的是接近我想要的但是图形并没有完全对齐(我添加的箭头显示):

我希望情节区域(而不是传说)完全匹配.怎么能实现呢?这里的差异不是很大,但是当您在下面的条形图中添加条件或使它们躲避条形图时position_dodge,差异会变得非常大并且图形不对齐.
标准的ggplot解决方案无法轻松转换为rpy2:
arrange似乎grid_arrange在gridExtra:
>>> gridExtra = importr("gridExtra")
>>> gridExtra.grid_arrange
<SignatureTranslatedFunction - Python:0x430f518 / R:0x396f678>
Run Code Online (Sandbox Code Playgroud)
ggplotGrob无法访问ggplot2,但可以像这样访问:
>>> ggplot2.ggplot2.ggplotGrob
Run Code Online (Sandbox Code Playgroud)
虽然我不知道如何访问grid::unit.pmax:
>>> grid.unit
<bound method type.unit of <class 'rpy2.robjects.lib.grid.Unit'>>
>>> 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
Run Code Online (Sandbox Code Playgroud)
所以目前尚不清楚如何将标准ggplot2解决方案转换为rpy2.
编辑:正如其他人指出的grid::unit.pmax是grid.unit_pmax.我仍然不知道如何在rpy2中访问对象的widths参数grob,这对于将绘图的宽度设置为更宽的绘图的宽度是必要的.我有:
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])
Run Code Online (Sandbox Code Playgroud)
这gA.widths不是正确的语法.该grob对象gA打印为:
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]
Run Code Online (Sandbox Code Playgroud)
更新:在访问宽度方面取得了一些进展,但仍无法翻译解决方案.要设置grobs的宽度,我有:
# 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)
Run Code Online (Sandbox Code Playgroud)
它运行但不起作用.输出是:
[[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))
Run Code Online (Sandbox Code Playgroud)
update2:实现为@baptiste指出,显示我试图在rpy2中重现的纯R版本会很有帮助.这是纯R版本:
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)
Run Code Online (Sandbox Code Playgroud)
我认为这通常适用于两个面板,其中的图例在ggplot2中有不同的方面,我想在rpy2中实现它.
update3:通过一次构建FloatVector一个元素,几乎让它工作:
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)
Run Code Online (Sandbox Code Playgroud)
但是这会生成段错误/核心转储:
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)
Run Code Online (Sandbox Code Playgroud)
更新:赏金结束.我很欣赏收到的答案,但是没有一个答案使用rpy2,这是一个rpy2问题,所以从技术上讲,答案不是主题.对于这个问题有一个简单的R解决方案(即使@baptiste指出一般没有解决方案),问题只是如何将其转换为rpy2
当涉及方面时,对齐两个图变得更加棘手.我不知道是否有一般的解决方案,即使在R中.考虑这种情况,
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)
Run Code Online (Sandbox Code Playgroud)

通过一些工作,您可以比较左轴和图例的宽度(可能在右侧可能存在也可能不存在).
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)
Run Code Online (Sandbox Code Playgroud)

请注意,我没有测试过其他案例; 我敢肯定它很容易打破.据我所知,从文档中rpy2提供了一种使用任意R代码的机制,因此转换不应成为问题.
从图中分割图例(请参阅ggplot 分离图例和图),然后使用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))
Run Code Online (Sandbox Code Playgroud)
这显然是 R实现。