Skip to contents

Detecting long-term shifts in annual SST-SSTA-DHW data using the reconstructed historical OISST2 dataset using BEAST (a Bayesian model averaging algorithm) to detect abrupt changes, trends, and periodic variations.

Sea Surface Temperatures

GBR-wide mean summer SST annual time series (°C):

  • Calculate spatial mean (GBR-area mean SST for each day)
  • Filter Jan–Mar (peak austral-summer conditions for the GBR).
  • Aggregate for each year the mean of the Jan–Mar daily GBR-area means

Output:

  • Top (trend): grey dots are annual Jan–Mar SST metric; green line is BEAST’s estimated underlying trend, flat to slightly variable through mid-century, then shows a clear upward shift / acceleration beginning around the late 1990s into early 2000s, continuing upward thereafter.
  • Pr(tcp): this is the posterior probability of a trend changepoint at each year. The dominant peak is around ~1998–2002, meaning BEAST sees the most plausible structural change in the long-term evolution there (often interpreted as a regime shift / onset of sustained warming in this metric).
  • orderT: BEAST’s inferred trend complexity/order over time (how curved the trend is allowed/needed to be). orderT rises into the late 1990s/early 2000s and then stabilizes higher, consistent with the trend needing more structure once the warming period begins (i.e., not just noise around a flat baseline).
  • slpSign: posterior support for the sign of the trend slope through time. Pre-~1998 it’s closer to “weak/uncertain or mixed,” then post-~1998 it shifts strongly toward persistent positive slope (sustained warming) for the remainder of the record.
  • error: residuals (what’s left after removing the fitted components), i.e. interannual variability around the inferred trend.
Code
library(terra)
library(Rbeast)
library(tidyverse)

GBR_OISST2_SST <- rast("/Users/rof011/GBR-dhw/datasets/GBR_OISST2_SST.tif")

sst_ts <- terra::global(GBR_OISST2_SST, mean, na.rm = TRUE) %>%
  mutate(date = as.Date(rownames(.))) |>
  mutate(
    year  = year(date),
    month = month(date),
    austral_year = if_else(month == 12L, year + 1L, year)  # Dec belongs to next year
  ) %>%
  filter(month %in% seq(1:3)) %>%
  filter(austral_year > 1940, austral_year < 2026) %>%
  group_by(austral_year) %>%
  summarise(sst = mean(mean, na.rm = TRUE), .groups = "drop") %>%
  rename(year = austral_year)

sst_ts <- sst_ts %>% arrange(year)
y_sst <- sst_ts$sst
t_sst <- sst_ts$year

beast_sst <- beast(
  y_sst,
  start  = t_sst[1],
  deltat = 1,
  season = "none",
  print.progress = FALSE,
  quiet = TRUE
)

cp_sst   <- beast_sst$trend$cp
cppr_sst <- beast_sst$trend$cpPr
if (is.null(cppr_sst)) cppr_sst <- beast_sst$trend$cpProb

sst_cp_tbl <- tibble(cp = cp_sst, cp_prob = cppr_sst) %>%
  mutate(year = as.integer(round(cp))) %>%
  arrange(desc(cp_prob))

breakpoints_sst <- sst_cp_tbl$cp[1:min(2, nrow(sst_cp_tbl))]
breakyears_sst  <- sst_cp_tbl$year[1:min(2, nrow(sst_cp_tbl))]

plot(beast_sst)

SST Anomalies

GBR-wide mean summer SSTA annual time series (°C anomaly):

  • Calculate spatial mean (GBR-area mean SST anomaly for each day)
  • Filter Jan–Mar (peak austral-summer conditions for the GBR).
  • Aggregate for each year the mean of the Jan–Mar daily GBR-area means

Output:

  • Trend: near-flat to mildly variable through mid-century, then shows a clear upward shift / acceleration beginning around the late 1990s into early 2000s, continuing upward thereafter.
  • Pr(tcp): dominant peak is around ~2000–2002, meaning BEAST sees the most plausible structural change in the long-term evolution there (often interpreted as a regime shift / onset of sustained positive anomalies in this metric).
  • orderT: rises into the late 1990s/early 2000s and then stabilizes higher, consistent with the trend needing more structure once the anomaly regime begins (i.e., not just noise around ~0).
  • slpSign: Pre-~2000 closer to “weak/uncertain or mixed,” then post-~2000 it shifts strongly toward persistent positive slope (sustained warming anomalies) for the remainder of the record.
Code
GBR_OISST2_SSTA <- rast("/Users/rof011/GBR-dhw/datasets/GBR_OISST2_SSTA.tif")

ssta_ts <- terra::global(GBR_OISST2_SSTA, mean, na.rm = TRUE) %>%
  mutate(date = as.Date(rownames(.))) |>
  mutate(
    year  = year(date),
    month = month(date),
    austral_year = if_else(month == 12L, year + 1L, year)  # Dec belongs to next year
  ) %>%
  filter(month %in% c(12, 1, 2, 3)) %>%
  filter(austral_year > 1940, austral_year < 2026) %>%
  group_by(austral_year) %>%
  summarise(ssta = mean(mean, na.rm = TRUE), .groups = "drop") %>%
  rename(year = austral_year)


ssta_ts <- ssta_ts %>% arrange(year)
y_ssta <- ssta_ts$ssta
t_ssta <- ssta_ts$year

beast_ssta <- beast(
  y_ssta,
  start  = t_ssta[1],
  deltat = 1,
  season = "none",
  print.progress = FALSE,
  quiet = TRUE
)

cp_ssta   <- beast_ssta$trend$cp
cppr_ssta <- beast_ssta$trend$cpPr
if (is.null(cppr_ssta)) cppr_ssta <- beast_ssta$trend$cpProb

ssta_cp_tbl <- tibble(cp = cp_ssta, cp_prob = cppr_ssta) %>%
  mutate(year = as.integer(round(cp))) %>%
  arrange(desc(cp_prob))

breakpoints_ssta <- ssta_cp_tbl$cp[1:min(2, nrow(ssta_cp_tbl))]
breakyears_ssta  <- ssta_cp_tbl$year[1:min(2, nrow(ssta_cp_tbl))]

plot(beast_ssta)

Degree Heating Weeks

GBR-wide peak heat stress (DHW) austral-year annual time series (°C-weeks):

  • Calculate spatial mean (GBR-area mean DHW for each day)
  • Aggregate for each year the maximum of the daily GBR-area mean DHW values

Output:

  • Top (trend): low/near-zero through mid-century, then rising with clear step-like increases around the late 1990s–early 2000s and another strong increase around ~2020, indicating higher typical peak heat-stress years in the recent period.
  • Pr(tcp): prominent peaks occur around ~1998–2002 and a very strong peak around ~2020, meaning BEAST sees major structural changes in the long-term evolution of peak heat stress at those times (often interpreted as shifts to higher heat-stress regimes and/or increased frequency of extreme DHW years).
  • orderT: fairly stable for most of the record, with a change around the most recent breakpoint, consistent with the trend structure being dominated by a few step/acceleration periods rather than many small bends.
  • slpSign: distribution is dominated by positive-slope support across most of the record, with localized dips around breakpoint windows, consistent with an overall increasing trajectory in peak DHW despite substantial year-to-year variability.
  • error: larger excursions in recent decades reflecting more extreme departures in peak DHW years.
Code
GBR_OISST2_DHW <- rast("/Users/rof011/GBR-dhw/datasets/GBR_OISST2_DHW.tif")

sst_dhw <- terra::global(GBR_OISST2_DHW, mean, na.rm = TRUE) %>%
  mutate(date = as.Date(rownames(.))) |>
  mutate(
    year  = year(date),
    month = month(date),
    austral_year = if_else(month == 12L, year + 1L, year)  # Dec belongs to next year
  ) %>%
  #filter(month %in% c(12, 1, 2, 3)) %>%
  filter(austral_year > 1940, austral_year < 2026) %>%
  group_by(austral_year) %>%
  summarise(dhw = max(mean, na.rm = TRUE), .groups = "drop") %>%
  rename(year = austral_year)
 

sst_dhw <- sst_dhw %>% arrange(year)
y_dhw <- sst_dhw$dhw
t_dhw <- sst_dhw$year

beast_dhw <- beast(
  y_dhw,
  start  = t_dhw[1],
  deltat = 1,
  season = "none",
  print.progress = FALSE,
  quiet = TRUE
)

cp_dhw   <- beast_dhw$trend$cp
cppr_dhw <- beast_dhw$trend$cpPr
if (is.null(cppr_dhw)) cppr_dhw <- beast_dhw$trend$cpProb

sst_cp_tbl <- tibble(cp = cp_dhw, cp_prob = cppr_dhw) %>%
  mutate(year = as.integer(round(cp))) %>%
  arrange(desc(cp_prob))

breakpoints_dhw <- sst_cp_tbl$cp[1:min(2, nrow(sst_cp_tbl))]
breakyears_dhw  <- sst_cp_tbl$year[1:min(2, nrow(sst_cp_tbl))]

plot(beast_dhw)