Truncate highest bar in ggplot - r

Please consider the following
library(ggplot)
data <- data.frame(qnt=c(10,20,22,12,14,9,1000),lbl=c("A","B","C","D","E","F","G"))
ggplot(data=data, aes(x=lbl, y=qnt)) + geom_histogram(stat="identity")
which produces
Which options should I consider to truncate the highest bar G in the plot? (of course explaining to the viewer what I did)

If you want to fiddle with it, you can use the gridExtra package, and plot 2 (or more) trimmed out sections of the graph. I've tinkered with the margins to make it line up, but a better plan would probably be to format the axis labels to the same text width,
require(ggplot2)
require(gridExtra)
data <- data.frame(qnt=c(10,20,22,12,14,9,1000),lbl=c("A","B","C","D","E","F","G"))
g1<-ggplot(data=data, aes(x=lbl, y=qnt)) +
geom_histogram(stat="identity")+
coord_cartesian(ylim=c(-10,50)) +
labs(x=NULL, y=NULL)+
theme(plot.margin=unit(c(2,2,6,3),"mm"))
g2<-ggplot(data=data, aes(x=lbl, y=qnt)) +
geom_histogram(stat="identity") +
coord_cartesian(ylim=c(990,1010)) +
theme(axis.text.x = element_blank(),
axis.title.x = element_blank(),
axis.title.y = element_blank(),
axis.ticks.x = element_blank()) +
labs(x=NULL, y=NULL) +
theme(plot.margin=unit(c(5,2,0,0),"mm"))
grid.arrange(g2,g1, heights=c(1/4, 3/4), ncol=1, nrow=2)

You can use coord_cartesian() and change limits for the y axis - coord_cartesian() will "zoom" the plot to the limits you will provide. Also I used geom_bar() as your are plotting factors on x axis.
ggplot(data=data, aes(x=lbl, y=qnt)) + geom_bar(stat="identity")+
coord_cartesian(ylim=c(0,100))
Another possibility is to use logarithm scale for y values.
ggplot(data=data, aes(x=lbl, y=qnt)) + geom_bar(stat="identity")+
scale_y_log10()

Related

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)

`ggplot2 - facet_grid`: Axes without ticks in interior facets

I would like to create facet_grid / facet_wrap plot with the x axis being repeated under each graph but with ticks present only on the lowest graph.
Here is an example of a plot with the x axis present only once using facet_grid
ggplot(mtcars, aes(y=mpg,x=cyl)) +
facet_grid(am~., scales="free") +
geom_point() +
theme_classic() +
theme(strip.background = element_rect(colour="white", fill="white"),
strip.text.y = element_blank())
Here is an example of a plot with the x axis present twice but with ticks both times using facet_wrap
ggplot(mtcars, aes(y=mpg, x=cyl)) +
facet_wrap(~am, ncol=1, scales="free") +
geom_point() +
theme_classic() +
theme(strip.background = element_rect(colour="white", fill="white"),
strip.text.x = element_blank())
I would like the same plot as the one just above but without the ticks on the x-axis of the upper graph. Or if you prefer, I would like the same plot as the first one but with an x-axis on the upper graph.
This is a very verbose solution, but I don't think you can get the plot you want using just the usual ggplot functions.
library(ggplot2)
library(grid)
Plot <- ggplot(mtcars, aes(y=mpg, x=cyl)) +
facet_wrap(~am, ncol=1, scales="free") +
geom_point() +
theme_classic() +
theme(strip.background = element_rect(colour="white", fill="white"),
strip.text.x = element_blank())
Switching off the top x-axis requires modifying the gtable object for the plot.
Plot.build <- ggplot_gtable(ggplot_build(Plot))
axis.pos <- grep("axis-b-1-1", Plot.build$layout$name)
num.ticks <- length(Plot.build$grobs[[axis.pos]]$children[2]$axis$grobs[[1]]$y)
This step removes the axis labels:
Plot.build$grobs[[axis.pos]]$children$axis$grobs[[2]]$children[[1]]$label <- rep("", num.ticks)
This step removes the tick marks:
Plot.build$grobs[[axes.pos]]$children[2]$axis$grobs[[1]]$y <- rep(unit(0, units="cm"), num.ticks)
Finally, the plot is generated using:
grid.draw(Plot.build)
The workaround I use to get just an axis line (no tick marks) is to use geom_hline() to fake an axis.
#make a dataframe with the y minimum for each facet
fake.axes <- data.frame(mpg = c(10, 15), #y minimum to use for axis location
am = c(0,1)) #facetting variable
#add an "axis" without ticks to upper graph using geom_hline()
ggplot(mtcars, aes(y=mpg,x=cyl)) +
facet_grid(am~., scales="free") +
geom_point() +
geom_hline(aes(yintercept = mpg), fake.axes, #dataframe with fake axes
linetype = c("solid", "blank")) + #line for top graph, blank for bottom graph
theme_classic() +
theme(strip.background = element_rect(colour="white", fill="white"),
strip.text.y = element_blank())
If you haven't used scales = "free", and all the axes are in the same location this is even simpler, you can skip making a dataframe with yintercepts for each facet and simply add
geom_hline(yintercept = 10) (or whatever your minimum is) to your plot code to add an axis line on each facet.

How to make gap between x and y axis and protruded ticks in ggplot2

How can I create the following style of graph:
Notice the gap between x-y axis (red circle) and protruded ticks in x-y axis (arrow).
At best I can do is this now:
library(ggplot2)
p <- ggplot(mpg, aes(class, hwy)) +
geom_boxplot() +
theme_bw(base_size=10)
p
One option is to remove the built-in axis lines and then use geom_segment to add axes with a gap. In order to make it easier to get the broken axis lines in the right place, we also use scale_y_continuous to specify exactly where we want the axis breaks and limits. The code also shows how to increase the size of the tick marks.
ggplot(data=mpg, aes(class, hwy)) +
geom_segment(y=10, yend=50, x=0.4, xend=0.4, lwd=0.5, colour="grey30", lineend="square") +
geom_segment(y=5, yend=5, x=1, xend=length(unique(mpg$class)),
lwd=0.5, colour="grey30", lineend="square") +
geom_boxplot() +
scale_y_continuous(breaks=seq(10,50,10), limits=c(5,50), expand=c(0,0)) +
theme_classic(base_size=12) +
theme(axis.line = element_blank(),
axis.ticks.length = unit(7,"pt"))
You can achieve something similar using ggthemes which provides geom_rangeframe and theme_tufte.
library(ggplot2)
library(ggthemes)
ggplot(mpg, aes(class, hwy)) +
geom_boxplot() +
geom_rangeframe() +
theme_tufte() +
theme(axis.ticks.length = unit(7, "pt"))
More inspiration here.
Originally posted as an answer to a related question, I was encouraged to share my answer here as well.
The ggh4x package has a truncated axis guide that solves this problem by taking advantage of position guide customisation introduced in ggplot2 v3.3.0. Because it uses the guide system directly instead of working through a geom, it is responsive to theme settings just as regular axes. (Disclaimer: I'm the author of ggh4x).
By default, it truncates the axis to the outermost breaks, but this can be adjusted.
library(ggplot2)
library(ggh4x)
ggplot(mpg, aes(class, hwy)) +
geom_boxplot() +
guides(x = "axis_truncated", y = "axis_truncated") +
theme(axis.line = element_line(colour = "black"))
Created on 2021-04-19 by the reprex package (v1.0.0)
The bars on the top and bottom of the lines are added with
geom_errorbar(aes(ymin = lower, ymax = upper), width = 0.2)
or by adding geom to another layer.
stat_summary(fun.data = mean_sdl,
fun.args = list(mult = 1),
geom = "errorbar",
width = 0.1)

How to make box plots within the same column to represent the soil column

I am trying to demonstrate the soil type (soil column) at different depths in the ground using box plots. However, as the sampling interval is not consistent, there are also gaps in between the samples.
My questions are as follows:
Is it possible to put the box plots within the same column? i.e. all box plots in 1 straight column
Is it possible to remove the x-axis labels and ticks when using ggdraw? I tried to remove it when using plot, but appears again when I use ggdraw.
My code looks like this:
SampleID <- c("Rep-1", "Rep-2", "Rep-3", "Rep-4")
From <- c(0,2,4,9)
To <- c(1,4,8,10)
Mid <- (From+To)/2
ImaginaryVal <- c(1,1,1,1)
Soiltype <- c("organic", "silt","clay", "sand")
df <- data.frame(SampleID, From, To, Mid, ImaginaryVal, Soiltype)
plot <- ggplot(df, aes(x=ImaginaryVal, ymin=From, lower=From,fill=Soiltype,
middle=`Mid`, upper=To, ymax=To)) +
geom_boxplot(colour= "black", stat="identity") + scale_y_reverse(breaks = seq(0,10,0.5)) + xlab('Soiltype') + ylab('Depth (m)') + theme(axis.text.x = element_blank(), axis.ticks.x = element_blank())
ggdraw(switch_axis_position(plot + theme_bw(8), axis = 'x'))
In the image I have pointed out what I want, using the red arrows and lines.
You can use position = position_dodge() like so:
plot <- ggplot(df, aes(x=ImaginaryVal, ymin=From, lower=From,fill=Soiltype, middle=Mid, upper=To, ymax=To)) +
geom_boxplot(colour= "black", stat="identity", position = position_dodge(width=0)) +
scale_y_reverse(breaks = seq(0,10,0.5)) +
xlab('Soiltype') +
ylab('Depth (m)') +
theme(axis.text.x = element_blank(), axis.ticks.x = element_blank())
edit: I don't think you need cowplot at all, if this is what you want your plot to look like:
ggplot(df, aes(x=ImaginaryVal, ymin=From, lower=From,fill=Soiltype, middle=Mid, upper=To, ymax=To)) +
geom_boxplot(colour= "black", stat="identity", position = position_dodge(width=0)) +
scale_y_reverse(breaks = seq(0,10,0.5)) +
xlab('Soiltype') +
ylab('Depth (m)') +
theme_bw() +
theme(axis.text.x = element_blank(), axis.ticks.x = element_blank()) +
xlab("") +
ggtitle("Soiltype")

Re-create graph design

[Update]:I found a great graph design for bar charts that I'd like to recreate in R, but I'm having difficulty with some of the major elements (it's from 538). Below is a picture of the graph and my progress so far.
Here's the graph I'm trying to recreate
Here's my code:
convicted <- c(0.68, 0.33)
incarcertated <- c(0.48, 0.12)
group <- c("GENERAL POPULATION", "LAW ENFORCEMENT")
df <- data.frame(convicted, incarcertated, group)
mdf <- melt(df)
ggplot(mdf) +
geom_bar(aes(x=variable, y=1), stat="identity", alpha=.1, position=position_dodge(1)) +
geom_bar(aes(x=variable, y=value, fill=group), stat="identity", position=position_dodge(1)) +
scale_fill_manual(values=c("#058cd3", "#ff2700"))
Here's what I'm not sure how to do
Get the "group" label to sit on top of each group and separate
them *(key design element)
Create a title and gray subheader
get the color gray bars to separate the same distance as the colored
bars
get the value labels to dodge with bar charts
I will add that in my ideal recreation, the colors would be separated (so incarcerated with be the same color in both groups).
Would love help re-creating this chat as precisely as possible. I'm pretty sure this was created in R, so I'm know it can be done. Thanks for the help!
[Update]: thanks to the help of hfty I'm getting very close, but i get a weird border effect, which I couldn't upload to the comment sections, so i've done it here. What's going on with this?
This was most likely not created solely with R. If it was, it probably was subsequently edited in Illustrator or something similar. However, here are some ways ggplot2 can get you close to the desired result:
Get the "group" label to sit on top of each group and separate them *(key design element)
Using a combination of facet_wrap() to separate the plots and coord_flip() to flip it should get you there.
ggplot(mdf, aes(x=variable, y=value, fill=group)) +
facet_wrap(~group, ncol=1) +
geom_bar(stat="identity", position=position_dodge(1)) +
coord_flip() + ...
Create a title and gray subheader
No easy way to do this with ggplot. I would suggest editing it later, e.g. with Illustrator. However, you can add a bold title e.g. like this:
... + ggtitle(expression(atop(bold("What Percentage of Crimininal Defendants Are\nConvicted and Incarcerated?")))) + ...
get the color gray bars to separate the same distance as the colored bars
You were almost there:
... + geom_bar(aes(x=variable, y=1), stat="identity", alpha=.1,
position=position_dodge(1), fill = "#aaaaaa") + ...
get the value labels to dodge with bar charts
Putting it all together with a few other tweaks, like using ggthemr to clean up the default style:
# devtools::install_github('ggthemr', 'cttobin') # Install ggthemr
library(ggthemr)
ggthemr('fresh')
ggplot(mdf, aes(x=variable, y=value, fill=group)) + facet_wrap(~group, ncol=1) +
geom_bar(stat="identity", position=position_dodge(1)) +
geom_bar(aes(x=variable, y=1), stat="identity", alpha=.1, position=position_dodge(1), fill = "#aaaaaa") +
geom_text(aes(label=round(100*value)), hjust=-0.5) +
scale_fill_manual(values=c("#058cd3", "#ff2700")) +
theme(strip.text.x = element_text(hjust=-0.15),
axis.text.x = element_blank(),
axis.line = element_blank(),
axis.ticks = element_blank(),
axis.title = element_blank(),
legend.title = element_blank(),
axis.title.y=element_blank(),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
legend.position = "none"
) +
coord_flip() +
ggtitle(expression(atop(bold("What Percentage of Crimininal Defendants Are\nConvicted and Incarcerated?")))) +
theme(plot.title = element_text(size = 20, hjust=-0.4, vjust=0.2))

Resources