Działki równoległe z ggplot2

339

Chciałbym umieścić dwa wykresy obok siebie za pomocą pakietu ggplot2 , tzn. Zrobić odpowiednik par(mfrow=c(1,2)).

Na przykład chciałbym, aby następujące dwa wykresy były wyświetlane obok siebie w tej samej skali.

x <- rnorm(100)
eps <- rnorm(100,0,.2)
qplot(x,3*x+eps)
qplot(x,2*x+eps)

Czy muszę umieścić je w tych samych data.frame?

qplot(displ, hwy, data=mpg, facets = . ~ year) + geom_smooth()
Christopher DuBois
źródło
Myślę, że możesz to zrobić za pomocą siatki. Czy ggplot2 jest trudnym wymaganiem?
JD Long
8
Nie. Ale poświęciłem już czas na ulepszanie qplotów, więc tak właśnie lubiłem. :-) Staram się bawić z ggplot.
Christopher DuBois,
1
Aby uzyskać ładny przegląd, zobacz winietę opakowania na jajko : Układanie wielu wykresów na stronie
Henrik

Odpowiedzi:

505

Wszelkie ggploty obok siebie (lub n wykresów na siatce)

Funkcja grid.arrange()w gridExtrapakiecie połączy wiele wykresów; w ten sposób umieścisz dwa obok siebie.

require(gridExtra)
plot1 <- qplot(1)
plot2 <- qplot(1)
grid.arrange(plot1, plot2, ncol=2)

Jest to przydatne, gdy dwa wykresy nie są oparte na tych samych danych, na przykład jeśli chcesz wykreślić różne zmienne bez użycia reshape ().

Spowoduje to wykreślenie wyniku jako efektu ubocznego. Aby wydrukować efekt uboczny w pliku, określ sterownik urządzenia (nppdf , pngitp), np

pdf("foo.pdf")
grid.arrange(plot1, plot2)
dev.off()

albo użyj arrangeGrob() w połączeniu z ggsave(),

ggsave("foo.pdf", arrangeGrob(plot1, plot2))

Jest to odpowiednik zrobienia dwóch różnych wykresów par(mfrow = c(1,2)). To nie tylko oszczędza czas na porządkowanie danych, jest to konieczne, gdy chcesz mieć dwa różne wykresy.


Dodatek: Korzystanie z aspektów

Aspekty są pomocne w tworzeniu podobnych wykresów dla różnych grup. Jest to wskazane poniżej w wielu odpowiedziach poniżej, ale chcę podkreślić to podejście za pomocą przykładów równoważnych z powyższymi wykresami.

mydata <- data.frame(myGroup = c('a', 'b'), myX = c(1,1))

qplot(data = mydata, 
    x = myX, 
    facets = ~myGroup)

ggplot(data = mydata) + 
    geom_bar(aes(myX)) + 
    facet_wrap(~myGroup)

Aktualizacja

plot_gridfunkcja w cowplotto warto sprawdzić jako alternatywa grid.arrange. Zobacz odpowiedź @ claus-wilke poniżej i tę winietę dla równoważnego podejścia; ale funkcja ta pozwala na dokładniejsze sterowanie lokalizacją i rozmiarem działki w oparciu o tę winietę .

David LeBauer
źródło
2
Gdy uruchomiłem kod za pomocą obiektów ggplot, sidebysideplot ma wartość null. Jeśli chcesz zapisać wynik w pliku, użyj gridArrange. Zobacz stackoverflow.com/questions/17059099/…
Jim
@Jim dziękuję za zwrócenie na to uwagi. Poprawiłem swoją odpowiedź. Daj mi znać, jeśli pozostaną jakieś pytania.
David LeBauer,
1
Czy grid.aarange jest teraz deprecjonowany?
Atticus29
?grid.arrangekaże mi myśleć, że ta funkcja nazywa się teraz arrangeGrob. Byłem w stanie zrobić to, co chciałem, robiąc a <- arrangeGrob(p1, p2)i wtedy print(a).
blakeoft
@blakeoft czy oglądałeś przykłady? grid.arrangejest nadal prawidłową, nieaktualną funkcją. Próbowałeś użyć tej funkcji? Co się stanie, jeśli nie to, czego się spodziewałeś.
David LeBauer
159

Wadą rozwiązań opartych na grid.arrangetym, że utrudniają oznaczanie działek literami (A, B itp.), Jak wymaga większość czasopism.

Napisałem pakiet cowplot , aby rozwiązać ten (i kilka innych) problemów, w szczególności funkcję plot_grid():

library(cowplot)

iris1 <- ggplot(iris, aes(x = Species, y = Sepal.Length)) +
  geom_boxplot() + theme_bw()

iris2 <- ggplot(iris, aes(x = Sepal.Length, fill = Species)) +
  geom_density(alpha = 0.7) + theme_bw() +
  theme(legend.position = c(0.8, 0.8))

plot_grid(iris1, iris2, labels = "AUTO")

wprowadź opis zdjęcia tutaj

Obiekt, który plot_grid()wraca to kolejny obiekt ggplot2, można zapisać go ggsave()jak zwykle:

p <- plot_grid(iris1, iris2, labels = "AUTO")
ggsave("plot.pdf", p)

Alternatywnie możesz użyć funkcji cowplot save_plot(), która jest cienkim owinięciem wokół ggsave(), co ułatwia uzyskanie prawidłowych wymiarów dla połączonych wykresów, np .:

p <- plot_grid(iris1, iris2, labels = "AUTO")
save_plot("plot.pdf", p, ncol = 2)

( ncol = 2Argument mówi, save_plot()że istnieją dwa wykresy obok siebie i save_plot()sprawia, że ​​zapisany obraz jest dwukrotnie szerszy.)

Bardziej szczegółowy opis układania wykresów w siatce znajduje się w tej winiecie. Istnieje również winieta wyjaśniająca, jak tworzyć fabuły ze wspólną legendą.

Często mylone jest to, że pakiet cowplot zmienia domyślny motyw ggplot2. Pakiet zachowuje się w ten sposób, ponieważ został pierwotnie napisany do użytku wewnętrznego w laboratorium i nigdy nie używamy domyślnego motywu. Jeśli powoduje to problemy, możesz zastosować jedno z następujących trzech podejść, aby je obejść:

1. Ustaw motyw ręcznie dla każdej działki. Myślę, że dobrą praktyką jest zawsze określanie konkretnego tematu dla każdej fabuły, tak jak to zrobiłem+ theme_bw() w powyższym przykładzie. Jeśli określisz konkretny motyw, domyślny motyw nie ma znaczenia.

2. Przywróć domyślny motyw z powrotem do domyślnego ggplot2. Możesz to zrobić za pomocą jednego wiersza kodu:

theme_set(theme_gray())

3. Wywołaj funkcje cowplot bez dołączania pakietu. Nie można również wywoływać library(cowplot)lub require(cowplot)zamiast tego wywoływać funkcje cowplot, przygotowując cowplot::. Na przykład powyższy przykład wykorzystujący domyślny motyw ggplot2 wyglądałby następująco:

## Commented out, we don't call this
# library(cowplot)

iris1 <- ggplot(iris, aes(x = Species, y = Sepal.Length)) +
  geom_boxplot()

iris2 <- ggplot(iris, aes(x = Sepal.Length, fill = Species)) +
  geom_density(alpha = 0.7) +
  theme(legend.position = c(0.8, 0.8))

cowplot::plot_grid(iris1, iris2, labels = "AUTO")

wprowadź opis zdjęcia tutaj

Aktualizacje:

  • Od wersji Cowplot 1.0 domyślny motyw ggplot2 nie jest już zmieniany.
  • Począwszy od ggplot2 3.0.0, wykresy można bezpośrednio oznaczać, patrz np . Tutaj.
Claus Wilke
źródło
Czy w wydruku wyjściowym cowplot usuwa motyw tła z obu wątków? czy jest jakaś alternatywa?
VAR121
@ VAR121 Tak, to jedna linia kodu. Wyjaśnione na końcu pierwszej części winiety wprowadzającej: cran.rstudio.com/web/packages/cowplot/vignettes/…
Claus Wilke
Czy dzięki temu pakietowi można mieć tę samą skalę y dla wszystkich wykresów?
Herman Toothrot
Musisz ręcznie dopasować skale y do siebie. Lub rozważ faceting.
Claus Wilke,
Możesz jednak ustawić ggtitle () dla każdego wątku przed użyciem grid.arrange ()?
Seanosapien
49

Z książki kucharskiej R Winstona Changa można skorzystać z następującej multiplotfunkcji

multiplot(plot1, plot2, cols=2)

multiplot <- function(..., plotlist=NULL, cols) {
    require(grid)

    # Make a list from the ... arguments and plotlist
    plots <- c(list(...), plotlist)

    numPlots = length(plots)

    # Make the panel
    plotCols = cols                          # Number of columns of plots
    plotRows = ceiling(numPlots/plotCols) # Number of rows needed, calculated from # of cols

    # Set up the page
    grid.newpage()
    pushViewport(viewport(layout = grid.layout(plotRows, plotCols)))
    vplayout <- function(x, y)
        viewport(layout.pos.row = x, layout.pos.col = y)

    # Make each plot, in the correct location
    for (i in 1:numPlots) {
        curRow = ceiling(i/plotCols)
        curCol = (i-1) %% plotCols + 1
        print(plots[[i]], vp = vplayout(curRow, curCol ))
    }

}
David LeBauer
źródło
24

Korzystając z pakietu patchwork , możesz po prostu użyć +operatora:

library(ggplot2)
library(patchwork)

p1 <- ggplot(mtcars) + geom_point(aes(mpg, disp))
p2 <- ggplot(mtcars) + geom_boxplot(aes(gear, disp, group = gear))


p1 + p2

coś pozszywanego z kawałków

Deena
źródło
Dla kompletności patchwork jest teraz również w CRAN. Mam nadzieję, że jesteś zadowolony z mojej małej edycji
Tjebo
18

Tak, uważa, że ​​musisz odpowiednio uporządkować swoje dane. Jednym ze sposobów byłoby:

X <- data.frame(x=rep(x,2),
                y=c(3*x+eps, 2*x+eps),
                case=rep(c("first","second"), each=100))

qplot(x, y, data=X, facets = . ~ case) + geom_smooth()

Jestem pewien, że są lepsze sztuczki w plyr lub przekształceniu - wciąż nie jestem zbyt szybki na tych wszystkich potężnych pakietach Hadley.

Dirk Eddelbuettel
źródło
16

Korzystając z pakietu zmiany kształtu, możesz zrobić coś takiego.

library(ggplot2)
wide <- data.frame(x = rnorm(100), eps = rnorm(100, 0, .2))
wide$first <- with(wide, 3 * x + eps)
wide$second <- with(wide, 2 * x + eps)
long <- melt(wide, id.vars = c("x", "eps"))
ggplot(long, aes(x = x, y = value)) + geom_smooth() + geom_point() + facet_grid(.~ variable)
Thierry
źródło
12

Warto też wspomnieć o pakiecie konfiguracji wielopanelowej . Zobacz także tę odpowiedź .

library(ggplot2)
theme_set(theme_bw())

q1 <- ggplot(mtcars) + geom_point(aes(mpg, disp))
q2 <- ggplot(mtcars) + geom_boxplot(aes(gear, disp, group = gear))
q3 <- ggplot(mtcars) + geom_smooth(aes(disp, qsec))
q4 <- ggplot(mtcars) + geom_bar(aes(carb))

library(magrittr)
library(multipanelfigure)
figure1 <- multi_panel_figure(columns = 2, rows = 2, panel_label_type = "none")
# show the layout
figure1

figure1 %<>%
  fill_panel(q1, column = 1, row = 1) %<>%
  fill_panel(q2, column = 2, row = 1) %<>%
  fill_panel(q3, column = 1, row = 2) %<>%
  fill_panel(q4, column = 2, row = 2)
figure1

# complex layout
figure2 <- multi_panel_figure(columns = 3, rows = 3, panel_label_type = "upper-roman")
figure2

figure2 %<>%
  fill_panel(q1, column = 1:2, row = 1) %<>%
  fill_panel(q2, column = 3, row = 1) %<>%
  fill_panel(q3, column = 1, row = 2) %<>%
  fill_panel(q4, column = 2:3, row = 2:3)
figure2

Utworzono 06.07.2018 przez pakiet reprezentx (v0.2.0.9000).

Tung
źródło
9

ggplot2 opiera się na grafice siatki, która zapewnia inny system do rozmieszczania wykresów na stronie. par(mfrow...)Polecenie nie ma bezpośredniego odpowiednika jako obiekty siatka (zwane grobs ) nie są koniecznie narysowane bezpośrednio, lecz mogą być przechowywane i manipulacji, jak zwykłe obiekty R, po czym przekształca się w produkcji graficznej. Umożliwia to większą elastyczność niż teraz w losowaniu modelu grafiki podstawowej, ale strategia jest z konieczności nieco inna.

Napisałem, grid.arrange()aby zapewnić prosty interfejs jak najbliżej par(mfrow). W najprostszej formie kod wyglądałby następująco:

library(ggplot2)
x <- rnorm(100)
eps <- rnorm(100,0,.2)
p1 <- qplot(x,3*x+eps)
p2 <- qplot(x,2*x+eps)

library(gridExtra)
grid.arrange(p1, p2, ncol = 2)

wprowadź opis zdjęcia tutaj

Więcej opcji opisano szczegółowo w tej winiecie .

Jednym z powszechnych zarzutów jest to, że wykresy niekoniecznie są wyrównane, np. Gdy mają etykiety osi o różnych rozmiarach, ale jest to z założenia: grid.arrange nie podejmuje prób specjalnych obiektów ggplot2 i traktuje je jednakowo do innych obiektów (na przykład wykresy sieci ). Po prostu umieszcza obiekty w prostokątnym układzie.

Dla szczególnego przypadku obiektów ggplot2 napisałem inną funkcję ggarrangeo podobnym interfejsie, która próbuje wyrównać panele wykresów (w tym wykresy fasetowane) i stara się przestrzegać proporcji, gdy jest zdefiniowany przez użytkownika.

library(egg)
ggarrange(p1, p2, ncol = 2)

Obie funkcje są kompatybilne z ggsave(). Aby uzyskać ogólny przegląd różnych opcji i kontekst historyczny, ta winieta zawiera dodatkowe informacje .

baptiste
źródło
9

Aktualizacja: Ta odpowiedź jest bardzo stara. gridExtra::grid.arrange()jest teraz zalecanym podejściem. Zostawiam to tutaj na wypadek, gdyby było to przydatne.


Stephen Turner opublikował arrange()funkcję na blogu Getting Genetics Done (instrukcje dotyczące aplikacji znajdują się w poście)

vp.layout <- function(x, y) viewport(layout.pos.row=x, layout.pos.col=y)
arrange <- function(..., nrow=NULL, ncol=NULL, as.table=FALSE) {
 dots <- list(...)
 n <- length(dots)
 if(is.null(nrow) & is.null(ncol)) { nrow = floor(n/2) ; ncol = ceiling(n/nrow)}
 if(is.null(nrow)) { nrow = ceiling(n/ncol)}
 if(is.null(ncol)) { ncol = ceiling(n/nrow)}
        ## NOTE see n2mfrow in grDevices for possible alternative
grid.newpage()
pushViewport(viewport(layout=grid.layout(nrow,ncol) ) )
 ii.p <- 1
 for(ii.row in seq(1, nrow)){
 ii.table.row <- ii.row 
 if(as.table) {ii.table.row <- nrow - ii.table.row + 1}
  for(ii.col in seq(1, ncol)){
   ii.table <- ii.p
   if(ii.p > n) break
   print(dots[[ii.table]], vp=vp.layout(ii.table.row, ii.col))
   ii.p <- ii.p + 1
  }
 }
}
Jeromy Anglim
źródło
9
jest to w zasadzie bardzo nieaktualna wersja grid.arrange(szkoda, że ​​nie opublikowałem go w tym czasie na listach mailingowych - nie ma możliwości aktualizacji tych zasobów online), wersja spakowana jest lepszym wyborem, jeśli mnie o to poprosisz
baptiste
4

Używanie tidyverse:

x <- rnorm(100)
eps <- rnorm(100,0,.2)
df <- data.frame(x, eps) %>% 
  mutate(p1 = 3*x+eps, p2 = 2*x+eps) %>% 
  tidyr::gather("plot", "value", 3:4) %>% 
  ggplot(aes(x = x , y = value)) + 
    geom_point() + 
    geom_smooth() + 
    facet_wrap(~plot, ncol =2)

df

wprowadź opis zdjęcia tutaj

błyszczący
źródło
1

Powyższe rozwiązania mogą nie być wydajne, jeśli chcesz wykreślić wiele wykresów ggplot za pomocą pętli (np. Jak tutaj: Tworzenie wielu wykresów w ggplot o różnych wartościach osi Y za pomocą pętli ), co jest pożądanym krokiem w analizie nieznanego ( lub duże) zestawy danych (np. gdy chcesz wykreślić Liczby wszystkich zmiennych w zestawie danych).

Poniższy kod pokazuje, jak to zrobić za pomocą wspomnianego powyżej „multiplikatora ()”, którego źródło znajduje się tutaj: http://www.cookbook-r.com/Graphs/Multiple_graphs_on_one_page_(ggplot2) :

plotAllCounts <- function (dt){   
  plots <- list();
  for(i in 1:ncol(dt)) {
    strX = names(dt)[i]
    print(sprintf("%i: strX = %s", i, strX))
    plots[[i]] <- ggplot(dt) + xlab(strX) +
      geom_point(aes_string(strX),stat="count")
  }

  columnsToPlot <- floor(sqrt(ncol(dt)))
  multiplot(plotlist = plots, cols = columnsToPlot)
}

Teraz uruchom funkcję - aby uzyskać Liczby dla wszystkich zmiennych drukowanych za pomocą ggplot na jednej stronie

dt = ggplot2::diamonds
plotAllCounts(dt)

Należy zauważyć, że:
użycie aes(get(strX)), którego normalnie używasz w pętlach podczas pracy ggplot, w powyższym kodzie zamiast aes_string(strX)NIE narysuje pożądanych wykresów. Zamiast tego wykreśli ostatni wątek wiele razy. Nie dowiedziałem się, dlaczego - może to zrobić aesi aes_stringzostaną wezwani ggplot.

W przeciwnym razie mam nadzieję, że funkcja okaże się przydatna.

IVIM
źródło
1
Zauważ, że twój kod uprawia plotsobiekty, w for-loopktórych jest wysoce nieefektywny i nie jest zalecany w R. Zapoznaj się z tymi świetnymi postami, aby dowiedzieć się, jak to zrobić lepiej: Efektywna akumulacja w R , Zastosowanie funkcji nad wierszami ramki danych i Zorientowane
Tung
Bardziej efektywnym sposobem na przechodzenie między zmiennymi jest stosowanie tidy evaluationpodejścia, które jest dostępne od ggplot2 v.3.0.0 stackoverflow.com/a/52045613/786542
Tung
0

Z mojego doświadczenia gridExtra: grid.arrange działa idealnie, jeśli próbujesz generować wykresy w pętli.

Krótki fragment kodu:

gridExtra::grid.arrange(plot1, plot2, ncol = 2)
Mayank Agrawal
źródło
Jak poprawia się twoja odpowiedź w stosunku do odpowiedzi baptysty z 2 grudnia 17 o 4:20? Twoja odpowiedź wydaje się być duplikatem. Sprawdź, co stanowi akceptowalną odpowiedź tutaj. Jak odpowiedzieć
Peter
Nie byłem w stanie podzielić fabuły w razie potrzeby w pętli, i stąd sugestia. Początkowo napisałem pełny fragment mojej pętli for wraz z jego implementacją, ale na razie zdecydowałem się go nie stosować. Zaktualizuje pełny kod za około tydzień.
Mayank Agrawal
Próbowałem to zrobić za pomocą samego pakietu cowplot, ale nie powiodło się. Podczas mojego szybkiego skanowania nikt nie wspominał o wielu rozwiązaniach do kreślenia w pętli for i stąd mój komentarz. Prześlij mi komentarz, jeśli się mylę.
Mayank Agrawal
Jeśli kod w twojej odpowiedzi zawiera pętlę for, byłoby inaczej.
Peter
Zaktualizowałbym go za tydzień w tym miejscu i cały projekt na Kaggle. Twoje zdrowie.
Mayank Agrawal
-3

cowplotPakiet daje piękny sposób to zrobić, w sposób odpowiadający publikacji.

x <- rnorm(100)
eps <- rnorm(100,0,.2)
A = qplot(x,3*x+eps, geom = c("point", "smooth"))+theme_gray()
B = qplot(x,2*x+eps, geom = c("point", "smooth"))+theme_gray()
cowplot::plot_grid(A, B, labels = c("A", "B"), align = "v")

wprowadź opis zdjęcia tutaj

tim
źródło
3
Zobacz także bardziej szczegółową odpowiedź i uzasadnienie autorów pakietu powyżej stackoverflow.com/a/31223588/199217
David LeBauer