2013-07-19 12 views
11

Łączę dwa odrębne wątki w układ siatki z grid zgodnie z sugestią @lgautier w rpy2 przy użyciu Pythona. Górna działka jest gęstość i a dolny wykres słupkowy:wyrównywanie wyraźnych wykresów niefazowych w ggplot2 przy użyciu Rpy2 w Pythonie

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) 

co mam jest blisko tego, co chcę, ale działki nie są dokładnie dopasowane (pokazany przez strzałki, które dodałem):

enter image description here

Chciałbym, aby 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 gdy dodasz warunki do wykresu słupkowego poniżej lub zmusisz je do uniknięcia wykresów słupkowych z position_dodge różnice mogą stać się bardzo duże, a wykresy nie są wyrównane.

Standardowe rozwiązanie ggplot nie da się łatwo przełożyć na rpy2:

arrange wydaje się być grid_arrange w gridExtra:

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

ggplotGrob nie jest dostępne od ggplot2, ale mogą być dostępne tak:

>>> ggplot2.ggplot2.ggplotGrob 

Chociaż nie mam pojęcia, jak uzyskać dostęp do 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, w jaki sposób tłumaczyć standardowe rozwiązanie ggplot2 do rpy2.

edycja: jak inni wskazali grid::unit.pmax jest . Nadal nie wiem, jak uzyskać dostęp do parametru rpy2 o wartości widths obiektów grob, co jest konieczne 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]) 

gA.widths nie jest poprawną składnią. W grob obiektów gA drukuje jak:

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] 

aktualizacja: pewien postęp w sprawie dostępu szerokościach, ale wciąż nie może tłumaczyć rozwiązanie. 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. Wyjście jest:

[[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: realizowane jako @baptiste podkreślić, że przydatne byłoby, aby pokazać czystą wersję R, 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 w ogólnych pracach na dwa panele z legendami, które mają różne aspekty w ggplot2 i chcę, aby zaimplementować to w rpy2.

update3: prawie dostał go do pracy, budując FloatVector aż 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 to generuje zrzut segfault/Rdzeń:

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 przetłumaczyć je na rpy2

+6

osoby, które oznaczyły to jako duplikat, mają zerową ocenę subtelności tłumaczenia kodu z R na Rpy2. Tak, czasami tłumaczenie jest łatwe, ale czasami jest 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

+0

'unit.pmax' to funkcja oddzielona od' jednostki' (chociaż oba są w pakiecie 'grid'). Czy w pythonie dostępne jest 'grid.unit.pmax' po' grid = importr ("grid") '? Czy też "rpy" robi tłumaczenie na kropki w nazwach funkcji, które nie są związane z wysyłką metody S3? –

+2

@BrianDiggs wydaje się, że 'grid :: unit.pmax' stanie się' grid.unit_pmax' – baptiste

Odpowiedz

2

Rozdzielić legendy od wykresów (zobacz ggplot separate legend and plot), a następnie użyć 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)) 

to jest oczywiście realizacja R.

+0

dziękuję, ale to wcale 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

6

Wyrównywanie dwóch poletek staje się znacznie trudniejsze, gdy uwzględniane są aspekty. Nie wiem, czy istnieje ogólne rozwiązanie, nawet w R. Rozważmy następujący 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

Z niektórych prac, można porównać do szerokości osi lewo i legendy (które mogą lub nie mogą być obecne 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 pozostałych przypadkach; Jestem pewien, że bardzo łatwo jest się zepsuć. O ile rozumiem z dokumentacji, rpy2 zapewnia mechanizm do use an arbitrary piece of R code, więc konwersja nie powinna stanowić problemu.

+0

Ale ludzie tworzą cały czas złożoną sieć działek w R. przez cały czas .. więc co to 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

+0

pytania o twój przykład: jeśli usunąłbyś legendę dla 'am', czy byłbyś w stanie wyrównać je w R? – user248237dfsf

+1

Kolejna obserwacja: jak ogólna jest ta metoda wykorzystywania różnych typów geomów w różnych podzbiorach danych? Czy pozwoliłoby to na utworzenie wykresu, który zamierzałem umieścić w oryginalnym wpisie, używając kombinacji 'geom_density' i' geom_bar'? http://stackoverflow.com/questions/7903972/can-you-specify-different-geoms-for-different-facets-in-a-plplot – user248237dfsf

1

Niewyjańczone tłumaczenie odpowiedzi za pomocą '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))) 
Powiązane problemy