Allinea più trame in ggplot2 quando alcune hanno legende e altre no

Ho usato il metodo indicato qui per allineare i grafici che condividono la stessa ascissa.

Ma non riesco a farlo funzionare quando alcuni dei miei grafici hanno una leggenda e altri no.

Ecco un esempio:

library(ggplot2) library(reshape2) library(gridExtra) x = seq(0, 10, length.out = 200) y1 = sin(x) y2 = cos(x) y3 = sin(x) * cos(x) df1 <- data.frame(x, y1, y2) df1 <- melt(df1, id.vars = "x") g1 <- ggplot(df1, aes(x, value, color = variable)) + geom_line() print(g1) df2 <- data.frame(x, y3) g2 <- ggplot(df2, aes(x, y3)) + geom_line() print(g2) gA <- ggplotGrob(g1) gB <- ggplotGrob(g2) maxWidth <- grid::unit.pmax(gA$widths[2:3], gB$widths[2:3]) gA$widths[2:3] <- maxWidth gB$widths[2:3] <- maxWidth g <- arrangeGrob(gA, gB, ncol = 1) grid::grid.newpage() grid::grid.draw(g) 

Usando questo codice, ho il seguente risultato:

inserisci la descrizione dell'immagine qui

Quello che vorrei è avere l’asse x allineato e la legenda mancante riempita da uno spazio vuoto. È ansible?

Modificare:

La soluzione più elegante proposta è quella di Sandy Muspratt di seguito.

L’ho implementato e funziona abbastanza bene con due grafici.

Poi ho provato con tre, avendo diverse dimensioni di legenda e non funziona più:

 library(ggplot2) library(reshape2) library(gridExtra) x = seq(0, 10, length.out = 200) y1 = sin(x) y2 = cos(x) y3 = sin(x) * cos(x) y4 = sin(2*x) * cos(2*x) df1 <- data.frame(x, y1, y2) df1 <- melt(df1, id.vars = "x") g1 <- ggplot(df1, aes(x, value, color = variable)) + geom_line() g1 <- g1 + theme_bw() g1 <- g1 + theme(legend.key = element_blank()) g1 <- g1 + ggtitle("Graph 1", subtitle = "With legend") df2 <- data.frame(x, y3) g2 <- ggplot(df2, aes(x, y3)) + geom_line() g2 <- g2 + theme_bw() g2 <- g2 + theme(legend.key = element_blank()) g2 <- g2 + ggtitle("Graph 2", subtitle = "Without legend") df3 <- data.frame(x, y3, y4) df3 <- melt(df3, id.vars = "x") g3 <- ggplot(df3, aes(x, value, color = variable)) + geom_line() g3 <- g3 + theme_bw() g3 <- g3 + theme(legend.key = element_blank()) g3 <- g3 + scale_color_discrete("This is indeed a very long title") g3 <- g3 + ggtitle("Graph 3", subtitle = "With legend") gA <- ggplotGrob(g1) gB <- ggplotGrob(g2) gC <- ggplotGrob(g3) gB = gtable::gtable_add_cols(gB, sum(gC$widths[7:8]), 6) maxWidth <- grid::unit.pmax(gA$widths[2:5], gB$widths[2:5], gC$widths[2:5]) gA$widths[2:5] <- maxWidth gB$widths[2:5] <- maxWidth gC$widths[2:5] <- maxWidth g <- arrangeGrob(gA, gB, gC, ncol = 1) grid::grid.newpage() grid::grid.draw(g) 

Ciò risulta nella seguente figura: inserisci la descrizione dell'immagine qui

Il mio problema principale con le risposte trovate qui e in altre domande riguardanti l’argomento è che le persone “giocano” parecchio con il vettore myGrob$widths senza spiegare in realtà perché lo stanno facendo. Ho visto persone modificare myGrob$widths[2:5] altri myGrob$widths[2:3] e proprio non riesco a trovare alcuna documentazione che spiega quali siano queste colonne.

Il mio objective è creare una funzione generica come:

 AlignPlots <- function(...) { # Retrieve the list of plots to align plots.list <- list(...) # Initialize the lists grobs.list <- list() widths.list <- list() # Collect the widths for each grob of each plot max.nb.grobs <- 0 longest.grob <- NULL for (i in 1:length(plots.list)){ if (i != length(plots.list)) { plots.list[[i]] <- plots.list[[i]] + theme(axis.title.x = element_blank()) } grobs.list[[i]] <- ggplotGrob(plots.list[[i]]) current.grob.length  max.nb.grobs) { max.nb.grobs <- current.grob.length longest.grob <- grobs.list[[i]] } widths.list[[i]] <- grobs.list[[i]]$widths[2:5] } # Get the max width maxWidth <- do.call(grid::unit.pmax, widths.list) # Assign the max width to each grob for (i in 1:length(grobs.list)){ if(length(grobs.list[[i]]) < max.nb.grobs) { grobs.list[[i]] <- gtable::gtable_add_cols(grobs.list[[i]], sum(longest.grob$widths[7:8]), 6) } grobs.list[[i]]$widths[2:5] <- as.list(maxWidth) } # Generate the plot g <- do.call(arrangeGrob, c(grobs.list, ncol = 1)) return(g) } 

Espandendo la risposta di @ Axeman, puoi fare tutto questo con cowplot senza mai dover usare draw_plot direttamente. In sostanza, basta creare la trama in due colonne, una per le trame stesse e una per le leggende, quindi posizionarle una accanto all’altra. Tieni presente che, poiché g2 non ha legende, sto utilizzando un object ggplot vuoto per mantenere il posto di quella legenda nella colonna delle legende.

 library(cowplot) theme_set(theme_minimal()) plot_grid( plot_grid( g1 + theme(legend.position = "none") , g2 , g3 + theme(legend.position = "none") , ncol = 1 , align = "hv") , plot_grid( get_legend(g1) , ggplot() , get_legend(g3) , ncol =1) , rel_widths = c(7,3) ) 

inserisci la descrizione dell'immagine qui

Il vantaggio principale qui, secondo me, è la possibilità di impostare e saltare le legende come necessario per ciascuna sottotrama.

Di nota è che, se tutti i grafici hanno una legenda, plot_grid gestisce l’allineamento per te:

 plot_grid( g1 , g3 , align = "hv" , ncol = 1 ) 

inserisci la descrizione dell'immagine qui

È solo la leggenda mancante in g2 che causa problemi.

Pertanto, se aggiungi una legenda fittizia a g2 e nascondi gli elementi, puoi ottenere plot_grid per eseguire tutto l’allineamento per te, invece di preoccuparti di regolare manualmente rel_widths se cambi la dimensione dell’output

 plot_grid( g1 , g2 + geom_line(aes(color = "Test")) + scale_color_manual(values = NA) + theme(legend.text = element_blank() , legend.title = element_blank()) , g3 , align = "hv" , ncol = 1 ) 

inserisci la descrizione dell'immagine qui

Questo significa anche che puoi facilmente avere più di una colonna, ma mantenere le aree del tracciato uguali. Semplicemente rimuovendo , ncol = 1 dall’alto produce un , ncol = 1 con 2 colonne, ma con spaziatura ancora corretta (sebbene sia necessario regolare le proporzioni per renderlo utilizzabile):

inserisci la descrizione dell'immagine qui

Come suggerito da @baptiste, puoi anche spostare le legende in modo che siano tutte allineate a sinistra nella porzione “leggenda” della trama aggiungendo il theme(legend.justification = "left") ai grafici con le legende ( o in theme_set per impostare globalmente), come questo:

 plot_grid( g1 + theme(legend.justification = "left") , g2 + geom_line(aes(color = "Test")) + scale_color_manual(values = NA) + theme(legend.text = element_blank() , legend.title = element_blank()) , g3 + theme(legend.justification = "left") , align = "hv" , ncol = 1 ) 

inserisci la descrizione dell'immagine qui

Ora potrebbero esserci modi più semplici per farlo, ma il tuo codice non era molto sbagliato.

Dopo esserti assicurato che le larghezze delle colonne 2 e 3 in gA siano le stesse di quelle in gB, controlla le larghezze dei due gtables: gA gA$widths width e gB$widths . Noterai che gA gtable ha due colonne addizionali non presenti nel gb gtable, ovvero le larghezze 7 e 8. Usa la funzione gtable_add_cols() per aggiungere le colonne al gtable gB:

 gB = gtable::gtable_add_cols(gB, sum(gA$widths[7:8]), 6) 

Quindi procedere con arrangeGrob() ….

Modifica: per una soluzione più generale

Il pacchetto egg (disponibile su github) è sperimentale e fragile, ma funziona bene con il set di trame riveduto.

 # install.package(devtools) devtools::install_github("baptiste/egg") library(egg) grid.newpage() grid.draw(ggarrange(g1,g2,g3, ncol = 1)) 

inserisci la descrizione dell'immagine qui

Grazie a questo e quello , pubblicato nei commenti (e poi rimosso), ho trovato la seguente soluzione generale.

Mi piace la risposta di Sandy Muspratt e il pacchetto di uova sembra fare il lavoro in modo molto elegante, ma poiché è “sperimentale e fragile”, ho preferito utilizzare questo metodo:

 #' Vertically align a list of plots. #' #' This function aligns the given list of plots so that the x axis are aligned. #' It assumes that the graphs share the same range of x data. #' #' @param ... The list of plots to align. #' @param globalTitle The title to assign to the newly created graph. #' @param keepTitles TRUE if you want to keep the titles of each individual #' plot. #' @param keepXAxisLegends TRUE if you want to keep the x axis labels of each #' individual plot. Otherwise, they are all removed except the one of the graph #' at the bottom. #' @param nb.columns The number of columns of the generated graph. #' #' @return The gtable containing the aligned plots. #' @examples #' g <- VAlignPlots(g1, g2, g3, globalTitle = "Alignment test") #' grid::grid.newpage() #' grid::grid.draw(g) VAlignPlots <- function(..., globalTitle = "", keepTitles = FALSE, keepXAxisLegends = FALSE, nb.columns = 1) { # Retrieve the list of plots to align plots.list <- list(...) # Remove the individual graph titles if requested if (!keepTitles) { plots.list <- lapply(plots.list, function(x) x <- x + ggtitle("")) plots.list[[1]] <- plots.list[[1]] + ggtitle(globalTitle) } # Remove the x axis labels on all graphs, except the last one, if requested if (!keepXAxisLegends) { plots.list[1:(length(plots.list)-1)] <- lapply(plots.list[1:(length(plots.list)-1)], function(x) x <- x + theme(axis.title.x = element_blank())) } # Builds the grobs list grobs.list <- lapply(plots.list, ggplotGrob) # Get the max width widths.list <- do.call(grid::unit.pmax, lapply(grobs.list, "[[", 'widths')) # Assign the max width to all grobs grobs.list <- lapply(grobs.list, function(x) { x[['widths']] = widths.list x}) # Create the gtable and display it g <- grid.arrange(grobs = grobs.list, ncol = nb.columns) # An alternative is to use arrangeGrob that will create the table without # displaying it #g <- do.call(arrangeGrob, c(grobs.list, ncol = nb.columns)) return(g) } 

Un trucco consiste nel tracciare e allineare i grafici senza legende e quindi tracciare la legenda separatamente accanto ad essa. cowplot ha una funzione comoda per ottenere rapidamente la legenda da un plot_grid , e plot_grid consente l’allineamento automatico.

 library(cowplot) theme_set(theme_grey()) l <- get_legend(g1) ggdraw() + draw_plot(plot_grid(g1 + theme(legend.position = 'none'), g2, ncol = 1, align = 'hv'), width = 0.9) + draw_plot(l, x = 0.9, y = 0.55, width = 0.1, height = 0.5) 

inserisci la descrizione dell'immagine qui

Il pacchetto patchwork di Thomas Lin Pedersen fa tutto questo automaticamente:

 ##devtools::install_github("thomasp85/patchwork") library(patchwork) g1 + g2 + plot_layout(ncol = 1) 

Difficilmente può essere più facile di così.

inserisci la descrizione dell'immagine qui

Utilizzando grid.arrange

 library(ggplot2) library(reshape2) library(gridExtra) x = seq(0, 10, length.out = 200) y1 = sin(x) y2 = cos(x) y3 = sin(x) * cos(x) df1 <- data.frame(x, y1, y2) df1 <- melt(df1, id.vars = "x") g1 <- ggplot(df1, aes(x, value, color = variable)) + geom_line() df2 <- data.frame(x, y3) g2 <- ggplot(df2, aes(x, y3)) + geom_line() #extract the legend from the first graph temp <- ggplotGrob(g1) leg_index <- which(sapply(temp$grobs, function(x) x$name) == "guide-box") legend <- temp$grobs[[leg_index]] #remove the legend of the first graph g1 <- g1 + theme(legend.position="none") #define position of each grobs/plots and width and height ratio grid_layout <- rbind(c(1,3), c(2,NA)) grid_width <- c(5,1) grid_heigth <- c(1,1) grid.arrange( grobs=list(g1, g2,legend), layout_matrix = grid_layout, widths = grid_width, heights = grid_heigth)