Skip to content

Instantly share code, notes, and snippets.

@cavedave
Last active May 26, 2026 13:28
Show Gist options
  • Select an option

  • Save cavedave/8551cfbb879ed28f24bfbcb321773c47 to your computer and use it in GitHub Desktop.

Select an option

Save cavedave/8551cfbb879ed28f24bfbcb321773c47 to your computer and use it in GitHub Desktop.
UK May temperatures. data from https://www.metoffice.gov.uk/hadobs/hadcet/
#!/usr/bin/env Rscript
##
## HadCET — daily mean / min / max for every day in May (Central England)
##
## Mirrors the logic in may1st_v2.ipynb for sharing via gist.github or similar.
##
## Depends: tidyverse, lubridate
## install.packages(c("tidyverse", "lubridate"))
##
## Usage (writes three PNG files in current working directory):
## Rscript hadcet_may_plots.R
##
## Source data:
## https://www.metoffice.gov.uk/hadobs/hadcet/data/
suppressPackageStartupMessages({
library(tidyverse)
library(lubridate)
})
base <- "https://www.metoffice.gov.uk/hadobs/hadcet/data/"
dest_mean <- "meantemp_daily_totals.txt"
dest_min <- "mintemp_daily_totals.txt"
dest_max <- "maxtemp_daily_totals.txt"
download.file(paste0(base, dest_mean), dest_mean, method = "libcurl")
download.file(paste0(base, dest_min), dest_min, method = "libcurl")
download.file(paste0(base, dest_max), dest_max, method = "libcurl")
cat(
"Downloaded:\n",
" ", normalizePath(dest_mean), "\n",
" ", normalizePath(dest_min), "\n",
" ", normalizePath(dest_max), "\n",
sep = ""
)
parse_hadcet_txt <- function(path) {
raw_lines <- readLines(path, warn = FALSE)
raw_lines <- raw_lines[!grepl("^\\s*$", raw_lines)]
hdr <- grep("^Date", raw_lines, ignore.case = TRUE)[1]
if (!is.finite(hdr)) hdr <- 1L
body_lines <- raw_lines[-(seq_len(hdr))]
split_lines <- str_split_fixed(body_lines, "\\s+", 2)
tibble(
Date = ymd(split_lines[, 1]),
value = as.numeric(split_lines[, 2])
) |> filter(!is.na(Date))
}
cet_data <- parse_hadcet_txt("meantemp_daily_totals.txt") |> rename(mean_temp = value) |>
full_join(
parse_hadcet_txt("mintemp_daily_totals.txt") |> rename(min_temp = value),
by = "Date"
) |>
full_join(
parse_hadcet_txt("maxtemp_daily_totals.txt") |> rename(max_temp = value),
by = "Date"
)
if (sum(is.na(cet_data |> select(mean_temp, min_temp, max_temp))) > 0) {
warning("Some mean/min/max values are NA after merge (series start at different dates).")
}
# -----------------------------------------------------------------------------
# Charts: May only, stacked by calendar year; colour = °C bins; minimal y headroom.
hot <- c(
"#6BBCD1", "#a6bddb", "#fed976", "#feb24c", "#fd8d3c",
"#fc4e2a", "#e31a1c", "#b10026", "#800026", "#4d004b"
)
temp_breaks <- c(-5, 2, 4, 6, 8, 10, 12, 14, 16, 18, Inf)
temp_labels <- c(
"<2", "2-4", "4-6", "6-8", "8-10",
"10-12", "12-14", "14-16", "16-18", "18+"
)
may_daily_from <- function(df, temp_nm) {
df |>
filter(month(Date) == 5L, !is.na(.data[[temp_nm]])) |>
mutate(
yr = year(Date),
temps = cut(.data[[temp_nm]], breaks = temp_breaks, labels = temp_labels)
)
}
may_scatter_plot <- function(may_tbl, temp_nm, title, ylab) {
vals <- dplyr::pull(may_tbl, temp_nm)
vmin <- suppressWarnings(min(vals, na.rm = TRUE))
vmax <- suppressWarnings(max(vals, na.rm = TRUE))
## Range-frame on 5 °C grid + tight ceiling (~1 °C padding before rounding up)
y_lo <- floor(vmin / 5) * 5L
y_hi <- ceiling((vmax + 1.2) / 5) * 5L
x_lo <- floor(min(may_tbl$yr, na.rm = TRUE) / 20) * 20L
x_hi <- ceiling(max(may_tbl$yr, na.rm = TRUE) / 20) * 20L
latest <- format(max(may_tbl$Date, na.rm = TRUE), "%Y-%m-%d")
ggplot(may_tbl, aes(x = yr, y = .data[[temp_nm]], colour = temps)) +
geom_point(alpha = 0.65, size = 0.9) +
geom_smooth(
method = "loess", span = 0.4, se = FALSE,
colour = "black", linewidth = 0.6
) +
scale_colour_manual(values = hot) +
scale_y_continuous(
limits = c(y_lo, NA),
breaks = seq(y_lo, y_hi, by = 5L),
expand = expansion(mult = c(0.012, 0.025))
) +
scale_x_continuous(
breaks = seq(x_lo, x_hi, by = 20L),
expand = expansion(mult = 0.02)
) +
labs(
title = title,
subtitle = paste("Daily HadCET through", latest),
x = "Year",
y = ylab,
colour = "°C bin",
caption = "Source: Met Office HadCET\nGraphic: @iamreddave"
) +
theme_bw() +
theme(
plot.title = element_text(hjust = 0.5),
panel.border = element_blank(),
axis.line.x = element_line(linewidth = 0.4, colour = "grey25"),
axis.line.y = element_line(linewidth = 0.4, colour = "grey25")
)
}
May_mean <- may_daily_from(cet_data, "mean_temp")
May_min <- may_daily_from(cet_data, "min_temp")
May_max <- may_daily_from(cet_data, "max_temp")
## Extremes for labels (tie-break: dplyr keeps first matching row).
may_rec <- May_max |> dplyr::slice_max(order_by = max_temp, n = 1L, with_ties = FALSE)
may_cold <- May_max |> dplyr::slice_min(order_by = max_temp, n = 1L, with_ties = FALSE)
xr <- may_rec$yr[[1]]
y0 <- may_rec$max_temp[[1]]
rec_txt <- format(may_rec$Date[[1]], "%d %B %Y")
xc <- may_cold$yr[[1]]
yc <- may_cold$max_temp[[1]]
cold_txt <- format(may_cold$Date[[1]], "%d %B %Y")
mean_hot <- May_mean |> dplyr::slice_max(order_by = mean_temp, n = 1L, with_ties = FALSE)
mean_cold <- May_mean |> dplyr::slice_min(order_by = mean_temp, n = 1L, with_ties = FALSE)
xmh <- mean_hot$yr[[1]]
ymh <- mean_hot$mean_temp[[1]]
mean_hot_txt <- format(mean_hot$Date[[1]], "%d %B %Y")
xml <- mean_cold$yr[[1]]
yml <- mean_cold$mean_temp[[1]]
mean_cold_txt <- format(mean_cold$Date[[1]], "%d %B %Y")
y_mean_top <- max(ymh + 0.92, yml + 2.9)
night_mild <- May_min |> dplyr::slice_max(order_by = min_temp, n = 1L, with_ties = FALSE)
night_frigid <- May_min |> dplyr::slice_min(order_by = min_temp, n = 1L, with_ties = FALSE)
xnm <- night_mild$yr[[1]]
ynm <- night_mild$min_temp[[1]]
night_mild_txt <- format(night_mild$Date[[1]], "%d %B %Y")
xnf <- night_frigid$yr[[1]]
ynf <- night_frigid$min_temp[[1]]
night_frigid_txt <- format(night_frigid$Date[[1]], "%d %B %Y")
y_min_top <- max(ynm + 0.82, ynf + 3.35)
y_panel_top <- max(y0 + 1.42, yc + 3.15)
p_mean <- may_scatter_plot(May_mean, "mean_temp", "Mean Temperature each May Day — Central England", "Mean temp °C") +
expand_limits(y = y_mean_top) +
annotate("segment",
x = xmh, xend = xmh - 6, y = ymh, yend = ymh + 0.65,
linewidth = 0.3, colour = "grey25", lineend = "round"
) +
annotate("text",
x = xmh - 6.5, y = ymh + 0.72, label = mean_hot_txt,
size = 2.95, colour = "grey20", lineheight = 1, hjust = 1, fontface = "plain"
) +
annotate("segment",
x = xml + 1.0, xend = xml + 0.42, y = yml + 1, yend = yml + 0.12,
linewidth = 0.3, colour = "grey25", lineend = "round"
) +
annotate("text",
x = xml + 1.05, y = yml + 1.08, label = mean_cold_txt,
size = 2.95, colour = "grey20", lineheight = 1, hjust = 0, fontface = "plain"
)
p_min <- may_scatter_plot(May_min, "min_temp", "Minimum Temperature each May Day — Central England", "Daily min °C") +
expand_limits(y = y_min_top) +
annotate("segment",
x = xnm, xend = xnm - 6, y = ynm, yend = ynm + 0.65,
linewidth = 0.3, colour = "grey25", lineend = "round"
) +
annotate("text",
x = xnm - 6.5, y = ynm + 0.72, label = night_mild_txt,
size = 2.95, colour = "grey20", lineheight = 1, hjust = 1, fontface = "plain"
) +
annotate("segment",
x = xnf + 1.0, xend = xnf + 0.42, y = ynf + 1, yend = ynf + 0.12,
linewidth = 0.3, colour = "grey25", lineend = "round"
) +
annotate("text",
x = xnf + 1.05, y = ynf + 1.08, label = night_frigid_txt,
size = 2.95, colour = "grey20", lineheight = 1, hjust = 0, fontface = "plain"
)
p_max <- may_scatter_plot(May_max, "max_temp", "Maximum Temperature each May Day — Central England", "Daily max °C") +
expand_limits(y = y_panel_top) +
annotate("segment",
x = xr, xend = xr - 6, y = y0, yend = y0 + 1.05,
linewidth = 0.3, colour = "grey25", lineend = "round"
) +
annotate("text",
x = xr - 6.5, y = y0 + 1.12, label = rec_txt,
size = 2.95, colour = "grey20", lineheight = 1, hjust = 1, fontface = "plain"
) +
annotate("segment",
x = xc + 1.0, xend = xc + 0.42, y = yc + 1, yend = yc + 0.12,
linewidth = 0.3, colour = "grey25", lineend = "round"
) +
annotate("text",
x = xc + 1.05, y = yc + 1.08, label = cold_txt,
size = 2.95, colour = "grey20", lineheight = 1, hjust = 0, fontface = "plain"
)
print(p_mean)
print(p_min)
print(p_max)
ggsave("May_daily_Mean_Temps_CET.png", plot = p_mean, width = 10, height = 6, dpi = 300)
ggsave("May_daily_Min_Temps_CET.png", plot = p_min, width = 10, height = 6, dpi = 300)
ggsave("May_daily_Max_Temps_CET.png", plot = p_max, width = 10, height = 6, dpi = 300)
cat("Saved May_daily_Mean_Temps_CET.png, May_daily_Min_Temps_CET.png, May_daily_Max_Temps_CET.png\n")
@cavedave

Copy link
Copy Markdown
Author
May_daily_Max_Temps_CET

@cavedave

Copy link
Copy Markdown
Author
May_daily_Mean_Temps_CET

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment