I want to write a ggplot2 theme, that formats the x-Axis differently when the x-Axis contains numerical values or factors.
Is it possible to detect which type of scale is used from within the theme call? If yes, how?
My code would look something like this, im looking for an expression to replace the pseucodode in the angle brackets:
my_theme <- function(){
thm <- theme_bw() %+replace%
theme(
panel.border = element_blank()
)
if(<x-Axis scale is factor?>){
thm <- thm %+replace%
axis.ticks.x = element_blank()
}
thm
}
layer_scales is a helper function in ggplot2 that returns the scale associated with a layer (by default the first geom layer) of your plot, so something like class(layer_scales(plot)$x) can tell you the type of x-axis you are dealing with.
Here's an example for how it can be implemented:
# continuous x-axis plot (the additional specifications are there to make sure
# its look closely matches the next one
p1 <- ggplot(mtcars, aes(gear, wt, colour = factor(cyl))) +
geom_point(size = 4, show.legend = FALSE) +
scale_x_continuous(breaks = c(3, 4, 5),
expand = c(0, 0.6))
# discrete x-axis plot
p2 <- ggplot(mtcars, aes(factor(gear), wt, colour = factor(cyl))) +
geom_point(size = 4, show.legend = FALSE)
my_theme <- function(plot){
thm <- theme_bw() %+replace%
theme(
panel.border = element_blank()
)
if("ScaleDiscrete" %in% class(layer_scales(plot)$x)){
thm <- thm %+replace%
theme(
axis.ticks.x = element_blank()
)
}
plot + thm
}
# check the difference in results for p1 & p2. p1 has axis ticks while p2 does not.
gridExtra::grid.arrange(my_theme(p1), my_theme(p2), nrow = 1)
Related
I'm struggling with a problem:
I created two volcano plots in ggplot2, but due to the fact that I had one outlier point in both plot, I need to add y axis break for better visualization.
The problem arises when I WANT TO plot both in the same page using plot_grid from cowplot::, because it visualizes the original plot without the breaks that I set.
p<- c1 %>%
ggplot(aes(x = avg_log2FC,
y = -log10(p_val_adj),
fill = gene_type,
size = gene_type,
alpha = gene_type)) +
geom_point(shape = 21, # Specify shape and colour as fixed local parameters
colour = "black") +
geom_hline(yintercept = 0,
linetype = "dashed") +
scale_fill_manual(values = cols) +
scale_size_manual(values = sizes) +
scale_alpha_manual(values = alphas) +
scale_x_continuous(limits=c(-1.5,1.5), breaks=seq(-1.5,1.5,0.5)) +
scale_y_continuous(limits=c(0,110),breaks=seq(0,110,25))+
labs(title = "Gene expression",
x = "log2(fold change)",
y = "-log10(adjusted P-value)",
colour = "Expression \nchange") +
theme_bw() + # Select theme with a white background
theme(panel.border = element_rect(colour = "black", fill = NA, size= 0.5),
panel.grid.minor = element_blank(),
panel.grid.major = element_blank())
p1 <- p + scale_y_break(breaks = c(30, 100))
p1
p plot without breaks:
and p1 plot with breaks:
The same I did for the second plot. But this is the result using plot_grid(p1,p3, ncol = 2)
Can you help me understanding if I'm doing something wrong? or it is just a limitation of the package?
OP, it seems in that ggbreak is not compatible with functions that arrange multiple plots, as indicated in the documentation for the package here. There does seem to be a workaround via either print() (I didn't get this to work) or aplot::plot_list(...), which did work for me. Here's an example using built-in datasets.
# setting up the plots
library(ggplot2)
library(ggbreak)
library(cowplot)
p1 <-
ggplot(mtcars, aes(x=mpg, disp)) + geom_point() +
scale_y_break(c(200, 220))
p2 <-
ggplot(iris, aes(x=Sepal.Length, y=Sepal.Width, color=Species)) +
geom_point() + scale_y_break(c(3.5, 3.7))
Plots p1 and p2 yield breaks in the y axis like you would expect, but plot_grid(p1,p2) results in the plots placed side-by-side without the y axis breaks.
The following does work to arrange the plots without disturbing the y axis breaks:
aplot::plot_list(p1,p2)
A while ago I asked this question about how to replace a barplots x-axis labels with individual plots and I received an answer. However, I'm back trying to do this again, except this time I want to flip the barplot. The issue I'm having is I cant figure out how to adapt the code in the previous answer to allow me to flip the plot.
For example, if I create some data and a barplot with the x-axis labels replaced by plots like so:
df <- data.frame(vals = c(10, 5, 18),
name = c("A", "B", "C"))
bp <- df %>%
ggplot() +
geom_bar(aes(x = name, y = vals), stat = "identity") +
xlab("") +
theme_bw() +
theme(axis.title.x=element_blank(),
axis.text.x=element_blank(),
axis.ticks.x=element_blank())
# create plots to use as x-axis --------------------------------------------
p1 <- ggplot(df, aes(x = vals, y = vals)) + geom_point() + theme_bw() +
theme(axis.title.x = element_blank(),
axis.text.x = element_blank(),
axis.ticks.x = element_blank(),
axis.title.y = element_blank(),
axis.text.y = element_blank(),
axis.ticks.y = element_blank())
p3 <- p2 <- p1
# turn into list of plots
myList <- list(p1, p2, p3)
# -------------------------------------------------------------------------
# attach plots to axis
width <- .9 # Default width of bars
p_axis <- ggplot(df) +
geom_blank(aes(x = name)) +
purrr::map2(myList, seq_along(myList), ~ annotation_custom(ggplotGrob(.x), xmin = .y - width / 2, xmax = .y + width / 2)) +
theme_void()
bp / p_axis + plot_layout(heights = c(4, 1))
That creates this:
Now, if I add in the line bp + coordflip() while creating the barplot and continue with the rest of the code, the barplot is flipped, but the individual plots remain in place, like so:
I'm guessing I need to alter the p_axis part of the code to fix the individual plots where A, B, C are shown in the above plot... but im not sure exactly what to do to fix this? I tried experimenting but have been unsuccessful so far.
I just changed in annotation_custom the xmin and xmam to ymin and ymax. Also, I changed the part bp / p_axis to p_axis|bp.
p_axis <- ggplot(df) +
geom_blank(aes(y = name)) +
purrr::map2(myList, seq_along(myList), ~ annotation_custom(ggplotGrob(.x), ymin = .y - width / 2, ymax = .y + width / 2)) +
theme_void()
p_axis|bp
Some fine-tuning of the widths are needed. Here is what it looks like now.
A simpler approach at this point is to use the ggExtra package, which has a function ggMarginal() that adds these plots with your choice of geom.
See https://geeksforgeeks.org/r-ggplot2-marginal-plots/ for a nice demonstration
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)
What Im trying to do is create a barplot and instead of showing the x-axis labels, I want to try and replace the labels with individual plots. Hopefully my example and attempted solution below will explain the problem.
To begin, I create a barplot, and then create multiple individual plots to display on the x-axis like so:
library(ggplot)
# create barplot ----------------------------------------------------------
df <- data.frame(vals = c(10, 5, 18),
name = c("A", "B", "C"))
bp <- df %>%
ggplot() +
geom_bar(aes(x = name, y = vals), stat = "identity") +
xlab("") +
theme_bw() +
theme(axis.title.x=element_blank(),
axis.text.x=element_blank(),
axis.ticks.x=element_blank())
# create plots to use as x-axis --------------------------------------------
p1 <- ggplot(df, aes(x = vals, y = vals)) + geom_point() + theme_bw() +
theme(axis.title.x = element_blank(),
axis.text.x = element_blank(),
axis.ticks.x = element_blank(),
axis.title.y = element_blank(),
axis.text.y = element_blank(),
axis.ticks.y = element_blank())
p3 <- p2 <- p1
# turn into list of plots
myList <- list(p1, p2, p3)
Attempted solution:
My attempted solution was to use the patchwork package to replace the x-axis labels with the individual plots, like so:
library(patchwork)
# setting positions manually
design <- c(
area(1, 1, 4, 4),
area(5,1),
area(5,3),
area(5,4)
)
bp + myList + plot_layout(design = design)
This looks like:
But as you can see, this doesn't align the individual plots under the corresponding bars. The issue of alignment is also compounded if there are more than 3 bars used in the barplot.
Additionally, having to set the positions manually using the patchwork::area function isn't ideal.
Is it possible to create a barplot with the x-axis displaying a bunch of individual plots where I don't have to position them manually?
One option to achieve your desired result via patchwork would be to
create a ggplot using geom_blank(aes(x=name)) (to get the same axis as in your barplot)
add the plots to be used as axis labels via annotation_custom where I make use of purrr::map2
remove all non-data ink via theme_void
use patchwork to glue the "axis plot" to your barchart
library(ggplot2)
library(patchwork)
width <- .9 # Default width of bars
p_axis <- ggplot(df) +
geom_blank(aes(x = name)) +
purrr::map2(myList, seq_along(myList), ~ annotation_custom(ggplotGrob(.x), xmin = .y - width / 2, xmax = .y + width / 2)) +
theme_void()
library(patchwork)
bp / p_axis + plot_layout(heights = c(4, 1))
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 5 years ago.
Improve this question
R Packages: cowplot / ggplot2
Use Case: Scatter plot with marginal histograms.
Issue: For histograms, I can't add bin sizes or reference lower/ upper
class intervals in the x-axis. Without these histograms are difficult
to read.
In cowplot, is there any way to add tick marks and corresponding data
labels (in x-axis) to marginal plots, when required? E.g. for
histograms in marginal plots
Basic scatter + marginal histogram plot using cowplot
require(ggplot2)
require(cowplot)
Main Plot:
pmain <- ggplot(data = mpg, aes(x = cty, y = hwy)) +
geom_point() +
xlab("City driving") +
ylab("Highway driving") +
theme_grey()
Marginal plot:
xbox <- axis_canvas(pmain, axis = "x") +
geom_histogram(
data = mpg,
aes(x = cty),
colour = "black"
)
Combined Plot:
p1 <- insert_xaxis_grob(pmain, xbox, grid::unit(0.5, "in"), position = "top")
ggdraw(p1)
However, I'd want the following plot xbox2 to be displayed as x-axis marginal plot:
xbox2.1 <- ggplot() +
geom_histogram(
data = mpg,
aes(x = cty),
colour = "black"
)
hist_tab <- ggplot_build(xbox2.1)$data[[1]]
xbox2 <- xbox2.1 +
scale_x_continuous(
breaks = c(round(hist_tab$xmin,1),
round(hist_tab$xmax[length(hist_tab$xmax)],1))
) +
labs(x = NULL, y = NULL) +
theme(
axis.text.x = element_text(angle = 90, size=7,vjust=0.5),
axis.line = element_blank(),
axis.text.y=element_blank(),
axis.ticks.y=element_blank()
)
xbox2
But I can't create a scatter + marginal histogram (xbox2). I get the same plot as the first one:
p2 <- insert_xaxis_grob(pmain, xbox2, grid::unit(0.5, "in"), position = "top")
ggdraw(p2)
Package author here. What you're seeing is the documented behavior. From the documentation of the grob argument of insert_xaxis_grob():
The grob to insert. This will generally have been obtained via get_panel() from a ggplot2 object, in particular one generated with axis_canvas(). If a ggplot2 plot is provided instead of a grob, then get_panel() is called to extract the panel grob.
This function is specifically not meant to stack plots. You could turn your entire plot into a grob and then insert using this function, but I'm not sure that makes a lot of sense. What you're trying to do is equivalent to stacking two plots with the same x-axis range. I think it's better to just code it like that explicitly.
library(cowplot)
xlimits <- c(6, 38)
pmain <- ggplot(data = mpg, aes(x = cty, y = hwy)) +
geom_point() +
xlab("City driving") +
ylab("Highway driving") +
scale_x_continuous(limits = xlimits, expand = c(0, 0)) +
theme_grey() +
theme(plot.margin = margin(0, 5.5, 5.5, 5.5))
xhist <- ggplot() +
geom_histogram(
data = mpg,
aes(x = cty),
colour = "black",
binwidth = 1,
center = 10
) +
scale_x_continuous(limits = xlimits, expand = c(0, 0), breaks = 8:35) +
labs(x = NULL, y = NULL) +
theme(
axis.text.x = element_text(angle = 90, size=7, vjust=0.5),
axis.line = element_blank(),
axis.text.y = element_blank(),
axis.ticks.y = element_blank(),
plot.margin = margin(5.5, 5.5, 0, 5.5)
)
plot_grid(xhist, pmain, ncol = 1, align = "v", rel_heights = c(0.2, 1))