strip.position external in two columns facet_wrap plot - r

in two columns facet_wrap figures, it would be handy and nicer to have the name of the facet and the axis on the left for the left column, and on the right for the right column, in order to have a compact plot, with the facets close together.
ggplot(economics_long, aes(date, value)) +
geom_line() +
labs(y="") +
facet_wrap(~variable, scales = "free_y", ncol = 2, strip.position = "left") +
theme(strip.background = element_blank(), strip.placement = "outside")

As far as I know, there is no way to do this in vanilla ggplot2. If you're comfortable with gtables, you might find the following doable.
library(ggplot2)
# Base plot
p <- ggplot(economics_long, aes(date, value)) +
geom_line() +
labs(y="") +
theme(strip.background = element_blank(), strip.placement = "outside")
# Left aligned strips/axes
left <- p +
facet_wrap(~variable, scales = "free_y", ncol = 2, strip.position = "left")
# Right aligned strips/axes
right <- p +
facet_wrap(~variable, scales = "free_y", ncol = 2, strip.position = "right") +
scale_y_continuous(position = "right")
# Convert to gtables
left <- ggplotGrob(left)
right <- ggplotGrob(right)
# Combine parts of left and right
# Column numbers found by browsing through layout
gt <- cbind(left[, 1:7], right[, 9:ncol(right)])
# Render
grid::grid.newpage(); grid::grid.draw(gt)
Created on 2021-10-20 by the reprex package (v2.0.1)
To find the panel positions in a more programmatically than judging the table layout manually, my best guess is to do this:
panels_left <- panel_cols(left)$l
panels_right <- panel_cols(right)$l
# The -2 is to include the left axis space (zero width because of absence)
# and the panel spacing
gt <- cbind(left[, 1:panels_left[1]],
right[, (panels_right[2]-2):ncol(right)])
grid::grid.newpage(); grid::grid.draw(gt)

Related

How to remove white spcaes around a bottom-aligned legend with cowplot

I am using cowplot to arrange two plots with a common legend below. The approach works, yet the legend has whitespace around I would like to get rid of.
Note: I am ware of the ggpubr::ggarange common.legend option I though want to stick to cowplot::plot_grid (which masks ggpubr::get_legend)
library(tidyverse)
library(ggpubr)
library(cowplot)
theme_set(theme_cowplot())
p1 <- iris %>%
ggplot(aes(x=Sepal.Width,y=Petal.Width,color=Species)) +
geom_point() + coord_fixed()
p2 <- iris %>%
ggplot(aes(x=Sepal.Width,y=Petal.Width,color=Species)) +
geom_point() + coord_fixed()
compound_plot <- plot_grid(p1 + theme(legend.position="none"),
p2 + theme(legend.position="none"),
ncol = 2,
labels = c("A","B"))
legend <- get_legend(p1 +
theme(legend.direction = "horizontal",
legend.justification="center" ,
legend.box.just = "bottom"))
plot_grid(compound_plot,
legend,
ncol = 1)
Thanks in advance!
You can set rel_heights inside plot_grid
plot_grid(compound_plot,
legend,
ncol = 1, rel_heights = c(0.95, 0.05))

Stacking multiple figures together in ggplot

I am attempting to make publication ready figures where the bottom axis (with tick marks) of one figure is cleanly combined with the top axis of the figure below it. Here is an example of what it might look like, although this one doesn't have tick marks on each panel:
Here is my attempt to do so, by simply using grid.arrange:
#Libraries:
library(ggplot2)
library(dplyr)
library(gridExtra)
#Filter to create two separate data sets:
dna1 <- DNase %>% filter(Run == 1)
dna2 <- DNase %>% filter(Run == 2)
#Figure 1:
dna1_plot <- ggplot(dna1, aes(x = conc, y = density)) + geom_point() + theme_classic() +
theme(axis.title.x = element_blank())
#Figure 2:
dna2_plot <- ggplot(dna2, aes(x = conc, y = density)) + geom_point() + theme_classic()
#Using grid.arrange to combine:
dna <- grid.arrange(dna1_plot, dna2_plot, nrow = 2)
And an attempt with some adjustments to the plot margins, although this didn't seem to work:
dna1_plot_round2 <- ggplot(dna1, aes(x = conc, y = density)) + geom_point() + theme_classic() +
theme(axis.title.x = element_blank(),
plot.margin = (0,0,0,0), "cm")
dna2_plot_round2 <- ggplot(dna2, aes(x = conc, y = density)) + geom_point() + theme_classic() +
theme(plot.margin = unit(c(-0.5,-1,0,0), "cm"))
dna_round2 <- grid.arrange(dna1_plot_round2, dna2_plot_round2, nrow = 2)
Does anyone know the best way to stack figures like this in ggplot? Is there a better way than using grid.arrange? If possible it would be great to see how to do it with/without tick marks on each x axis as well.
Thank you!
You don't need any non-native ggplot stuff. Keep your data in one data frame and use facet_grid.
dna <- DNase %>% filter(Run %in% 1:2)
ggplot(dna, aes(x = conc, y = density)) +
geom_point() +
theme_bw() +
facet_grid(rows = vars(Run)) +
theme(panel.spacing = unit(0, "mm"))
The R package deeptime has a function called ggarrange2 that can achieve this. Instead of just pasting the plots together like grid.arrange (and ggarrange), it lines up all of the axes and axis labels from all of the plots.
# remove bottom axis elements, reduce bottom margin, add panel border
dna1_plot_round2 <- ggplot(dna1, aes(x = conc, y = density)) + geom_point() + theme_classic() +
theme(axis.text.x = element_blank(), axis.ticks.x = element_blank(), axis.title.x = element_blank(),
plot.margin = margin(0,0,-.05,0, "cm"), panel.border = element_rect(fill = NA))
# reduce top margin (split the difference so the plots are the same height), add panel border
dna2_plot_round2 <- ggplot(dna2, aes(x = conc, y = density)) + geom_point() + theme_classic() +
theme(plot.margin = margin(-.05,0,0,0, "cm"), panel.border = element_rect(fill = NA))
dna_round2 <- ggarrange2(dna1_plot_round2, dna2_plot_round2, nrow = 2)
You might also try the fairly recent patchwork package, although I don't have much experience with it.
Note that while Gregor's answer may be fine for this specific example, this answer might be more appropriate for other folks that come across this question (and see the example at the top of the question).
For your purposes, I believe Gregor Thomas' answer is best. But if you are in a situation where facets aren't the best option for combining two plots, the newish package {{patchwork}} handles this more elegantly than any alternatives I've seen.
Patchwork also provides lots of options for adding annotations surrounding the combined plot. The readME and vignettes will get you started.
library(patchwork)
(dna1_plot / dna2_plot) +
plot_annotation(title = "Main title for combined plots")
Edit to better address #Cameron's question.
According to the package creator, {{patchwork}} does not add any space between the plots. The white space in the example above is due to the margins around each individual ggplot. These margins can be adjusted using the plot.margin argument in theme(), which takes a numeric vector of the top, right, bottom, and left margins.
In the example below, I set the bottom margin of dna1_plot to 0 and strip out all the bottom x-axis ticks and text. I also set the top margin of dna2_plot to 0. Doing this nearly makes the y-axis lines touch in the two plots.
dna1_plot <- ggplot(dna1, aes(x = conc, y = density)) + geom_point() + theme_classic() +
theme(axis.title.x = element_blank(),
axis.ticks.x = element_blank(),
axis.text.x = element_blank(),
plot.margin = unit(c(1,1,0,1), "mm"))
#Figure 2:
dna2_plot <- ggplot(dna2, aes(x = conc, y = density)) + geom_point() + theme_classic() +
theme(plot.margin = unit(c(0,1,1,1), "mm"))
(dna1_plot / dna2_plot)

Align a shared legend in cowplot

I'm looking to use the cowplot package to give two ggplots a shared legend. However, how can I center the shared legend when there's an even number of plots.
Here's some example code:
library(ggplot2)
library(cowplot)
library(rlang)
# down-sampled diamonds data set
dsamp <- diamonds[sample(nrow(diamonds), 1000), ]
# function to create plots
plot_diamonds <- function(xaes) {
xaes <- enquo(xaes)
ggplot(dsamp, aes(!!xaes, price, color = clarity)) +
geom_point() +
theme_half_open(12) +
# we set the left and right margins to 0 to remove
# unnecessary spacing in the final plot arrangement.
theme(plot.margin = margin(6, 0, 6, 0))
}
# make two plots
p1 <- plot_diamonds(carat)
p2 <- plot_diamonds(depth) + ylab(NULL)
# arrange the three plots in a single row
prow <- plot_grid(
p1 + theme(legend.position="none"),
p2 + theme(legend.position="none"),
align = 'vh',
labels = c("A", "B"),
hjust = -1,
nrow = 1
)
prow
# extract a legend that is laid out horizontally
legend_b <- get_legend(
p1 +
guides(color = guide_legend(nrow = 1)) +
theme(legend.position = "bottom")
)
# add the legend underneath the row we made earlier. Give it 10%
# of the height of one plot (via rel_heights).
plot_grid(prow, legend_b, ncol = 1, rel_heights = c(1, .1))
Which produces this image:
As you can see, cow_plot puts the the legend is located underneath the left image. Is there a way to make this shared legend centered between the two images?
Change the theme like below;
legend_b <- get_legend( p1 +
guides(color = guide_legend(nrow = 1)) +
theme(legend.direction = "horizontal",
legend.justification="center",
legend.box.just = "bottom")
)

How to modify my legends when arranging several plots together to don't collapse the plots with ggplot2

I have 16 plots that I want to arrange together for illustration purposes. Below I show one graph as an example.
The code for creating each plot is next:
P1<- ggplot(Fish_acc_C.D.Mean_bottom_invierno_P16, aes(C.D.Mean_bottom,meanAcc)) +
geom_point(aes(C.D.Mean_bottom,meanAcc, color = C.I.Mean_bottom),show.legend = FALSE) +
scale_colour_gradientn(colours=c("green","black")) +
theme_bw() +
geom_smooth(aes(linetype = "Activity"),fill = "lightblue",color="red", alpha = 0.99) +
ggtitle("Activity ~ Curr Direct Mean bottom WINTER (Hourly data)") +
theme(plot.title = element_text(size=10,hjust = 0.5)) +
geom_smooth(aes(C.D.Mean_bottom, C.I.Mean_bottom * max(range(Fish_acc_C.D.Mean_bottom_invierno_P16$C.I.Mean_bottom)), linetype = "C.I.M.B"), se=FALSE, colour = "blue",show.legend = FALSE) +
scale_y_continuous(sec.axis = sec_axis(trans = ~ . /max(range(Fish_acc_C.D.Mean_bottom_invierno_P16$C.I.Mean_bottom)), name = "C.I.Mean bottom")) +
scale_linetype_manual(values = c(1,1),guide = guide_legend(override.aes = list(colour = c("red", "blue")))) +
coord_cartesian(ylim = c(0, 1.25)) +
theme(legend.justification = c(1,1),
legend.position = c(1,1))
My problem is that when I use grid.arrange() to group them all together, the legends of each plot increase their size with regard to their size in the individual ones. Here an example:
Here I show the code for creating the grid_arrange:
grid.arrange(P1,P2,P3,P4,P5,P6,P7,P8,P9,P10,P11,P12,P13,P14,P15,P16,ncol=4)
Does anyone know how to reduce the size of the legends in the grid_arranged graph in order to see the lines? I thought that maybe I could place the legends in the upper-middle position of the graph, delete the word "linetype" from the legend and display the linetypes in one line (side by side instead of up and down). However I don't know how to do it.
Does anyone have any recommendation?
You can look at the ggarrange function from the ggpubr package.
It allows you to group your plots as you need and have a common legend for all of them, for which you can specify the position.
e.g.
library(ggpubr)
ggarrange(g1, g2, ncol = 2, common.legend = T, legend = 'bottom')
# or
ggarrange(plotlist = my_list, ncol = 2, common.legend = T, legend = 'bottom')
#Dekike then you can extract the legend and put the legend on top/bottom of the main plot.
Here are my psude code
library(ggplot2)
library(cowplot)
P1 <- ggplot() + ... + guides(lineType=guide_legend(ncol=2))
legend_extracted <- get_legend(P1)
P1 <- P1 + theme(legend.position="none")
P2 <- ggplot() + ... + theme(legend.position="none")
...
P16 <- ggplot() + ... + theme(legend.position="none")
main_plot <- grid.arrange(P1,P2,P3,P4,P5,P6,P7,P8,P9,P10,P11,P12,P13,P14,P15,P16,ncol=4)
main_plot_wLegend <- grid.arrange(legend_extracted, main_plot, ncol=1, nrow=2)
Please try and feedback on this!

decrease size of dendogram (or y-axis) ggplot

I have this code for a dendrogram. How can I decrease the size of dendrogram (or y-axis)?
I am using this code as example. In my dataset, I have large labels so I do not have space enough to include it. For that reason, I would like to reduce the space used for y axis, decrease the distance between 0 and 150. Also, when I save the figure as tiff, most of figure is the dendogram and I can not see labels clearly.
df <- USArrests # really bad idea to muck up internal datasets
labs <- paste("sta_",1:50,sep="") # new labels
rownames(df) <- labs # set new row names
library(ggplot2)
library(ggdendro)
hc <- hclust(dist(df), "ave") # heirarchal clustering
dendr <- dendro_data(hc, type="rectangle") # convert for ggplot
clust <- cutree(hc,k=2) # find 2 clusters
clust.df <- data.frame(label=names(clust), cluster=factor(clust))
# dendr[["labels"]] has the labels, merge with clust.df based on label column
dendr[["labels"]] <- merge(dendr[["labels"]],clust.df, by="label")
# plot the dendrogram; note use of color=cluster in geom_text(...)
ggplot() +
geom_segment(data=segment(dendr), aes(x=x, y=y, xend=xend, yend=yend)) +
geom_text(data=label(dendr),
aes(x, y, label=label, hjust=0, color=cluster),
size=3) +
coord_flip() +
scale_y_reverse(expand=c(0.2, 0)) +
theme(axis.line.y=element_blank(),
axis.ticks.y=element_blank(),
axis.text.y=element_blank(),
axis.title.y=element_blank(),
panel.background=element_rect(fill="white"),
panel.grid=element_blank())
How can I decrease the size of dendogram similar than this heatmap?
(source: r-graph-gallery.com)
Thanks you so much
For flexibility, I recommend putting the dendrogram labels on the x-axis itself, rather than text labels within the plot. Otherwise no matter what values you choose for expand in the y-axis, part of the labels could be cut off for some image sizes / dimensions.
Define colour palette for the dendrogram labels:
library(dplyr)
label.colour = label(dendr)$cluster %>%
factor(levels = levels(.),
labels = scales::hue_pal()(n_distinct(.))) %>%
as.character()
For the purpose of illustration, make some labels very long:
label.values <- forcats::fct_recode(
label(dendr)$label,
sta_45_abcdefghijklmnop = "sta_45",
sta_31_merrychristmas = "sta_31",
sta_6_9876543210 = "sta_6")
Plot:
p <- ggplot(segment(dendr)) +
geom_segment(aes(x=x, y=y, xend=xend, yend=yend)) +
coord_flip() +
scale_x_continuous(breaks = label(dendr)$x,
# I'm using label.values here because I made
# some long labels for illustration. you can
# simply use `labels = label(dendr)$label`
labels = label.values,
position = "top") +
scale_y_reverse(expand = c(0, 0)) +
theme_minimal() +
theme(axis.title = element_blank(),
axis.text.y = element_text(size = rel(0.9),
color = label.colour),
panel.grid = element_blank())
p
# or if you want a color legend for the clusters
p + geom_point(data = label(dendr),
aes(x = x, y = y, color = cluster), alpha = 0) +
scale_color_discrete(name = "Cluster",
guide = guide_legend(override.aes = list(alpha = 1))) +
theme(legend.position = "bottom")
You can do this by adding a size parameter to axis.text.y like so:
theme(axis.line.y=element_blank(),
axis.ticks.y=element_blank(),
axis.text.y=element_text(size=12),
axis.title.y=element_blank(),
panel.background=element_rect(fill="white"),
panel.grid=element_blank())

Resources