Adding horizontal title/label to bar charts in ggplot R - r

is there a way to plot a bar graph in ggplot and group the bars accordingly using a horizontal line with label. It should look something like in the image below but for multiple groups and bars in one single graph only.
For context, I'm planning to group three bars for each horizontal label in one graph. For example, the first 3 bars would represent my control, the next three bars would be for treatment 1, the next three would be for treatment 2, and so on.
Let me know if you have a template for making such a graph. Thanks!

The default approach to achieve the kind of grouping would be via faceting, However, TBMK the default element_rect provided by ggplot2 does not offer the option to just draw a top line.
One option would be to create a custom theme element to achieve that. To this end I adapted the code for the default element_rect where I replaced the ´rectGrobby alinesGrob`.
Moreover, to also add the top lines for the axis labels I draw on ggh4x to add the axis labels via facets too. Doing so allows to use the custom theme element to add the top lines for the "axis labels" too (Of course would it be possible to create another custom theme element.).
Using mtcars as example data:
element_rect2 <- function(colour = NULL, size = NULL, linetype = NULL,
color = NULL, inherit.blank = FALSE) {
if (!is.null(color)) {
colour <- color
}
structure(list(
colour = colour, size = size,
linetype = linetype, inherit.blank = inherit.blank
),
class = c("element_rect2", "element_rect", "element")
)
}
element_grob.element_rect2 <- function(element, x = unit(c(0, 1), "npc"), y = unit(c(1, 1), "npc"),
colour = NULL, size = NULL, linetype = NULL, ...) {
gp <- grid::gpar(lwd = size, col = colour, lty = linetype)
element_gp <- grid::gpar(lwd = element$size, col = element$colour, lty = element$linetype)
gp <- modifyList(element_gp, gp)
grid::linesGrob(x, y - unit(gp$lwd, unit = "pt"), gp = gp, ...)
}
library(ggplot2)
library(ggh4x)
ggplot(mtcars, aes(factor(cyl))) +
geom_bar() +
scale_y_continuous(expand = expansion(mult = c(0, .05))) +
labs(x = NULL, y = NULL) +
facet_wrap2(am ~ cyl,
strip.position = "bottom", scales = "free_x",
strip = strip_nested(
background_x = list(
element_rect2(size = 2),
element_rect2(size = 2)
),
by_layer_x = TRUE
),
nrow = 1
) +
theme_minimal() +
theme(
panel.grid = element_blank(),
axis.line.y = element_line(size = .75, lineend = "square"),
axis.ticks.y = element_line(size = .75),
axis.text.x = element_blank(),
strip.placement = "outside"
)

You can use geom_bar() from the ggplot2 package to make similar looking bar plots.
library(ggplot2)
data(iris)
ggplot(iris, aes(x = Species, y = Petal.Length)) +
geom_bar(stat = "identity", fill = "black") +
theme_bw()

Related

Neatly place 2 legends together in ggplot2

I am trying to get my ggplot2 legends to sit together well.
I have a fill legend and a colour legend and I want them to be over multiple rows at the base of the plot but with the colour legend continuing directly after the fill legend, rather than starting a new column.
I've made a quick example and desired output (just made in paint) below to illustrate
library(ggplot2)
set.seed(1)
testdf <- data.frame(mon = factor(month.abb, levels = month.abb), y = rnorm(84,mean = 20, sd = 10), cat = rep(paste0("class ",letters[1:7]), each = 12))
thresholds <- data.frame(ThresholdNm = c("low","high"), ThresholdVal = c(110,150))
ggplot(testdf, aes(x = mon, y = y, fill = cat))+
geom_bar(stat = "identity")+
geom_hline(data = thresholds, aes(yintercept = ThresholdVal, colour = ThresholdNm))+
scale_colour_manual(values = c("red","black"))+
theme(legend.position = "bottom", legend.title = element_blank())+
guides(fill = guide_legend(nrow=3,byrow=FALSE,order = 1),colour = guide_legend(nrow=2,byrow=FALSE,order = 2))
This is what I get:
But what I am hoping for is this:
Created on 2022-11-10 by the reprex package (v0.3.0)
Adapting my answer on this post to your case you could achieve your desired result using a custom key glyph like so:
Basically this involves mapping ThresholdVal on the fill aes in geom_hline. Doing so will add the items to the fill legend too.
Create a color palette which could be used for both the fill and the color scale and which takes care of the right order of the items.
Write custom key glyph function which conditional on the color value switches between the key glyph used for bars and the one used for geom_hline
Remove the color legend.
Use theme options to get a border around all legend keys including the ones for the hlines.
library(ggplot2)
nclass <- nlevels(factor(testdf$cat))
pal <- c(scales::hue_pal()(nclass), "red", "black")
names(pal) <- c(levels(factor(testdf$cat)), "high", "low")
draw_key_cust <- function(data, params, size) {
if (data$fill %in% c("red", "black")) {
data$colour <- data$fill
data$fill <- NA
draw_key_path(data, params, size)
} else {
GeomCol$draw_key(data, params, size)
}
}
ggplot(testdf, aes(x = mon, y = y, fill = cat)) +
geom_bar(stat = "identity", key_glyph = "cust") +
geom_hline(data = thresholds, aes(yintercept = ThresholdVal, colour = ThresholdNm, fill = ThresholdNm)) +
scale_fill_manual(values = pal, aesthetics = c("fill", "color")) +
theme(legend.position = "bottom", legend.title = element_blank(),
legend.key = element_rect(linewidth = .25 * .pt, color = "white")) +
guides(fill = guide_legend(nrow = 3, byrow = FALSE, order = 1), colour = "none")
#> Warning in geom_hline(data = thresholds, aes(yintercept = ThresholdVal, :
#> Ignoring unknown aesthetics: fill

ggplot: Add annotations using separate data above faceted chart

I'm trying to add set of markers with text above the top of a faceted chart to indicate certain points of interest in the value of x. Its important that they appear in the right position left to right (as per the main scale), including when the overall ggplot changes size.
Something like this...
However, I'm struggling to:
place it in the right vertical position (above the facets). In my
reprex below (a simplified version of the original), I tried using a
value of the factor (Merc450 SLC), but this causes issues such as adding that to
every facet including when it is not part of that facet and doesn't
actually go high enough. I also tried converting the factor to a number using as.integer, but this causes every facet to include all factor values, when they obviously shouldn't
apply to the chart as a whole, not each
facet
Note that in the full solution, the marker x values are independent of the main data.
I have tried using cowplot to draw it separately and overlay it, but that seems to:
affect the overall scale of the main plot, with the facet titles on the right being cropped
is not reliable in placing the markers at the exact location along the x scale
Any pointers welcome.
library(tidyverse)
mtcars2 <- rownames_to_column(mtcars, var = "car") %>%
mutate(make = stringr::word(car, 1)) %>%
filter(make >= "m" & make < "n")
markers <- data.frame(x = c(max(mtcars2$mpg), rep(runif(nrow(mtcars2), 1, max(mtcars2$mpg))), max(mtcars2$mpg))) %>%
mutate(name = paste0("marker # ", round(x)))
ggplot(mtcars2, aes()) +
# Main Plot
geom_tile(aes(x = mpg, y = car, fill = cyl), color = "white") +
# Add Markers
geom_point(data = markers, aes(x = x, y = "Merc450 SLC"), color = "red") +
# Marker Labels
geom_text(data = markers, aes(x = x, "Merc450 SLC",label = name), angle = 45, size = 2.5, hjust=0, nudge_x = -0.02, nudge_y = 0.15) +
facet_grid(make ~ ., scales = "free", space = "free") +
theme_minimal() +
theme(
# Facets
strip.background = element_rect(fill="Gray90", color = "white"),
panel.background = element_rect(fill="Gray95", color = "white"),
panel.spacing.y = unit(.7, "lines"),
plot.margin = margin(50, 20, 20, 20)
)
Perhaps draw two separate plots and assemble them together with patchwork:
library(patchwork)
p1 <- ggplot(markers, aes(x = x, y = 0)) +
geom_point(color = 'red') +
geom_text(aes(label = name),
angle = 45, size = 2.5, hjust=0, nudge_x = -0.02, nudge_y = 0.02) +
scale_y_continuous(limits = c(-0.01, 0.15), expand = c(0, 0)) +
theme_minimal() +
theme(axis.text = element_blank(),
axis.title = element_blank(),
panel.grid = element_blank())
p2 <- ggplot(mtcars2, aes(x = mpg, y = car, fill = cyl)) +
geom_tile(color = "white") +
facet_grid(make ~ ., scales = "free", space = "free") +
theme_minimal() +
theme(
strip.background = element_rect(fill="Gray90", color = "white"),
panel.background = element_rect(fill="Gray95", color = "white"),
panel.spacing.y = unit(.7, "lines")
)
p1/p2 + plot_layout(heights = c(1, 9))
It required some workaround with plot on different plot and using cowplot alignment function to align them on the same axis. Here is a solution
library(tidyverse)
library(cowplot)
# define a common x_axis to ensure that the plot are on same scales
# This may not needed as cowplot algin_plots also adjust the scale however
# I tended to do this extra step to ensure.
x_axis_common <- c(min(mtcars2$mpg, markers$x) * .8,
max(mtcars2$mpg, markers$x) * 1.1)
# Plot contain only marker
plot_marker <- ggplot() +
geom_point(data = markers, aes(x = x, y = 0), color = "red") +
# Marker Labels
geom_text(data = markers, aes(x = x, y = 0,label = name),
angle = 45, size = 2.5, hjust=0, nudge_x = 0, nudge_y = 0.001) +
# using coord_cartesian to set the zone of plot for some scales
coord_cartesian(xlim = x_axis_common,
ylim = c(-0.005, 0.03), expand = FALSE) +
# using theme_nothing from cow_plot which remove all element
# except the drawing
theme_nothing()
# main plot with facet
main_plot <- ggplot(mtcars2, aes()) +
# Main Plot
geom_tile(aes(x = mpg, y = car, fill = cyl), color = "white") +
coord_cartesian(xlim = x_axis_common, expand = FALSE) +
# Add Markers
facet_grid(make ~ ., scales = "free_y", space = "free") +
theme_minimal() +
theme(
# Facets
strip.background = element_rect(fill="Gray90", color = "white"),
panel.background = element_rect(fill="Gray95", color = "white"),
panel.spacing.y = unit(.7, "lines"),
plot.margin = margin(0, 20, 20, 20)
)
Then align the plot and plot them using cow_plot
# align the plots together
temp <- align_plots(plot_marker, main_plot, axis = "rl",
align = "hv")
# plot them with plot_grid also from cowplot - using rel_heights for some
# adjustment
plot_grid(temp[[1]], temp[[2]], ncol = 1, rel_heights = c(1, 8))
Created on 2021-05-03 by the reprex package (v2.0.0)

adding a border around a grob (R) [duplicate]

I'm using the code below:
# Libs
require(ggplot2); require(gridExtra); require(grid)
# Generate separate charts
chrts_list_scts <- list()
# Data
data("mtcars")
# A
chrts_list_scts$a <- ggplot(mtcars) +
geom_point(size = 2, aes(x = mpg, y = disp,
colour = as.factor(cyl))) +
geom_smooth(aes(x = mpg, y = disp),
method = "auto") +
xlab("MPG") +
ylab("Disp") +
theme_bw() +
theme(panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
legend.position = "none")
# B
chrts_list_scts$b <- ggplot(mtcars) +
geom_point(size = 2, aes(x = mpg, y = drat,
colour = as.factor(cyl))) +
geom_smooth(aes(x = mpg, y = drat),
method = "auto") +
xlab("MPG") +
ylab("Drat") +
theme_bw() +
theme(panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
legend.position = "none")
# C
chrts_list_scts$c <- ggplot(mtcars) +
geom_point(size = 2, aes(x = mpg, y = qsec,
colour = as.factor(cyl))) +
geom_smooth(aes(x = mpg, y = qsec),
method = "auto") +
xlab("MPG") +
ylab("QSEC") +
guides(colour = guide_legend(title = "cyl")) +
theme_bw() +
theme(panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
legend.position = "bottom",
legend.key = element_rect(colour = NA))
# Arrange grid
png(filename = "chrts.PNG", width = 6,
height = 10, units = 'in', res = 300)
title_text <- c("mtcars")
chrts_list_scts$all_scts <- grid.arrange(chrts_list_scts$a,
chrts_list_scts$b,
chrts_list_scts$c,
top =
textGrob(label = title_text,
gp = gpar(
fontsize = 14,
font = 2)))
dev.off()
rm(title_text)
To generate the following chart:
I'm interested in adding border around that chart, as in the picture below:
Attempts
I tried to address this request via adding polygonGrob in the code:
chrts_list_scts$all_scts <- grid.arrange(chrts_list_scts$dep_work,
chrts_list_scts$chld_work,
chrts_list_scts$pens,
polygonGrob(x = c(0,0.5,1.05),
y = c(0,0.5,1.05)
),
top =
textGrob(label = title_text,
gp = gpar(
fontsize = 14,
font = 2)))
but this generates a pointless chart with one line across in the bottom. I had a look at the seeming similar discussion on SO but it wasn't clear to me how to arrive at a working solution.
Side requirements
In addition to generating the border, I would like to:
Be able to exercise some control over the border aesthetics, like changing size and colour of the border.
Ideally, I would like to encapsulate this solution within the arrange.grid call. So at the object chrts_list_scts$all_scts has all elements including charts and neat border around all of them.
I will be happy to accept solutions that address the major requirements with respect to the border only, if there is a suggested solution that matches the remaining two points it will be even nicer.
1) Using the iris example (but further simplified) from the link provided in the question just add the last line. Modify the gpar(...) components (and possibly the width and height) to get different aesthetics. (This is not encapsulated in the grid.arrange call.)
library(ggplot2)
library(grid)
library(gridExtra)
g <- ggplot(iris, aes(Sepal.Width, Sepal.Length)) + geom_point()
grid.arrange(g, g, ncol=2)
# next line adds border
grid.rect(width = .98, height = .98, gp = gpar(lwd = 2, col = "blue", fill = NA))
(continued after plot)
2) This is a variation of solution (1) in which on the plus side encapsulates both the graphics and border in the gt gTree by creating grobs to hold each. On the other hand it does involve some additional complexity:
grid.newpage()
ga <- arrangeGrob(g, g, ncol = 2)
gb <- rectGrob(height = .98, width = .98, gp = gpar(lwd = 2, col = "blue", fill = NA)) # border, no fill
gt <- gTree(children = gList(ga, gb))
grid.draw(gt)
you can add a rectGrob to the gtable
grid.draw(gtable::gtable_add_grob(arrangeGrob(g, g, ncol=2),
rectGrob(gp=gpar(lwd=5, fill=NA)), 1, 1, 1, 2))
NOTE: fill=NA or fill='transparent' is required otherwise the rectangle can mask the objects below it.

texture in geom_polygon fill

I need to create a European map to show the distribution of a variable across countries. I need the map in black and white. I rely on ggplot and followed this approach as an example. I changed the legend based on this blogpost. All this works fine with this result:
My question is how to change the map in a way that the countries where I am missing the information for fill and are shown as pure white have a texture over-them (I am thinking diagonal lines)?
Since my script is a bit messy, I just show the ggplot here, without the data preparation part:
require(ggplot2)
plotCoords <- read.csv("http://eborbath.github.io/stackoverflow/PlotCoords.csv")
showCoords <- read.csv("http://eborbath.github.io/stackoverflow/showCoords.csv")
ggplot() +
geom_polygon(
data = plotCoords,
aes(x = long, y = lat, group = group),
fill = "white", colour = "darkgrey", size = 0.6) +
geom_polygon(
data = showCoords,
aes(x = long, y = lat, group = group),
fill = "grey", colour = "black", size = 0.6) +
geom_polygon(
data = showCoords,
aes(x = long, y = lat, group = group, fill = sh_left),
colour = "black", size = 0.1) +
scale_fill_gradient(
low = "gray90", high = "gray0",
name = "Share of left-wing protesters",
guide = guide_colorbar(
direction = "horizontal",
barheight = unit(2, units = "mm"),
barwidth = unit(50, units = "mm"),
draw.ulim = F,
title.position = 'top',
title.hjust = 0.5,
label.hjust = 0.5
)) +
scale_x_continuous(element_blank(), breaks = NULL) +
scale_y_continuous(element_blank(), breaks = NULL) +
coord_map(xlim = c(-26, 47), ylim = c(32.5, 73)) +
theme_bw() +
theme(legend.justification = c(-0.4, 1.2), legend.position = c(0, 1))
The first geom_polygon is for the background, I assume I have to edit the fill there. Obviously, this is important to differentiate no information from low values of the variable I plot. Given I have to rely on black and white I came up with the idea of using textures, but I am open to alternative suggestions.
Thanks!
it's technically possible with gridSVG, but not sure it's worth the effort.
I created a new geom based on GeomPolygon, and modified the draw_panel method to return,
gl <- by(munched, munched$group,
function(m){
g <- polygonGrob(m$x, m$y, default.units = "native")
patternFillGrob(g,
pattern = pattern(linesGrob(gp=gpar(col="red",lwd=3)),
width = unit(2, "mm"), height = unit(2, "mm"),
dev.width = 1, dev.height = 1))
}, simplify = FALSE)
gTree(children = do.call(gList, gl))

ggplot: Centre and move the vertical axis labels

I'm looking to set up a mirrored bar chart with one set of axis labels in the middle. This image shows what I have so far (code to reproduce at the end):
I'd like the names to be centred between the charts. Methods tried:
using axis labels (best attempt shown here)
using annotation_custom (I found placing the labels to be very difficult and disliked the combination of ggplot references and base plot references)
creating a separate "chart object" to put into the grid.arrange panel (difficult to get the correct vertical spacing between labels without there being any bars)
I'd welcome any suggestions around the easiest way to achieve this layout. The base has to be ggplot, but happy to use other packages to arrange charts.
require("ggplot2")
require("gridExtra")
dataToPlot <- data.frame(
"Person" = c("Alice", "Bob", "Carlton"),
"Age" = c(14, 63, 24),
"Score" = c(73, 62.1, 21.5))
plot1 <- ggplot(dataToPlot) +
geom_bar(data = dataToPlot, aes(x = Person, y = Score), stat = "identity",
fill = "blue", width = 0.8) +
scale_y_continuous(trans = "reverse", expand = c(0, 0)) +
scale_x_discrete(position = "top") +
theme(
axis.text.y = element_blank()
) +
labs(x = NULL) +
coord_flip()
plot2 <- ggplot(dataToPlot) +
geom_bar(data = dataToPlot, aes(x = Person, y = Age), stat = "identity",
fill = "red", width = 0.8) +
scale_y_continuous(expand = c(0, 0)) +
theme(
axis.text.y = element_text(size = 20, hjust = 0.5)
) +
labs(x = "") +
coord_flip()
gridExtra::grid.arrange(plot1, plot2, ncol = 2, widths = c(1, 1.2))
There are two ways (perhaps in combination)...
Add a margin to the right of the axis labels in the right-hand chart...
element_text(size = 20, hjust = 0.5, margin=margin(r=30))
...or move the two charts closer together
grid.arrange(plot1, plot2, ncol = 2, widths = c(1, 1.2),padding=0)

Resources