Pytanie wyrównywanie różnych wykresów niefazowych w ggplot2 przy użyciu Rpy2 w Pythonie


Łączę dwie odrębne działki z układem siatki grid zgodnie z sugestią @lgautier w rpy2 przy użyciu Pythona. Górna powierzchnia jest gęstością, a dolna jest wykresem słupkowym:

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)

to, co otrzymuję, jest bliskie temu, co chcę, ale wykresy nie są dokładnie wyrównane (pokazane strzałkami, które dodałem):

enter image description here

Chciałbym, żeby obszary działek (nie legendy) dokładnie się zgadzały. Jak można to osiągnąć? Różnica tutaj nie jest tak duża, ale w miarę dodawania warunków do wykresu słupkowego poniżej lub do zignorowania wykresów słupkowych z position_dodge różnice mogą stać się bardzo duże, a wykresy nie są wyrównane.

Standardowego rozwiązania ggplota nie można łatwo przetłumaczyć na rpy2:

arrange Wydaje się, że grid_arrange w gridExtra:

>>> gridExtra = importr("gridExtra")
>>> gridExtra.grid_arrange
<SignatureTranslatedFunction - Python:0x430f518 / R:0x396f678>

ggplotGrob jest niedostępne z ggplot2, ale można uzyskać do niego dostęp w następujący sposób:

>>> ggplot2.ggplot2.ggplotGrob

Chociaż nie mam pojęcia, jak uzyskać dostęp 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

więc nie jest jasne, jak przetłumaczyć standardowy roztwór ggplot2 na rpy2.

edytować: jak zauważyli inni grid::unit.pmax jest grid.unit_pmax. Nadal nie wiem, jak uzyskać dostęp w rpy2 widths parametr z grob obiekty, które są niezbędne do ustawienia szerokości wykresów na szerszy wykres. Mam:

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

The gA.widths nie jest poprawna składnia. The grob obiekt gA drukuje jako:

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]

update: poczyniła pewne postępy w dostępie do szerokości, ale nadal nie może przetłumaczyć rozwiązania. Aby ustawić szerokość grobs, mam:

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

Działa, ale nie działa. Wynik to:

[[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: zrealizowany jako @baptiste zwrócił uwagę, że byłoby pomocne pokazanie czystej wersji R tego, co próbuję odtworzyć w rpy2. Oto czysta wersja 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)

Myślę, że to ogólnie działa na dwóch panelach z legendami, które mają różne aspekty w ggplot2 i chcę to zaimplementować w rpy2.

update3: prawie dostał go do działania, budując a FloatVector jeden element na raz:

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)

jednak generuje to zrzut 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)

Aktualizacja: nagroda została zakończona. Doceniam otrzymane odpowiedzi, ale żadna z odpowiedzi nie używa rpy2 i jest to pytanie rpy2, więc technicznie odpowiedzi nie dotyczą tematu. Istnieje proste rozwiązanie tego problemu (nawet jeśli nie ma na to rozwiązania w ogóle, jak wskazał @baptiste), a pytanie brzmi po prostu jak je przetłumaczyć na rpy2


11
2017-07-19 00:51


pochodzenie


ci, którzy oznaczyli to jako duplikat, nie zważają na subtelności tłumaczenia kodu z R na Rpy2. Tak, czasami tłumaczenie jest łatwe, ale czasami jest to sprzeczne z intuicją. To jest pytanie rpy2. To nie jest proste pytanie ggplot2. Po prostu przeszkadzasz i blokujesz proces pytań i odpowiedzi bez żadnego powodu. - user248237dfsf
unit.pmax jest funkcją oddzielną od unit (chociaż oba są w grid pakiet). Czy istnieje grid.unit.pmax dostępny w Pythonie po grid=importr("grid")? Lub robi rpy zrobić tłumaczenie na kropki w nazwach funkcji, które nie są związane z wysyłką metody S3? - Brian Diggs
Wydaje się, że @BrianDiggs grid::unit.pmax stanie się grid.unit_pmax - baptiste
@ Baptiste: masz rację, dziękuję, ale nadal nie jestem pewien, jak uzyskać dostęp do grob obiekty z rpy2, zobacz zmiany - user248237dfsf
Obawiam się, że nie mogę pomóc; ta składnia rpy jest całkowicie obca dla mnie. - baptiste


Odpowiedzi:


Wyrównanie dwóch działek staje się znacznie trudniejsze, gdy zaangażowane są aspekty. Nie wiem, czy istnieje ogólne rozwiązanie, nawet w R. Rozważmy ten scenariusz,

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

Przy niektórych pracach możesz porównać szerokości lewej osi i legend (które mogą, ale nie muszą, znajdować się po prawej stronie).

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

Zauważ, że nie testowałem innych przypadków; Jestem pewien, że bardzo łatwo jest się zepsuć. O ile rozumiem z dokumentów, rpy2 zapewnia mechanizm użyj dowolnego fragmentu kodu R, więc konwersja nie powinna stanowić problemu.


6
2017-07-21 01:24



Ale ludzie przez cały czas budują złożone siatki działek w R ... więc jaki jest ogólny sposób na zrobienie tego? Czy istnieje sposób na uzyskanie oryginalnego wykresu, który zrobiłem w poście za pomocą faset, wszystko w ramach fasetowania ggplota (z facet_wrap i facet_grid), aby całkowicie uniknąć oddzielnej siatki? Być może po prostu nie znam zaawansowanych zastosowań fasetowania - user248237dfsf
pytanie o twój przykład: jeśli usunięto legendę am, czy byłbyś w stanie wyrównać je w R? - user248237dfsf
kolejna obserwacja: jak ogólna jest ta metoda wykorzystywania różnych typów geomów w różnych podzbiorach danych? pozwoliłoby to na utworzenie wykresu zamierzonego w oryginalnym poście za pomocą kombinacji geom_density i geom_bar? stackoverflow.com/questions/7903972/... - user248237dfsf
Nie jestem pewien, czy rozumiem, co masz na myśli, ale w żadnym wypadku nie będziesz w stanie mieć jednej działki z 3 ściankami w górnym rzędzie i jednego aspektu, który obejmuje całą szerokość w dolnym rzędzie. Do tego trzeba użyć dwóch działek. - baptiste


Podziel legendy z fabuł (zob ggplot oddzielna legenda i fabuła), a następnie użyj 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))

Jest to oczywiście Rrealizacja.


2
2017-07-19 01:13



dzięki, ale nie jest jasne, jak przetłumaczyć to na rpy2 i chciałbym uniknąć zdefiniowania nowych czystych funkcji R, ponieważ to komplikuje tłumaczenie jeszcze bardziej - user248237dfsf


Niewłaściwe tłumaczenie odpowiedzi za pomocą gridExtra„s grid.arrange(). Lewa strona wykresu (gdzie znajdują się etykiety dla osi y) może nie zawsze być wyrównana.

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

1
2017-08-31 01:20