Related
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)
I have decided to rephrase this question. (Editing would have taken more time and in my opinion would also not have helped the OP.)
How can one left-adjust (hjust = 0, i.e., in text direction) over facets, when scale = 'free_x'?
I don't really think that left-adjustment of x-labels is a very necessary thing to do (long labels generally being difficult to read, and right-adjusting probably the better choice) - but I find the problem interesting enough.
I tried with empty padding to the maximum character length, but this doesn't result in the same length for all strings. Also, setting axis.text.x = element.text(margin = margin()) doesn't help. Needless to say, hjust = 0 does not help, because it is adjusting within each facet.
library(ggplot2)
diamonds$cut_label <- paste("Super Dee-Duper", as.character(diamonds$cut))
ggplot(data = diamonds, aes(cut_label, carat)) +
facet_grid(~ cut, scales = "free_x") +
theme(axis.text.x = element_text(angle = 90))
The red arrows and dashed line indicate how the labels should adjust. hjust = 0 or margins or empty padding do not result in adjustment of those labels over all facets.
Data modification from this famous question
I tried with empty padding to the maximum character length, but this
doesn't result in the same length for all strings.
This caught my attention. Actually, it would result in the same length for all strings if you padded the labels with spaces, made them all the same length, and ensured the font family was non-proportionally spaced.
First, pad the labels with spaces such that all labels have the same length. I'm going to ustilise the str_pad function from the stringr package.
library(ggplot2)
data("diamonds")
diamonds$cut_label <- paste("Super Dee-Duper", as.character(diamonds$cut))
library(stringr)
diamonds$cut_label <- str_pad(diamonds$cut_label, side="right",
width=max(nchar(diamonds$cut_label)), pad=" ")
Then, you may need to load a non-proportionally-spaced font using the extrafont package.
library(extrafont)
font_import(pattern='consola') # Or any other of your choice.
Then, run the ggplot command and specify a proportionally spaced font using the family argument.
ggplot(data = diamonds, aes(cut_label, carat)) +
facet_grid(~cut, scales = "free_x") +
theme(axis.text.x = element_text(angle = 90, family="Consolas"))
One way, and possibly the most straight forward hack, would be to annotate outside the coordinates.
Disadvantage is that the parameters would need manual adjustments (y coordinate, and plot margin), and I don't see how to automate this.
library(ggplot2)
diamonds$cut_label <- paste("Super Dee-Duper", as.character(diamonds$cut))
ann_x <- data.frame(x = unique(diamonds$cut_label), y = -16, cut = unique(diamonds$cut))
ggplot(data = diamonds, aes(cut_label, carat)) +
facet_grid(~cut, scales = "free_x") +
geom_text(data = ann_x, aes(x, y, label = x), angle = 90, hjust = 0) +
theme(
axis.text.x = element_blank(),
plot.margin = margin(t = 0.1, r = 0.1, b = 2.2, l = 0.1, unit = "in")
) +
coord_cartesian(ylim = c(0, 14), clip = "off")
Created on 2020-03-14 by the reprex package (v0.3.0)
I'd approach this by making 2 plots, one of the plot area and one of the axis labels, then stick them together with a package like cowplot. You can use some theme settings to disguise the fact that the axis labels are actually made by a geom_text.
The first plot is fairly straightforward. For the second which becomes the axis labels, use dummy data with the same variables and adjust spacing how you want via text size and scale expansion. You'll probably also want to mess with the rel_heights argument in plot_grid to change the ratio of the two charts' heights.
library(ggplot2)
library(cowplot)
p1 <- ggplot(diamonds, aes(x = cut_label, y = carat)) +
facet_grid(cols = vars(cut), scales = "free_x") +
theme(axis.text.x = element_blank()) +
labs(x = NULL)
axis <- ggplot(dplyr::distinct(diamonds, cut_label, cut), aes(x = cut_label, y = 1)) +
geom_text(aes(label = cut_label), angle = 90, hjust = 0, size = 3.5) +
facet_grid(cols = vars(cut), scales = "free_x") +
scale_x_discrete(breaks = NULL) +
scale_y_continuous(expand = expansion(add = c(0.1, 1)), breaks = NULL) +
labs(y = NULL) +
theme(strip.text = element_blank(),
axis.text.x = element_blank(),
axis.ticks = element_blank(),
panel.background = element_blank())
plot_grid(p1, axis, ncol = 1, axis = "lr", align = "v")
We can edit the text grobs after generating the plot, using library(grid).
g <- ggplot(data = diamonds, aes(cut_label, carat)) +
facet_grid(~cut, scales = "free_x") +
theme(axis.text.x = element_text(angle = 90, vjust = 0.5))
gt <- cowplot::as_gtable(g)
axis_grobs <- which(grepl("axis-b", gt$layout$name))
labs <- levels(factor(diamonds$cut_label))[order(levels(diamonds$cut))]
for (i in seq_along(axis_grobs)) {
gt$grobs[axis_grobs[i]][[1]] <-
textGrob(labs[i], y = unit(0, "npc"), just = "left", rot = 90, gp = gpar(fontsize = 9))
}
grid.draw(gt)
I've been stuck on an issue and can't find a solution. I've tried many suggestions on Stack Overflow and elsewhere about manually ordering a stacked bar chart, since that should be a pretty simple fix, but those suggestions don't work with the huge complicated mess of code I plucked from many places. My only issue is y-axis item ordering.
I'm making a series of stacked bar charts, and ggplot2 changes the ordering of the items on the y-axis depending on which dataframe I am trying to plot. I'm trying to make 39 of these plots and want them to all have the same ordering. I think ggplot2 only wants to plot them in ascending order of their numeric mean or something, but I'd like all of the bar charts to first display the group "Bird Advocates" and then "Cat Advocates." (This is also the order they appear in my data frame, but that ordering is lost at the coord_flip() point in plotting.)
I think that taking the data frame through so many changes is why I can't just add something simple at the end or use the reorder() function. Adding things into aes() also doesn't work, since the stacked bar chart I'm creating seems to depend on those items being exactly a certain way.
Here's one of my data frames where ggplot2 is ordering my y-axis items incorrectly, plotting "Cat Advocates" before "Bird Advocates":
Group,Strongly Opposed,Opposed,Slightly Opposed,Neutral,Slightly Support,Support,Strongly Support
Bird Advocates,0.005473026,0.010946052,0.012509773,0.058639562,0.071149335,0.31118061,0.530101642
Cat Advocates,0.04491726,0.07013396,0.03624901,0.23719464,0.09141056,0.23404255,0.28605201
And here's all the code that takes that and turns it into a plot:
library(ggplot2)
library(reshape2)
library(plotly)
#Importing data from a .csv file
data <- read.csv("data.csv", header=TRUE)
data$s.Strongly.Opposed <- 0-data$Strongly.Opposed-data$Opposed-data$Slightly.Opposed-.5*data$Neutral
data$s.Opposed <- 0-data$Opposed-data$Slightly.Opposed-.5*data$Neutral
data$s.Slightly.Opposed <- 0-data$Slightly.Opposed-.5*data$Neutral
data$s.Neutral <- 0-.5*data$Neutral
data$s.Slightly.Support <- 0+.5*data$Neutral
data$s.Support <- 0+data$Slightly.Support+.5*data$Neutral
data$s.Strongly.Support <- 0+data$Support+data$Slightly.Support+.5*data$Neutral
#to percents
data[,2:15]<-data[,2:15]*100
#melting
mdfr <- melt(data, id=c("Group"))
mdfr<-cbind(mdfr[1:14,],mdfr[15:28,3])
colnames(mdfr)<-c("Group","variable","value","start")
#remove dot in level names
mylevels<-c("Strongly Opposed","Opposed","Slightly Opposed","Neutral","Slightly Support","Support","Strongly Support")
mdfr$variable<-droplevels(mdfr$variable)
levels(mdfr$variable)<-mylevels
pal<-c("#bd7523", "#e9aa61", "#f6d1a7", "#999999", "#c8cbc0", "#65806d", "#334e3b")
ggplot(data=mdfr) +
geom_segment(aes(x = Group, y = start, xend = Group, yend = start+value, colour = variable,
text=paste("Group: ",Group,"<br>Percent: ",value,"%")), size = 5) +
geom_hline(yintercept = 0, color =c("#646464")) +
coord_flip() +
theme(legend.position="top") +
theme(legend.key.width=unit(0.5,"cm")) +
guides(col = guide_legend(ncol = 12)) + #has 7 real columns, using to adjust legend position
scale_color_manual("Response", labels = mylevels, values = pal, guide="legend") +
theme(legend.title = element_blank()) +
theme(axis.title.x = element_blank()) +
theme(axis.title.y = element_blank()) +
theme(axis.ticks = element_blank()) +
theme(axis.text.x = element_blank()) +
theme(legend.key = element_rect(fill = "white")) +
scale_y_continuous(breaks=seq(-100,100,100), limits=c(-100,100)) +
theme(panel.background = element_rect(fill = "#ffffff"),
panel.grid.major = element_line(colour = "#CBCBCB"))
The plot:
I think this works, you may need to play around with the axis limits/breaks:
library(dplyr)
mdfr <- mdfr %>%
mutate(group_n = as.integer(case_when(Group == "Bird Advocates" ~ 2,
Group == "Cat Advocates" ~ 1)))
ggplot(data=mdfr) +
geom_segment(aes(x = group_n, y = start, xend = group_n, yend = start + value, colour = variable,
text=paste("Group: ",Group,"<br>Percent: ",value,"%")), size = 5) +
scale_x_continuous(limits = c(0,3), breaks = c(1, 2), labels = c("Cat", "Bird")) +
geom_hline(yintercept = 0, color =c("#646464")) +
theme(legend.position="top") +
theme(legend.key.width=unit(0.5,"cm")) +
coord_flip() +
guides(col = guide_legend(ncol = 12)) + #has 7 real columns, using to adjust legend position
scale_color_manual("Response", labels = mylevels, values = pal, guide="legend") +
theme(legend.title = element_blank()) +
theme(axis.title.x = element_blank()) +
theme(axis.title.y = element_blank()) +
theme(axis.ticks = element_blank()) +
theme(axis.text.x = element_blank()) +
theme(legend.key = element_rect(fill = "white"))+
scale_y_continuous(breaks=seq(-100,100,100), limits=c(-100,100)) +
theme(panel.background = element_rect(fill = "#ffffff"),
panel.grid.major = element_line(colour = "#CBCBCB"))
produces this plot:
You want to factor the 'Group' variable in the order by which you want the bars to appear.
mdfr$Group <- factor(mdfr$Group, levels = c("Bird Advocates", "Cat Advocates")
I'm ploting a Hydrograph but I additionally use facet_grid in R because I have objects with common features.
But when I use facet_grid the plot gets distorted, as shown in the figure below. How can I randerize this?
Note that it is not aligned properly, the scale of the y axis is scrambled, etc.
Among the adjustments I tried, I realized that it is possible to greatly improve this plot. I've created an image based on the above plot, some other attempts on how I'm trying and making some adjustments to paint to demonstrate what I'm trying to do.
Here's my code:
library(ggplot2)
library(grid)
library(gridExtra)
g1 <- ggplot(data_cet,
aes(x = Periodo,
y = Ind_plu)) +
geom_bar(stat = 'identity',
fill = "blue",
position = position_dodge()) +
ylab("Precip.") +
scale_y_reverse(labels = scales::comma) +
theme_bw() +
theme(axis.title.x = element_blank(),
axis.text.x = element_blank(),
axis.ticks.x = element_blank())
g2 <- ggplot(data_cet,
aes(x = Periodo,
y = Nivel,
colour = Bomba)) +
geom_line(aes(group = 1)) +
scale_color_manual(values = c("#0B775E", "#35274A", "#F2300F")) +
labs(colour = "Status CMB") +
facet_grid(data_cet$arranjo + data_cet$Bacia ~.) +
scale_x_date(breaks = datebreaks_m,
labels = date_format("%b/%y")) +
xlab('Período') + ylab('% Nível') +
theme_bw() +
theme(axis.text.x = element_text(face = "plain",
color = "black",
angle = 90),
axis.text.y = element_text(face = "plain",
color = "black"),
legend.title = element_blank(),
strip.background = element_blank(),
legend.position = "bottom")
g1 <- ggplot_gtable(ggplot_build(g1))
g2 <- ggplot_gtable(ggplot_build(g2))
maxWidth = unit.pmax(g1$widths[2:3], g2$widths[2:3])
g1$widths[2:3] <- maxWidth
g2$widths[2:3] <- maxWidth
plot_hyd <- grid.arrange(g1, g2, ncol = 1, heights = c(1, 3))
ggsave(file = "plot_hyd4.pdf", plot_hyd)
My dataset is too large, my apologize for not showing the dataset and dput().
You could add a widths = c(0.9, 1) to grid.arrange (fiddle with the first number some) to get your graphs to line up along the right side.
Otherwise, ggsave your file to a larger pdf. Your element_text objects, such as the legend, are absolute sizes, so if you scale up the pdf dimensions your graphs will look larger by comparison.
The exact values of widths and ggsave(width, height) are going to depend on you data, and unfortunately will take some trial and error. If you're using something like RStudio, I suggest fiddling with the grid.arrange call and finding the widths argument you like before calling ggsave. When you are ready to experiment with different ggsave width and height arguments, run it at a lower dpi the first few times so it processes more quickly.
Note that since you haven't included your data, I haven't tried to recreate this problem - this is just how I've solved this kind of issue in the past. If these suggestions don't work for you, let me know and I can use some built-in datasets to find another solution
Following the logic of the #Pintintended tip for the code. I adopted the layout_matrix argument.
>
plot_hyd <- grid.arrange(g1, g2,
layout_matrix = rbind(c(1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,NA),
c(2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2),
c(2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2),
c(2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2),
c(2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2),
c(2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2),
c(2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2)))
#ggsave(file="plot_hyd4.jpeg",plot_hyd,width=13,height=16,dpi=200)