How to add a box around the legend in ggplot2? - r

Using the following script, how can I add a bottom legend box like the image bellow?
start <- c('2002 Q1', '2008 Q4')
end <- c('2003 Q3', '2011 Q2')
dates <- as.yearqtr(seq(as.Date('2002-01-01'), as.Date('2019-06-01'), by='quarter'))
cod <- tibble(start = as.yearqtr(start), end = as.yearqtr(end)) %>%
filter(start %in% dates) %>%
mutate(start = as.Date(start)) %>%
mutate(end = as.Date(end))
dates <- as.Date(dates)
tbl_fz <- tibble(x = dates, fz = 0.5)
plot <- ggplot(data = tbl_fz) +
geom_rect(data = cod, aes(xmin = start, xmax = end,
ymin = 0, ymax = 1, fill = "b"), alpha = 0.9) +
geom_line(aes(x = x, y = fz), size = 0.5) +
ggtitle('') +
theme_classic() +
theme(title = element_text(size = 8),
plot.title = element_text(hjust = 0.5),
legend.position = c(0.5, -0.5)) +
ylab('') +
xlab('') +
scale_x_date(date_breaks = '2 year', date_labels = '%Y',
expand = c(0, 0)) +
scale_y_continuous(expand = c(0,0)) +
scale_fill_manual(name = 'kkkk',
values = c('grey'),
labels = c('kkkk'))
plot

This could be achieved like so. To put the legend on the bottom I would recommend to
use legend.position = "bottom". To add the line to the legend map something on the color aes and use scale_color_manual as you have done for the fill aes. To get a box around the legend use legend.box.background = element_rect(color = "black"). I also added some margin on the top and to the left as the box was partially overlayed. Finally, to get the order of the legends right and e.g. to get a thicker line you can make use of guide_legend to style the legend.
# Packages ----------------------------------------------------------------
library(dplyr)
library(ggplot2)
library(zoo)
start <- c('2002 Q1', '2008 Q4')
end <- c('2003 Q3', '2011 Q2')
dates <- as.yearqtr(seq(as.Date('2002-01-01'), as.Date('2019-06-01'), by='quarter'))
cod <- tibble(start = as.yearqtr(start), end = as.yearqtr(end)) %>%
filter(start %in% dates) %>%
mutate(start = as.Date(start)) %>%
mutate(end = as.Date(end))
dates <- as.Date(dates)
tbl_fz <- tibble(x = dates, fz = 0.5)
plot <- ggplot(data = tbl_fz) +
geom_rect(data = cod, aes(xmin = start, xmax = end,
ymin = 0, ymax = 1, fill = "b"), alpha = 0.9) +
geom_line(aes(x = x, y = fz, color = "c"), size = 0.5) +
ggtitle('') +
theme_classic() +
theme(title = element_text(size = 8),
plot.title = element_text(hjust = 0.5),
legend.position = "bottom",
legend.box.background = element_rect(color = "black"),
legend.box.margin = margin(t = 1, l = 1)) +
scale_x_date(date_breaks = '2 year', date_labels = '%Y',
expand = c(0, 0)) +
scale_y_continuous(expand = c(0,0)) +
scale_fill_manual(values = c('grey'), labels = c('kkkk'))+
scale_color_manual(values = c('black'), labels = c('llll')) +
labs(x = NULL, y = NULL, fill = NULL, color = NULL) +
guides(fill = guide_legend(order = 1), color = guide_legend(order = 2, override.aes = list(size = 2)))
plot

Related

How to highlight non-zero start of y-axis in ggplot?

I try to replicate the fertility graph from gapminder as good as I can in ggplot:
I have:
fertility <- read.csv("https://raw.githubusercontent.com/MarcoKuehne/marcokuehne.github.io/main/data/Gapminder/tfr-by-gapminder-v12-20171212.csv", sep = ";")
# manipulate
library(tidyverse)
fertility <- fertility %>%
select(!c(geo.name, geo, indicator)) %>%
rownames_to_column %>%
gather(var, value, -rowname) %>%
spread(rowname, value) %>%
rename(year = var, fert = `1`) %>%
slice_head(n = 301)
fertility[,1:2] <- sapply(fertility[,1:2],FUN=as.numeric)
fertility1 <- fertility[1:217,]
fertility2 <- fertility[218:301,]
# visualize
ggplot(data = fertility, aes(x=year, y=fert)) +
geom_point(aes(x=year[1], y=fert[1]), size = 4) +
geom_text(aes(x=year[1], y=fert[1], label = year[1]), vjust = 2.5) +
geom_point(aes(x=year[165], y=fert[165]), size = 4) +
geom_text(aes(x=year[165], y=fert[165], label = year[165]), vjust = -3, hjust = -0.5) +
geom_text(aes(x=year[165], y=fert[165], label = "5 births"), vjust = -1.5, hjust = -0.2) +
#geom_point(aes(x=year[217], y=fert[217]), size = 4) +
geom_text(aes(x=year[217], y=fert[217], label = year[217]), vjust = -3) +
geom_text(aes(x=year[217], y=fert[217], label = "2.5 births"), vjust = -1.5) +
geom_line(data = fertility1, size=1.3, arrow=arrow(length=unit(0.30,"cm"), ends="last", type = "closed")) +
geom_line(data = fertility2, size=1.3,linetype="dashed") +
labs(title = "Average Number Of Babies Per Woman From 1800 to Today",
caption = "Source: Gapminder based on UN-Pop", y="", x="") +
theme_minimal() +
theme(panel.grid.major.x = element_blank(),
panel.grid.minor.x = element_blank())
I have some troubles with the linewidth of geom_line(). And I am looking for a compromise between geom_line() and geom_smooth() to make the line plot just a little bit more smooth.
But my major concern at the moment is the broken y-axis. When y-axis does not start at zero, I'd like to hightlight this as in the gapminder graph.
Following the suggestion by #Roland you could smooth your lines using a loess curve. However, instead of trying with geom_smooth I would suggest to do the calculation outside of ggplot as we need the smoothed values also for the points and the labels and we also need the same values for "both" lines.
For your axis break a simple approach would be to use two annotate, one to place the segments, one to add the filled "gap" for which I use a ribbon. This requires some fiddling to get the right positions, to fix the limits and to set clip="off". Also note that I added the axis lines via geom_h/vline so that the annotate layers could be placed on top of the axes.
Finally, I slightly adjusted your data wrangling code, use a dataset to place the labels and points and instead of using vjust I shift the labels directly via the y position.
EDIT For the final touch I added some additional styling.
# manipulate
library(tidyverse)
library(showtext)
library(hrbrthemes)
font_add_google("lato", "Lato", regular.wt = 900)
fertility <- fertility %>%
select(!c(geo.name, geo, indicator)) %>%
mutate(
across(-indicator.name, as.character),
across(-indicator.name, ~ readr::parse_number(.x, locale = locale(decimal_mark = ",")))
) %>%
pivot_longer(-indicator.name, names_to = "year", values_to = "fert", names_prefix = "X") %>%
mutate(year = as.numeric(year)) |>
mutate(fert_smooth = predict(loess(fert ~ year, span = .05)))
fertility_to_high <- fertility |>
filter(year %in% c(1800, 1964, 2016)) |>
mutate(
label = if_else(!year == 1800, paste0(year, "<br>**", round(fert, 1), " births**"), as.character(year)),
hjust = if_else(year == 2016, 0, .5)
)
ggplot(data = fertility, aes(x = year, y = fert_smooth)) +
geom_point(data = subset(fertility_to_high, year == 1964), size = 10, shape = 21, fill = NA, color = "black") +
geom_point(data = subset(fertility_to_high, year != 2016), size = 3) +
ggtext::geom_richtext(data = fertility_to_high, aes(y = fert + .15, label = label, hjust = hjust),
vjust = 0, label.colour = NA, family = font_rc, lineheight = 1.2) +
geom_line(linewidth = 1.3, linetype = "dashed") +
geom_line(data = ~ subset(.x, year <= 2016), linewidth = 1.3, arrow = arrow(length = unit(0.30, "cm"), ends = "last", type = "closed")) +
geom_hline(yintercept = 1) +
geom_vline(xintercept = 1785) +
annotate(geom = "ribbon", x = c(1780, 1790) - .55, ymin = c(1.3, 1.5), ymax = c(1.5, 1.7), fill = "white") +
annotate(
geom = "segment",
x = c(1780, 1780) - .5, xend = c(1790, 1790) - .5,
y = c(1.3, 1.5), yend = c(1.5, 1.7), linewidth = 1
) +
scale_y_continuous(breaks = 1:6, labels = c(0, 2:6), expand = c(0, .0, .05, 0)) +
scale_x_continuous(expand = c(0.05, 0, 0, 0)) +
labs(
title = toupper("Average Number Of Babies Per Woman From 1800 to Today"),
caption = "Source: Gapminder based on UN-Pop", y = "", x = ""
) +
coord_cartesian(clip = "off", ylim = c(1, 6), xlim = c(1800, NA)) +
theme_bw(base_family = font_rc, base_size = 12) +
theme(
panel.grid.major.x = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
plot.title = element_text(family = "lato"),
plot.title.position = "plot",
plot.caption = element_text(family = "lato", color = "grey40"),
axis.text.x = element_text(hjust = c(rep(.5, 3), 1))
)
The key is to add:
scale_y_continuous(limits = c(0, 6.2), expand = c(0, 0))
It tells R to limit the y range within 0 and 6.2, and show no gap beyond these two values.
The sample code below may work for you, after running it, you will get:
fertility <- read.csv("https://raw.githubusercontent.com/MarcoKuehne/marcokuehne.github.io/main/data/Gapminder/tfr-by-gapminder-v12-20171212.csv", sep = ";")
# manipulate
library(tidyverse)
fertility <- fertility |>
select(!c(geo.name, geo, indicator)) |>
t() |>
as.data.frame() |>
rownames_to_column() |>
slice(-1) |>
as_tibble() |>
rename(c(year = rowname, fert = V1)) |>
mutate(year = str_remove(year, "X"),
year = as.Date(ISOdate(year, 1, 1)),
fert = str_replace(fert, ",", "."),
fert = as.numeric(fert),
fert_1 = case_when(year <= as.Date("2017-01-01") ~ fert,
TRUE ~ as.numeric(NA)),
fert_2 = case_when(year >= as.Date("2017-01-01") ~ fert,
TRUE ~ as.numeric(NA)),
arr_data = case_when((year > as.Date("2014-01-01") & year < "2018-01-01") ~ fert,
TRUE ~ as.numeric(NA)))
fertility |>
ggplot(aes(x = year,
y = fert)) +
geom_smooth(aes(x = year,
y = fert_1,
group = 1),
span = 0.11,
se = FALSE,
colour = "black",
size = 1.5) +
geom_line(aes(x = year,
y = arr_data),
arrow = arrow(length=unit(0.4,"cm"),
ends="last",
type = "closed"),
size = 3) +
geom_line(aes(x = year,
y = fert_2,
group = 1),
linetype = 2,
size = 1.5) +
geom_point(aes(x = year[1],
y = fert[1]),
size = 5) +
annotate(geom = "text",
x = fertility$year[1],
y = fertility$fert[1],
label = "1800",
size = 4,
vjust = -1.2) +
geom_point(aes(x = fertility$year[166],
y = fertility$fert[166]),
shape = 1,
size = 12,
colour = "grey50") +
annotate(geom = "text",
x = fertility$year[166],
y = fertility$fert[166],
label = "1965",
size = 4,
vjust = -4.2) +
annotate(geom = "text",
x = fertility$year[166],
y = fertility$fert[166],
label = "5 births",
size = 5,
fontface = "bold",
vjust = -2) +
geom_point(aes(x = fertility$year[166],
y = fertility$fert[166]),
shape = 1,
size = 12,
colour = "grey50") +
annotate(geom = "text",
x = fertility$year[166],
y = fertility$fert[166],
label = "1965",
size = 4,
vjust = -4.2) +
annotate(geom = "text",
x = fertility$year[166],
y = fertility$fert[166],
label = "5 births",
size = 5,
fontface = "bold",
vjust = -2) +
annotate(geom = "text",
x = fertility$year[218],
y = fertility$fert[218],
label = "2017",
size = 4,
vjust = -4.2,
hjust = 0) +
annotate(geom = "text",
x = fertility$year[218],
y = fertility$fert[218],
label = "2.5 births",
size = 5,
fontface = "bold",
vjust = -2,
hjust = 0) +
theme_bw() +
scale_x_date(expand = expansion(mult = c(0.02, 0))) +
theme(
panel.grid.major.x = element_blank(),
panel.grid.minor.x = element_blank(),
panel.grid.minor.y = element_blank(),
panel.border = element_blank(),
axis.text.x = element_text(size = 10),
axis.line.x = element_line(),
axis.line.y = element_line(),
plot.caption = element_text(colour = "grey50"),
plot.title = element_text(size = 16,
hjust = 0.5,
face = "bold"),
plot.margin = margin(r = 18,
t = 5,
l = 5,
b = 2)
) +
labs(
title = "AVERAGE NUMBER OF BABIES PER WOMAN FROM 1800 TO TODAY",
x = element_blank(),
y = element_blank(),
caption = "Source:Gapminder[7] based on UN-Pop[3]"
)

ggplot2: how to extend y-axis labels across stacked figures

I have a dataframe of daily mean temperature observations for a year from two locations (i.e., Site 1 & Site 2). The three temperature variables are:
Air temperature
Water temperature
Difference = Air - Water
I would like to produce a four-panel figure where the top and bottom rows are Site 1 and Site 2 respectively, the left column displays Air and Water and the right column shows Difference. I am specifically not using facet_wrap() or facet_grid() because the y-axes are two different variables (i.e., temperature and temperature difference), and the legends are custom.
I can create the four-panel figure however, I would like the y-axis title for each column not to be repeated. Is there a way to extend each y-axis title across the stacked plots for each column?
Here is the output from the figure code below
The ideal figure would look something more like this
Example Data
library(data.table)
library(dplyr)
library(ggplot2)
library(patchwork)
set.seed(321)
# Create the example air and water temperature time series
df1 <- data.frame(matrix(ncol = 4, nrow = 365*4))
colnames(df1)[1:4] <- c("Location","Variable", "Date", "Temperature")
df1[1:730,1] <- "Site 1"
df1[731:NROW(df1),1] <- "Site 2"
df1[c(1:365,731:1095),2] <- "Air"
df1[c(366:730,1096:NROW(df1)),2] <- "Water"
df1$Date <- rep(seq.Date(as.Date("2021-01-01"),as.Date("2021-12-31"),"1 day"),4)
df1$noise <- rep(runif(365),4)
df1$t <- rep(seq(0,1*pi,,365),4)
for (i in 1:NROW(df1)) {
df1$Temperature[1:365] <- 20*sin(df1$t)+df1$noise*8
df1$Temperature[365:730] <- 17*sin(df1$t)+df1$noise*2
df1$Temperature[731:1095] <- 25*sin(df1$t)+df1$noise*6
df1$Temperature[1096:NROW(df1)] <- 18*sin(df1$t)+df1$noise*1.5
}
# Take the difference between air and water temperature
df1 <- df1[,1:4]
site1 <- df1[df1$Location == 'Site 1',]
site1 <- site1 %>%
tidyr::pivot_wider(names_from = Variable, values_from = Temperature) %>%
mutate(Difference = Air - Water)
site2 <- df1[df1$Location == 'Site 2',]
site2 <- site2 %>%
tidyr::pivot_wider(names_from = Variable, values_from = Temperature) %>%
mutate(Difference = Air - Water)
Code for figure
fig1a <- site1 %>%
ggplot() +
geom_line(aes(x = Date, y = Air), color = "red", size = 1) +
geom_line(aes(x = Date, y = Water), size = 1, alpha = 0.7) +
scale_x_date(date_breaks = "2 month",
date_labels = "%b",
expand = c(0, 0)) +
labs(x = NULL,
y = expression(paste("Temperature ( ", degree, "C)"))) +
annotate("rect", fill = "white", color = "black",
xmin = as.Date("2021-01-15", "%Y-%m-%d"),
xmax = as.Date("2021-03-15", "%Y-%m-%d"),
ymin = 26, ymax = 30) +
annotate("segment", color = "red", size = 1,
x = as.Date("2021-01-20", "%Y-%m-%d"),
xend = as.Date("2021-01-30", "%Y-%m-%d"),
y = 29, yend = 29) +
annotate("segment", color = "black", size = 1,
x = as.Date("2021-01-20", "%Y-%m-%d"),
xend = as.Date("2021-01-30", "%Y-%m-%d"),
y = 27, yend = 27) +
annotate("text", x = as.Date("2021-02-01", "%Y-%m-%d"), y = 29,
label = 'Air',
size = 5, fontface = 1, hjust = 0) +
annotate("text", x = as.Date("2021-02-01", "%Y-%m-%d"), y = 27,
label = "Water",
size = 5, fontface = 1, hjust = 0) +
annotate("text", x = as.Date("2021-11-30", "%Y-%m-%d"), y = 30,
label = "(a",
size = 6, fontface = 1, hjust = 0) +
theme_bw() +
theme(plot.margin = unit(c(0, 1, 0, 1), "lines"),
panel.grid = element_blank(),
text = element_text(size = 16),
axis.text.x = element_text(size = 14, color = "white"),
axis.text.y = element_text(size = 14, color = "black"),
panel.border = element_blank()) +
guides(fill = guide_legend(nrow = 2)) +
coord_cartesian(clip = 'off', ylim = c(0, 32)) +
annotation_custom(grid::rectGrob(gp = grid::gpar(fill = NA)))
fig1b <- site2 %>%
ggplot() +
geom_line(aes(x = Date, y = Air), color = "red", size = 1) +
geom_line(aes(x = Date, y = Water), size = 1, alpha = 0.7) +
scale_x_date(date_breaks = "2 month",
date_labels = "%b",
expand = c(0, 0)) +
labs(x = NULL,
y = expression(paste("Temperature ( ", degree, "C)"))) +
annotate("text", x = as.Date("2021-11-30", "%Y-%m-%d"), y = 30,
label = "(b",
size = 6, fontface = 1, hjust = 0) +
theme_bw() +
theme(plot.margin = unit(c(0, 1, 2, 0), "lines"),
panel.grid = element_blank(),
text = element_text(size = 16),
axis.text.x = element_text(size = 16, color = "black", angle = 90, vjust = 0.5, hjust = 1),
axis.text.y = element_text(size = 16, color = "black"),
panel.border = element_blank()) +
guides(fill = guide_legend(nrow = 2)) +
coord_cartesian(clip = 'off', ylim = c(0,32)) +
annotation_custom(grid::rectGrob(gp = grid::gpar(fill = NA))) +
annotate(geom = "text", x = as.Date("2021-06-15", "%Y-%m-%d"), y = -7, label = 2021, size = 6)
fig1c <- site1 %>%
ggplot() +
geom_line(aes(x = Date, y = Difference), size = 1) +
scale_x_date(date_breaks = "2 month",
date_labels = "%b",
expand = c(0, 0)) +
labs(x = NULL,
y = expression(paste(Delta~" Temperature ( ", degree, "C)"))) +
annotate("rect", fill = "white", color = "black",
xmin = as.Date("2021-01-15", "%Y-%m-%d"),
xmax = as.Date("2021-06-01", "%Y-%m-%d"),
ymin = 10.25, ymax = 12.5) +
annotate("text", x = as.Date("2021-01-20", "%Y-%m-%d"), y = 12,
label = 'Pos. = Air > Water',
size = 5, fontface = 1, hjust = 0) +
annotate("text", x = as.Date("2021-01-20", "%Y-%m-%d"), y = 11,
label = "Neg. = Water > Air",
size = 5, fontface = 1, hjust = 0) +
annotate("text", x = as.Date("2021-11-30", "%Y-%m-%d"), y = 12,
label = "(c",
size = 6, fontface = 1, hjust = 0) +
theme_bw() +
theme(plot.margin = unit(c(0, 1, 0, 0), "lines"),
panel.grid = element_blank(),
text = element_text(size = 16),
axis.text.x = element_text(size = 14, color = "white"),
axis.text.y = element_text(size = 14, color = "black"),
panel.border = element_blank()) +
guides(fill = guide_legend(nrow = 2)) +
coord_cartesian(clip = 'off', ylim = c(-2, 12)) +
annotation_custom(grid::rectGrob(gp = grid::gpar(fill = NA)))
fig1d <- site2 %>%
ggplot() +
geom_line(aes(x = Date, y = Difference), size = 1) +
scale_x_date(date_breaks = "2 month",
date_labels = "%b",
expand = c(0, 0)) +
labs(x = NULL,
y = expression(paste(Delta~" Temperature ( ", degree, "C)"))) +
annotate("text", x = as.Date("2021-11-30", "%Y-%m-%d"), y = 12,
label = "(d",
size = 6, fontface = 1, hjust = 0) +
theme_bw() +
theme(plot.margin = unit(c(1, 1, 2, 0), "lines"),
panel.grid = element_blank(),
text = element_text(size = 16),
axis.text.x = element_text(size = 16, color = "black", angle = 90, vjust = 0.5, hjust = 1),
axis.text.y = element_text(size = 16, color = "black"),
panel.border = element_blank()) +
guides(fill = guide_legend(nrow = 2)) +
coord_cartesian(clip = 'off', ylim = c(-2,12)) +
annotation_custom(grid::rectGrob(gp = grid::gpar(fill = NA))) +
annotate(geom = "text", x = as.Date("2021-06-15", "%Y-%m-%d"), y = -5, label = 2021, size = 6)
# width = 1200 height = 900
fig1a + fig1c + fig1b + fig1d + plot_layout(ncol = 2)
I figured out how to do it, which required using facet_grid. I ended up needing to create two facets that I then stitched together, see below.
# Take the difference between air and water temperature
df1 <- df1[,1:4]
site1 <- df1[df1$Location == 'Site 1',]
site1 <- site1 %>%
tidyr::pivot_wider(names_from = Variable, values_from = Temperature) %>%
mutate(Difference = Air - Water) %>%
tidyr::pivot_longer(cols = c('Water','Air','Difference'),
names_to = 'Variable',
values_to = 'Temperature')
site2 <- df1[df1$Location == 'Site 2',]
site2 <- site2 %>%
tidyr::pivot_wider(names_from = Variable, values_from = Temperature) %>%
mutate(Difference = Air - Water) %>%
tidyr::pivot_longer(cols = c('Water','Air','Difference'),
names_to = 'Variable',
values_to = 'Temperature')
df1 <- rbind(site1,site2)
# This creates the first column of figures
p1 <- df1 %>%
subset(!df1$Variable == 'Difference',) %>%
mutate(var_air_water = ## Here is the new variable
if_else(Variable %in% c("Air", "Water"),
true = "Air & Water",
false = Variable)) %>%
ggplot() +
geom_line(aes(x = Date, y = Temperature, group = Variable, color = Variable)) +
scale_color_manual(values = c("black", "red")) +
scale_x_date(date_breaks = "2 month",
date_labels = "%b",
expand = c(0, 0)) +
scale_y_continuous(breaks = seq(0,30,5)) +
labs(x = NULL,
y = expression(paste("Temperature ( ", degree, "C)"))) +
theme_bw() +
theme(plot.margin = unit(c(0, 1, 2, 0), "lines"),
panel.grid = element_blank(),
text = element_text(size = 16),
axis.text.x = element_text(size = 16, color = "black", angle = 90, vjust = 0.5, hjust = 1),
axis.text.y = element_text(size = 16, color = "black"),
legend.title = element_blank(),
legend.text = element_text(size = 16),
legend.position = c(0.15,0.95),
legend.background = element_blank(),
strip.text = element_blank(),
strip.background = element_blank(),
panel.border = element_blank()) +
guides(fill = guide_legend(nrow = 2)) +
coord_cartesian(clip = 'off', ylim = c(0,33)) +
annotation_custom(grid::rectGrob(gp = grid::gpar(fill = NA))) +
annotate(geom = "text", x = as.Date("2021-06-15", "%Y-%m-%d"), y = -8, label = 2021, size = 6) +
facet_grid(Location~var_air_water)
# This adds the text to the corresponding figures
dat_text1 <- data.frame(
label = c("(a","(b"),
Location = c('Site 1','Site 2'),
x = c(as.Date("2021-12-01", "%Y-%m-%d"),
as.Date("2021-12-01", "%Y-%m-%d")),
y = c(32,32))
p1 <- p1 + geom_text(
data = dat_text1,
mapping = aes(x = x, y = y, label = label,
hjust = 0,
vjust = 1),
size = 5)
# This creates the second column of figures
p2 <- df1 %>%
mutate(var_air_water = ## Here is the new variable
if_else(Variable %in% c("Air", "Water"),
true = "Air & Water",
false = Variable)) %>%
subset(!var_air_water == c('Air & Water'),) %>%
ggplot() +
geom_line(aes(x = Date, y = Temperature, group = Variable, color = Variable)) +
scale_color_manual(values = "black") +
scale_x_date(date_breaks = "2 month",
date_labels = "%b",
expand = c(0, 0)) +
scale_y_continuous(breaks = seq(0,12,2)) +
labs(x = NULL,
y = expression(paste(Delta~" Temperature ( ", degree, "C)"))) +
theme_bw() +
theme(plot.margin = unit(c(0, 1, 2, 0), "lines"),
panel.grid = element_blank(),
text = element_text(size = 16),
axis.text.x = element_text(size = 16, color = "black", angle = 90, vjust = 0.5, hjust = 1),
axis.text.y = element_text(size = 16, color = "black"),
legend.position = 'none',
strip.background = element_rect(fill = "gray80"),
strip.background.x = element_blank(),
strip.text.x = element_blank(),
strip.text.y = element_text(size = 16)) +
guides(fill = guide_legend(nrow = 2)) +
coord_cartesian(clip = 'off', ylim = c(0,12)) +
annotation_custom(grid::rectGrob(gp = grid::gpar(fill = NA))) +
annotate(geom = "text", x = as.Date("2021-06-15", "%Y-%m-%d"), y = -3, label = 2021, size = 6) +
facet_grid(Location~var_air_water)
# Like above, this adds the text to the corresponding figures
dat_text2 <- data.frame(
label = c("Pos. = Air > Water", "Neg. = Water > Air", "(c","(d"),
Location = c('Site 1','Site 1','Site 1','Site 2'),
x = c(as.Date("2021-01-15", "%Y-%m-%d"),
as.Date("2021-01-15", "%Y-%m-%d"),
as.Date("2021-12-01", "%Y-%m-%d"),
as.Date("2021-12-01", "%Y-%m-%d")),
y = c(12,11,12,12))
p2 <- p2 + geom_text(
data = dat_text2,
mapping = aes(x = x, y = y, label = label,
hjust = 0,
vjust = 1,
size = 10),
size = 5)
# width = 1200 height = 900
p1 + p2 + plot_layout(ncol = 2)

Adding a black outline for each x category in heat map in R

Here is my data which produces a heat map. What I am hoping to do is produce multiple difference heatmaps with an outline around each of x categories.
data <- data.frame(id=c("john","john","john","kate","kate","kate","chris","chris","chris"),
group=c("geo","his","math","geo","his","math","geo","his","math"),
grade=c(65,76,87,67,89,98,99,97,96),
class=c("A","A","A","A","A","A","B","B","B"))
data
mine.heatmap <- ggplot(data = data, mapping = aes(x = id, y = group, fill = grade)) +
geom_tile() +
xlab(label = "id") +
ylab(label="group") +
labs(fill="grade")+
scale_fill_gradient2(low = "#800080",
high = "#FF8C00",mid = "white")
x <- mine.heatmap + facet_grid(
cols = vars(class), scales = "free", space = "free"
)
x + theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1, size = 18, margin = margin(b=2)))+
theme(axis.text.y= element_text(angle = 0, vjust = 0.5, hjust=1, size = 18)) +
theme(legend.text = element_text(size=14))+
theme(legend.title = element_text(size=14))+
theme(strip.text = element_text(size=14))+
theme(axis.title.x = element_text(size=18)) +theme(axis.title.y = element_text(size=18))
Original Heat map:
What I am hoping to get are the following heatmaps:
One option to achieve your desired result would be to
put your plotting code in a function which takes as one argument the id for which you want to draw a outline.
Use some data wrangling to convert the categories to be plotted on the x and y aes to numerics per facet variable.
Add a geom_rect to your plotting code to draw the outline which uses the numerics computed in step 2.
library(ggplot2)
library(dplyr)
mine_heatmap <- function(x) {
p <- ggplot(data = data, mapping = aes(x = id, y = group, fill = grade)) +
geom_tile() +
# Add outline via a geom_rect
geom_rect(
data = subset(data, id == x),
aes(
xmin = id_num - .5, xmax = id_num + .5,
ymin = min(group_num) - .5, ymax = max(group_num) + .5
), fill = NA, color = "black", size = 1
) +
labs(x = "id", y = "group", fill = "grade") +
scale_fill_gradient2(
low = "#800080",
high = "#FF8C00", mid = "white"
)
p <- p + facet_grid(
cols = vars(class), scales = "free", space = "free"
)
p + theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1, size = 18, margin = margin(b = 2))) +
theme(axis.text.y = element_text(angle = 0, vjust = 0.5, hjust = 1, size = 18)) +
theme(legend.text = element_text(size = 14)) +
theme(legend.title = element_text(size = 14)) +
theme(strip.text = element_text(size = 14)) +
theme(axis.title.x = element_text(size = 18)) + theme(axis.title.y = element_text(size = 18))
}
# Convert id and group to numerics per facet variable
data <- data |>
group_by(class) |>
mutate(
id_num = as.numeric(factor(id)),
group_num = as.numeric(factor(group))
) |>
ungroup()
mine_heatmap("john")
mine_heatmap("kate")
mine_heatmap("chris")

In ggplot2, how can I display the month abbreviation on the x-axis vertically with the year below the months horizontally?

I would like the following time series to display the month abbreviation (e.g., Jan, Feb, etc.) in chronological order with a vertical orientation while also displaying the year horizontally below the months. Please see the ideal figure example below.
Example Data:
library(dplyr)
library(ggplot2)
df1 <- data.frame(matrix(ncol = 3, nrow = 12))
colnames(df1)[1:3] <- c("Date", "Group", "Value")
df1$Date <- rep(seq.Date(as.Date("2020-03-14"),as.Date("2020-08-20"),"1 month"),2)
df1$Group <- sort(rep(c("A","B"),6))
df1$Value <- rnorm(12,50,10)
df1 <- df1 %>%
mutate(Month = month(Date),
Year = year(Date),
date = zoo::as.yearmon(paste(Year, Month), "%Y %m"))
df2 <- data.frame(matrix(ncol = 3, nrow = 12))
colnames(df2)[1:3] <- c("Date", "Group", "Value")
df2$Date <- rep(seq.Date(as.Date("2021-03-14"),as.Date("2021-08-20"),"1 month"),2)
df2$Group <- sort(rep(c("A","B"),6))
df2$Value <- rnorm(12,50,10)
df2 <- df2 %>%
mutate(Month = month(Date),
Year = year(Date),
date = zoo::as.yearmon(paste(Year, Month), "%Y %m"))
df3 <- rbind(df1,df2)
Example Figure: This code produces the figure below with the year associated with each month, both vertically oriented.
cols <- c("A" = "#ca0020", "B" = "#0571b0")
ggplot(data = df3, aes(x = factor(date), y = Value, color = Group, group = paste(Year,Group))) +
geom_line() +
geom_point(size = 3, aes(fill = Group), color = "black", shape = 21) +
scale_fill_manual(values = cols) +
scale_color_manual(values = cols) +
labs(x = "") +
theme_bw() +
theme(panel.grid = element_blank(),
text = element_text(size = 16),
axis.text.x = element_text(size = 14, color = "black", angle = 90, vjust = 0.5, hjust = 1),
axis.text.y = element_text(size = 14, color = "black"),
legend.title = element_blank(),
legend.direction = "horizontal",
legend.margin = margin(),
legend.background = element_blank(),
legend.position = c(0.1,0.93),
panel.border = element_blank()) +
guides(fill = guide_legend(nrow = 2)) +
coord_cartesian(clip = 'off', ylim = c(0, 100)) +
annotation_custom(grid::rectGrob(gp = grid::gpar(fill = NA))) +
annotate('rect',
xmin = 6.35,
xmax = 6.65,
ymin = -10, ymax = 0, fill = 'white') +
annotate('segment',
x = c(6.35, 6.65),
xend = c(6.35, 6.65), y = -10, yend = 0)
Ideal Figure: The ideal figure would display the month vertically and the year horizontally (please see below). I'm pretty sure I can add the year horizontally based off this SO question, I am just having an issue with displaying the month without the year in chronological order.
I have tried using zoo::scale_x_yearmon(format = "%b") in the example figure code above but have not had success with this approach.
Here a solution partially based in Multi-row x-axis labels in ggplot line chart
ggplot(data = df3, aes(x = factor(date), y = Value, color = Group, group = paste(Year,Group))) +
geom_line() +
geom_point(size = 3, aes(fill = Group), color = "black", shape = 21) +
scale_fill_manual(values = cols) +
scale_color_manual(values = cols) +
scale_x_discrete(labels=substr(df3$date,1,3))+
labs(x = "") +
theme_bw() +
theme(plot.margin = unit(c(1, 1, 2, 1), "lines"),
panel.grid = element_blank(),
text = element_text(size = 16),
axis.text.x = element_text(size = 14, color = "black", angle = 90, vjust = 0.5, hjust = 1),
axis.text.y = element_text(size = 14, color = "black"),
legend.title = element_blank(),
legend.direction = "horizontal",
legend.margin = margin(),
legend.background = element_blank(),
legend.position = c(0.1,0.93),
panel.border = element_blank()) +
guides(fill = guide_legend(nrow = 2)) +
coord_cartesian(clip = 'off', ylim = c(0, 100)) +
annotation_custom(grid::rectGrob(gp = grid::gpar(fill = NA))) +
annotate(geom = "text", x = 3.5 + 6 * (0:1), y = -30, label = unique(df3$Year), size = 6) +
annotate('rect',
xmin = 6.35,
xmax = 6.65,
ymin = -10, ymax = 0, fill = 'white') +
annotate('segment',
x = c(6.35, 6.65),
xend = c(6.35, 6.65), y = -10, yend = 0)
Alternative:
Using strip.position = "bottom" in facet_wrap
library(tidyverse)
library(lubridate)
df3 %>%
mutate(Month = month(date, label=TRUE, abbr=TRUE),
Year = year(date)) %>%
arrange(Year) %>%
ggplot(aes(x = Month, y = Value, color = Group, group = paste(Year,Group))) +
geom_line() +
geom_point(size = 3, aes(fill = Group), color = "black", shape = 21) +
scale_fill_manual(values = cols) +
scale_color_manual(values = cols) +
labs(x = "") +
coord_cartesian(clip = 'off', ylim = c(0, 100)) +
facet_wrap(~Year, scales = "free_x", strip.position = "bottom") +
theme(strip.background = element_blank(),
strip.placement = "outside") +
theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))

How do I combined a line graph and a bar graph in R with two different y axis

I have my code with that created two different graphs. I want to combine the graphs into one graph with "Elevation on the primary y axis and "Precipitation" on the secondary y axis. Is this possible or would it be best to stick with having the two graphs on top of each other?
pe1.plot <- combine.df %>% filter(site== "VWP 1") %>%
ggplot(aes(x = datetime, y = elevation)) +
geom_line(color = "blue")+
theme(plot.subtitle = element_text(vjust = 1),
plot.caption = element_text(vjust = 1),
plot.background = element_rect(linetype = "solid")) +labs(title = "VWP 1", x = "Date", y = "Elevation (MSL)")
precip.plot <- ggplot(precip.df, aes(x = datetime, y = precipitation)) +
geom_bar(stat = "identity")+
theme(plot.subtitle = element_text(vjust = 1),
plot.caption = element_text(vjust = 1),
plot.background = element_rect(linetype = "solid")) +labs(x = "Date", y = "Pecipitation (in.)")
pe1.plot+precip.plot + plot_layout(ncol = 1)
Possible but kind of a pain:
library(tidyverse)
set.seed(42)
my_data = tibble(date = seq.Date(as.Date("2020-01-01"),
as.Date("2020-06-30"), by = "day"),
line_y = seq(698, 700, length.out = 182) + rnorm(182, sd = 0.1),
bar_y = rpois(182, c(0,0,5))/10)
ggplot(my_data, aes(date)) +
geom_line(aes(y = line_y)) +
geom_col(aes(y = (2*bar_y) + 695)) +
scale_y_continuous(breaks = seq(695, 699.5, by = 0.5),
labels = c(rep("", 6), seq(698, 699.5, by = 0.5)),
sec.axis = sec_axis(~ (. - 695) / 2,
breaks = seq(0, 1, by = 0.25))) +
coord_cartesian(ylim = c(695, NA), expand = 0)

Resources