## ----setup, include=FALSE-----------------------------------------------------
knitr::opts_chunk$set(
  collapse  = TRUE,
  comment   = "#>",
  fig.align = "center",
  warning   = FALSE,
  message   = FALSE
)
set.seed(2024)
library(climatestatsr)

## ----mk_basic-----------------------------------------------------------------
# Simulate 50 years of warming at 0.025 °C/year
years <- 1971:2020
temp  <- 14.0 + 0.025 * seq_len(50) + stats::rnorm(50, 0, 0.4)

result <- mk_test(temp)
print(result)

## ----mk_plot, fig.cap="Mann-Kendall result: time series with Sen slope (left) and value distribution (right)."----
plot(result)

## ----mk_prewhiten-------------------------------------------------------------
# Apply AR(1) pre-whitening for autocorrelated series
ar_temp <- as.numeric(stats::arima.sim(list(ar = 0.65), n = 60)) +
           seq(0, 3, length.out = 60) + 14
mk_pw <- mk_test(ar_temp, prewhiten = TRUE)
cat("Pre-whitened MK: Z =", round(mk_pw$statistic, 3),
    "  p =", round(mk_pw$p.value, 4), "\n")

## ----sens---------------------------------------------------------------------
ss <- sens_slope(years, temp)
cat(sprintf(
  "Sen's slope : %+.4f °C/year\n",  ss$slope))
cat(sprintf(
  "Rate/decade : %+.3f °C\n",       ss$slope_decade))
cat(sprintf(
  "95%% CI      : [%.4f, %.4f]\n",  ss$slope_ci["lower"],
                                    ss$slope_ci["upper"]))

## ----sens_plot, fig.cap="Observed temperature with Sen slope (red dashed) and 95% CI band."----
plot(years, temp, pch = 16, cex = 0.7, col = "steelblue",
     xlab = "Year", ylab = "Temperature (°C)",
     main = "Annual Mean Temperature with Sen's Slope")
abline(a = ss$intercept, b = ss$slope,
       col = "firebrick", lwd = 2, lty = 2)
legend("topleft", legend = c("Observed", "Sen slope"),
       col = c("steelblue", "firebrick"),
       pch = c(16, NA), lty = c(NA, 2), lwd = c(NA, 2), bty = "n")

## ----cp-----------------------------------------------------------------------
# Simulate a step change at observation 30
x <- c(stats::rnorm(30, mean = 14.0, sd = 0.5),
        stats::rnorm(30, mean = 15.5, sd = 0.5))

cp <- change_point_detection(x, method = "pettitt")
cat(sprintf("Change point at index : %d\n", cp$change_point))
cat(sprintf("Mean before shift     : %.2f °C\n", cp$mean_before))
cat(sprintf("Mean after shift      : %.2f °C\n", cp$mean_after))
cat(sprintf("Significant (α=0.05)  : %s\n", cp$significant))

## ----cp_plot, fig.cap="CUSUM series — the peak identifies the most probable break point."----
cp_cusum <- change_point_detection(x, method = "cusum")
plot(cp_cusum$cusum, type = "l", col = "steelblue", lwd = 2,
     xlab = "Index", ylab = "CUSUM",
     main = "CUSUM Change-Point Detection")
abline(v   = cp_cusum$change_point, col = "firebrick",
       lty = 2, lwd = 2)
abline(h   = 0, lty = 3, col = "gray50")
legend("topleft",
       legend = c("CUSUM", sprintf("Break at %d", cp_cusum$change_point)),
       col    = c("steelblue", "firebrick"),
       lty    = c(1, 2), lwd = 2, bty = "n")

## ----decomp_data--------------------------------------------------------------
n     <- 360   # 30 years of monthly data
t_idx <- seq_len(n)
temp_m <- 15 + 0.003 * t_idx +
          8   * sin(2 * pi * t_idx / 12) +
          stats::rnorm(n, 0, 0.5)

## ----decomp_plot, fig.height=6, fig.cap="STL decomposition: original, trend, seasonal component, and remainder."----
dc <- seasonal_decompose_climate(temp_m, frequency = 12, method = "stl")
plot(dc)

## ----decomp_trend-------------------------------------------------------------
trend_mk <- mk_test(dc$trend[!is.na(dc$trend)])
cat("Trend component MK test: tau =", round(trend_mk$tau, 3),
    " p =", round(trend_mk$p.value, 4), "\n")

## ----rolling, fig.cap="20-year rolling Sen slope: warming accelerated after index ~40."----
temp2  <- 13.5 + c(0.01 * seq_len(50), 0.04 * seq_len(41)) +
          stats::rnorm(91, 0, 0.4)
rt <- rolling_trend(temp2, window = 20, step = 2)

plot(rt$mid_index, rt$slope_decade, type = "l",
     col = "steelblue", lwd = 2,
     xlab = "Mid-window index",
     ylab = "Trend (°C per decade)",
     main = "Rolling 20-Year Sen Slope")
abline(h = 0, lty = 2, col = "gray50")
polygon(c(rt$mid_index, rev(rt$mid_index)),
        c(rt$slope_decade * 1.15, rev(rt$slope_decade * 0.85)),
        col = adjustcolor("steelblue", 0.15), border = NA)

## ----snht---------------------------------------------------------------------
# Inhomogeneous series: +0.8 °C offset after observation 40
x_inh <- c(stats::rnorm(40, 0, 1), stats::rnorm(40, 0.8, 1))
snht  <- temporal_homogeneity(x_inh)
cat(sprintf("T0 statistic  : %.2f  (critical ≈ %.2f)\n",
            snht$T0, snht$critical))
cat(sprintf("Break at index: %d\n", snht$break_index))
cat(sprintf("Significant   : %s\n", snht$significant))

## ----snht_plot, fig.cap="SNHT statistic series — peak marks the inhomogeneity break."----
plot(snht$T_series, type = "l", col = "steelblue", lwd = 1.5,
     xlab = "Index", ylab = "T statistic",
     main = "SNHT — Homogeneity Test")
abline(v = snht$break_index, col = "firebrick", lty = 2, lwd = 2)
abline(h = snht$critical,    col = "orange",    lty = 3, lwd = 1.5)
legend("topright",
       legend = c("T statistic", "Break point", "Critical value"),
       col    = c("steelblue", "firebrick", "orange"),
       lty    = c(1, 2, 3), lwd = 2, bty = "n")

## ----trendsig-----------------------------------------------------------------
mat <- matrix(stats::rnorm(50 * 30), nrow = 50)
# impose trend in 10 stations
mat[, 1:10] <- mat[, 1:10] + outer(seq_len(50), rep(0.05, 10))

ts_res <- trend_significance(mat, correction = "fdr")
cat("Stations with significant trend:\n")
print(table(ts_res$trend))

## ----acf, fig.cap="ACF and PACF of an AR(1) climate series (ρ ≈ 0.7)."--------
ar_series <- as.numeric(stats::arima.sim(list(ar = 0.7), n = 100)) + 15
ac <- autocorrelation_climate(ar_series, max.lag = 20, plot = TRUE)
cat("AR(1) sample coefficient:", round(ac$ar1, 3), "\n")
cat("Ljung-Box p-value       :", round(ac$ljung_box$p.value, 4), "\n")

## ----moran--------------------------------------------------------------------
set.seed(42)
n      <- 50
coords <- data.frame(lon = stats::runif(n, -10, 10),
                     lat = stats::runif(n, 40, 60))
# Temperature decreases with latitude → positive autocorrelation
x_sp <- 26 - 0.35 * coords$lat + stats::rnorm(n, 0, 0.8)

mi <- morans_i(x_sp, coords, n_perm = 499)
cat(sprintf("Moran's I = %.4f\n", mi$I))
cat(sprintf("Z-score   = %.3f  (p = %.4f)\n", mi$Z, mi$p.value))
cat(mi$interpretation, "\n")

## ----moran_plot, fig.cap="Scatter plot of spatial values coloured by latitude gradient."----
col_ramp <- colorRampPalette(c("steelblue", "white", "firebrick"))(50)
val_rank <- rank(x_sp)
plot(coords$lon, coords$lat,
     pch = 21, cex = 1.5,
     bg  = col_ramp[val_rank],
     xlab = "Longitude", ylab = "Latitude",
     main = sprintf("Spatial Field  (Moran's I = %.3f)", mi$I))

## ----hotspot------------------------------------------------------------------
set.seed(5)
vals <- ifelse(coords$lon > 4,
               stats::rnorm(n, 30, 2),
               stats::rnorm(n, 18, 2))
hs <- hot_cold_spots(vals, coords, dist_threshold = 5)
print(table(hs$classification))

## ----hotspot_plot, fig.cap="Getis-Ord Gi* classification: hot spots (east), cold spots (west)."----
cols <- c("hot spot"       = "firebrick",
          "cold spot"      = "steelblue",
          "not significant"= "gray80")
plot(hs$lon, hs$lat,
     pch = 21, cex = 1.6,
     bg  = cols[hs$classification],
     xlab = "Longitude", ylab = "Latitude",
     main = "Getis-Ord Gi* Hot/Cold Spots")
legend("topright", legend = names(cols), pt.bg = cols,
       pch = 21, pt.cex = 1.4, bty = "n")

## ----interp, fig.cap="IDW interpolation from 25 stations to a 20×20 grid."----
set.seed(7)
obs  <- matrix(stats::runif(50), ncol = 2,
               dimnames = list(NULL, c("lon","lat")))
vals_obs <- sin(obs[,"lon"] * 3) + cos(obs[,"lat"] * 3)

grd  <- as.matrix(expand.grid(
  lon = seq(0.05, 0.95, length.out = 20),
  lat = seq(0.05, 0.95, length.out = 20)))
pred <- spatial_interpolate(obs, vals_obs, grd, method = "idw")

image(matrix(pred, 20, 20),
      col  = colorRampPalette(c("steelblue","white","firebrick"))(64),
      main = "IDW Interpolated Climate Field",
      xlab = "Longitude", ylab = "Latitude")
points((obs[,"lon"] - 0.05) / 0.9,
       (obs[,"lat"] - 0.05) / 0.9, pch = 3, cex = 0.8)

## ----stf----------------------------------------------------------------------
set.seed(9)
mat_st <- matrix(stats::rnorm(50 * 200), nrow = 50)
mat_st[, 101:200] <- mat_st[, 101:200] +
                     outer(seq_len(50), rep(0.04, 100))

stf <- spatial_trend_field(mat_st)
cat(sprintf("Cells with significant trend: %d / %d (%.0f%%)\n",
            sum(stf$p.value < 0.05, na.rm = TRUE),
            nrow(stf),
            100 * mean(stf$p.value < 0.05, na.rm = TRUE)))

## ----stf_plot, fig.cap="Fraction of significant trend cells by group."--------
stf$group <- ifelse(seq_len(nrow(stf)) <= 100, "No trend", "Trend imposed")
sig_frac  <- tapply(stf$p.value < 0.05, stf$group, mean, na.rm = TRUE)
barplot(sig_frac * 100,
        col = c("gray70", "firebrick"),
        ylab = "% Significant (α=0.05)",
        main = "Spatial Trend Field Results",
        border = NA)

## ----cluster------------------------------------------------------------------
set.seed(1)
clim_mat <- matrix(
  c(stats::rnorm(200, rep(c(5, 15, 25, 10, 20), each = 40), 2),
    stats::rnorm(200, rep(c(1200, 600, 200, 900, 400), each = 40), 80)),
  ncol = 2,
  dimnames = list(NULL, c("temp", "precip")))

cz <- cluster_climate_zones(clim_mat, k = 5)
cat("Cluster sizes:\n")
print(table(cz$cluster))

## ----cluster_plot, fig.cap="Five K-means climate zones in temperature–precipitation space."----
pal <- c("#e41a1c","#377eb8","#4daf4a","#984ea3","#ff7f00")
plot(clim_mat[, "temp"], clim_mat[, "precip"],
     pch = 21, cex = 0.9,
     bg  = pal[cz$cluster],
     xlab = "Mean Temperature (°C)",
     ylab = "Annual Precipitation (mm)",
     main = "K-Means Climate Zone Classification")
legend("topright", legend = paste("Zone", 1:5),
       pt.bg = pal, pch = 21, pt.cex = 1.2, bty = "n")

## ----lapse, fig.cap="Temperature vs elevation with fitted lapse rate."--------
elev <- seq(100, 3000, by = 100)
temp_lapse <- 25 - 0.0065 * elev + stats::rnorm(30, 0, 0.4)
lr <- elevation_lapse_rate(temp_lapse, elev)

plot(elev, temp_lapse, pch = 16, cex = 0.8, col = "steelblue",
     xlab = "Elevation (m a.s.l.)",
     ylab = "Temperature (°C)",
     main = sprintf("Environmental Lapse Rate: %.2f °C / 1000 m",
                    lr$lapse_rate_per_1000m))
lines(lr$data$elevation, lr$data$fitted, col = "firebrick", lwd = 2)
legend("topright",
       legend = c("Station data",
                  sprintf("Lapse rate = %.3f °C/1000 m  (R² = %.3f)",
                          lr$lapse_rate_per_1000m, lr$r_squared)),
       col    = c("steelblue", "firebrick"),
       pch    = c(16, NA), lty = c(NA, 1), lwd = c(NA, 2), bty = "n")

## ----gev----------------------------------------------------------------------
set.seed(10)
ann_max <- rgev_sim(60, mu = 34, sigma = 4.5, xi = 0.12)

gev <- fit_gev(ann_max)
print(gev)

## ----rl-----------------------------------------------------------------------
rp <- return_period(gev, c(2, 5, 10, 20, 50, 100, 200))
print(rp)

## ----rl_plot, fig.cap="GEV return level curve with 95% delta-method confidence band."----
with(rp, {
  plot(T, level, type = "b", log = "x", pch = 16,
       col  = "steelblue",
       xlab = "Return period (years)",
       ylab = "Temperature (°C)",
       main = "GEV Return Level Curve",
       ylim = range(c(lower, upper), na.rm = TRUE))
  polygon(c(T, rev(T)), c(upper, rev(lower)),
          col = adjustcolor("steelblue", 0.18), border = NA)
  lines(T, lower, lty = 2, col = "steelblue")
  lines(T, upper, lty = 2, col = "steelblue")
  abline(v = c(10, 100), lty = 3, col = "gray60")
})

## ----pot----------------------------------------------------------------------
set.seed(11)
daily_p <- stats::rexp(365 * 30, rate = 1 / 6)
pot <- peaks_over_threshold(daily_p, threshold = 22,
                             n_years = 30,
                             return_periods = c(10, 50, 100, 200))
cat(sprintf("Threshold     : %.0f mm\n",  pot$threshold))
cat(sprintf("Peaks retained: %d\n",        pot$n_excess))
cat(sprintf("GPD shape (ξ) : %.4f\n",      pot$xi))
cat(sprintf("GPD scale (σ) : %.4f\n",      pot$sigma))
cat("\nReturn levels:\n")
print(pot$return_levels)

## ----heatwave-----------------------------------------------------------------
dates <- seq(as.Date("2000-01-01"), by = "day",
             length.out = 365 * 15)
doy   <- as.integer(format(dates, "%j"))
tmax  <- 28 + 10 * sin(2 * pi * doy / 365) +
         stats::rnorm(length(dates), 0, 2.5)

hw <- heat_wave_detection(tmax, dates,
                           threshold = "p95", min_days = 3)
cat(sprintf("Heat wave events over 15 years : %d\n", nrow(hw)))
cat(sprintf("Mean duration                  : %.1f days\n",
            mean(hw$duration)))
cat(sprintf("Maximum peak temperature       : %.1f °C\n",
            max(hw$peak_temp)))

## ----heatwave_plot, fig.cap="Annual heat wave count and mean duration over 15 years."----
hw$year <- as.integer(format(hw$start_date, "%Y"))
ann_hw  <- tapply(hw$duration, hw$year, length)

barplot(ann_hw,
        col  = "firebrick",
        border = NA,
        xlab = "Year", ylab = "Number of events",
        main = "Annual Heat Wave Frequency (p95 threshold, ≥3 days)")

## ----coldspell----------------------------------------------------------------
cs <- cold_spell_detection(
  tmin  = tmax - stats::rnorm(length(tmax), 12, 1),
  dates = dates,
  threshold = "p05",
  min_days  = 3)
cat(sprintf("Cold spell events: %d\n", nrow(cs)))
if (nrow(cs) > 0) {
  cat(sprintf("Mean duration    : %.1f days\n", mean(cs$duration)))
}

## ----drought------------------------------------------------------------------
set.seed(12)
spi_vals <- stats::rnorm(360)
dates_m  <- seq(as.Date("1990-01-01"), by = "month",
                length.out = 360)
droughts <- drought_spell(spi_vals, dates_m, threshold = -1.0,
                          min_duration = 2)
cat(sprintf("Drought spells detected : %d\n", nrow(droughts)))
cat(sprintf("Mean duration           : %.1f months\n",
            mean(droughts$duration)))
cat(sprintf("Maximum severity        : %.2f\n",
            max(droughts$severity)))

## ----hill, fig.cap="Hill stability plot — plateau indicates a good choice of k."----
set.seed(13)
ws <- abs(stats::rnorm(500, 10, 4))^1.5
ev <- extreme_value_index(ws, k = 30)
cat(sprintf("Hill tail index (k=%d): %.4f\n", ev$k_used, ev$hill_index))

plot(ev$hill_plot$k, ev$hill_plot$hill, type = "l",
     col = "steelblue", lwd = 1.5,
     xlab = "k (number of order statistics)",
     ylab = "Hill estimate",
     main = "Hill Stability Plot")
abline(v = ev$k_used, col = "firebrick", lty = 2)

## ----spi_data-----------------------------------------------------------------
precip <- stats::rgamma(360, shape = 2, scale = 35)  # 30 years
spi3  <- spi(precip, scale = 3)
spi12 <- spi(precip, scale = 12)

cat(sprintf("SPI-3  — mean: %.3f  SD: %.3f\n",
            mean(spi3,  na.rm = TRUE), stats::sd(spi3,  na.rm = TRUE)))
cat(sprintf("SPI-12 — mean: %.3f  SD: %.3f\n",
            mean(spi12, na.rm = TRUE), stats::sd(spi12, na.rm = TRUE)))

## ----spi_plot, fig.height=5, fig.cap="SPI-3 (top) and SPI-12 (bottom) drought indices over 30 years."----
old_par <- graphics::par(mfrow = c(2, 1), mar = c(3, 4, 2, 1))

col3 <- ifelse(!is.na(spi3) & spi3 >= 0, "steelblue", "firebrick")
graphics::plot(spi3, type = "h", col = col3,
               xlab = "", ylab = "SPI-3",
               main = "Standardised Precipitation Index")
graphics::abline(h = c(-1, 1), lty = 2, col = "gray40")

col12 <- ifelse(!is.na(spi12) & spi12 >= 0, "steelblue", "firebrick")
graphics::plot(spi12, type = "h", col = col12,
               xlab = "Month", ylab = "SPI-12")
graphics::abline(h = c(-1, 1), lty = 2, col = "gray40")

graphics::par(old_par)

## ----spei_calc----------------------------------------------------------------
set.seed(14)
tmin_m <- abs(stats::rnorm(360, 8, 3)) + 2
tmax_m <- tmin_m + stats::runif(360, 7, 14)
pr_m   <- stats::rgamma(360, 5, 0.06)

sp6 <- spei(precip = pr_m, tmin = tmin_m, tmax = tmax_m,
            lat = 45, scale = 6)
cat(sprintf("SPEI-6 — mean: %.3f  SD: %.3f\n",
            mean(sp6, na.rm = TRUE), stats::sd(sp6, na.rm = TRUE)))

## ----spei_plot, fig.cap="SPEI-6 — the index accounts for evapotranspiration demand."----
col_sp <- ifelse(!is.na(sp6) & sp6 >= 0, "steelblue", "firebrick")
graphics::plot(sp6, type = "h", col = col_sp,
               xlab = "Month", ylab = "SPEI-6",
               main = "SPEI-6  (Hargreaves PET, lat = 45°)")
graphics::abline(h = c(-1, 1), lty = 2, col = "gray40")

## ----pdsi, fig.cap="Simplified PDSI — values below −2 indicate severe drought."----
doy_m  <- rep(1:12, 30)
temp_p <- 10 + 12 * sin(pi * doy_m / 6) + stats::rnorm(360, 0, 1)
pr_p   <- pmax(0, 50 + 20 * cos(pi * doy_m / 6) +
               stats::rnorm(360, 0, 15))

pdsi_vals <- pdsi_simple(temp_p, pr_p, lat = 40)
graphics::plot(pdsi_vals, type = "l", col = "steelblue",
               xlab = "Month", ylab = "PDSI",
               main = "Simplified Palmer Drought Severity Index")
graphics::abline(h = c(-2, 2), lty = 2, col = c("firebrick","steelblue"))
graphics::abline(h = 0, lty = 3, col = "gray50")

## ----hi, fig.cap="Heat index surface: apparent temperature rises steeply with humidity."----
temp_grid <- seq(25, 45, by = 2)
rh_grid   <- seq(30, 100, by = 5)
hi_mat    <- outer(temp_grid, rh_grid,
                    FUN = function(t, r) heat_index(t, r))

image(temp_grid, rh_grid, hi_mat,
      col  = colorRampPalette(c("lightyellow","orange","firebrick"))(64),
      xlab = "Air temperature (°C)",
      ylab = "Relative humidity (%)",
      main = "Heat Index (°C)")
contour(temp_grid, rh_grid, hi_mat,
        levels = c(27, 32, 41, 54), add = TRUE, col = "white")

## ----wc, fig.cap="Wind chill surface: perceived temperature can be far below air temperature."----
temp_wc <- seq(-30, 5, by = 2)
wind_wc <- seq(5, 80, by = 5)
wc_mat  <- outer(temp_wc, wind_wc, FUN = wind_chill)

image(temp_wc, wind_wc, wc_mat,
      col  = colorRampPalette(c("navy","steelblue","white"))(64),
      xlab = "Air temperature (°C)",
      ylab = "Wind speed (km/h)",
      main = "Wind Chill Temperature (°C)")
contour(temp_wc, wind_wc, wc_mat,
        levels = c(-40, -30, -20, -10), add = TRUE, col = "gray20")

## ----frost, fig.cap="Annual frost day count simulated over 10 years."---------
dates_d <- seq(as.Date("2010-01-01"), by = "day",
               length.out = 365 * 10)
doy_d   <- as.integer(format(dates_d, "%j"))
tmin_d  <- 5 - 15 * sin(2 * pi * doy_d / 365) +
            stats::rnorm(length(dates_d), 0, 2)

fd <- frost_days(tmin_d, dates_d, by = "year")
barplot(fd, col = "steelblue", border = NA,
        xlab = "Year", ylab = "Frost days",
        main = "Annual Frost Day Count (Tmin < 0 °C)")

## ----gdd, fig.cap="Cumulative GDD accumulation through the growing season."----
dates_g <- seq(as.Date("2020-01-01"),
               as.Date("2020-12-31"), by = "day")
doy_g   <- as.integer(format(dates_g, "%j"))
tmax_g  <- 22 + 12 * sin(2 * pi * doy_g / 365)
tmin_g  <- 10 +  8 * sin(2 * pi * doy_g / 365)

gdd_cum <- growing_degree_days(tmax_g, tmin_g,
                                base_temp  = 10,
                                cumulative = TRUE)
plot(gdd_cum, type = "l", col = "darkgreen", lwd = 2,
     xlab = "Day of year",
     ylab = "Cumulative GDD (base 10 °C)",
     main = "Growing Degree Days 2020")
abline(v = 91,  lty = 2, col = "gray60")   # ~April 1
abline(v = 274, lty = 2, col = "gray60")   # ~Oct 1
text(91,  max(gdd_cum) * 0.05, "Apr", col = "gray40", adj = 0)
text(274, max(gdd_cum) * 0.05, "Oct", col = "gray40", adj = 1)

## ----dtr, fig.cap="Monthly mean DTR — a proxy for cloudiness and land-surface change."----
dtr <- diurnal_temp_range(tmax_g, tmin_g, dates_g, by = "month")
barplot(dtr, col = "steelblue", border = NA,
        names.arg = month.abb,
        xlab = "Month", ylab = "DTR (°C)",
        main = "Mean Diurnal Temperature Range by Month")

## ----detect-------------------------------------------------------------------
set.seed(20)
obs_anom  <- cumsum(stats::rnorm(50, 0.03, 0.12))
nat_ens   <- matrix(stats::rnorm(50 * 30, 0, 0.45), ncol = 30)
forc_sig  <- seq(0, 1.5, length.out = 50)

da <- detection_attribution(obs_anom, nat_ens, forc_sig)
cat(sprintf("Signal detected     : %s\n", da$detected))
cat(sprintf("Z-score             : %.3f\n", da$z_score))
cat(sprintf("p-value             : %.4f\n", da$p.value))
cat(sprintf("Attribution fraction: %.1f%%\n",
            da$attribution_fraction * 100))

## ----detect_plot, fig.cap="Observed projection vs natural ensemble distribution — signal clearly separated."----
hist(da$projection_natural,
     col    = "steelblue",
     border = "white",
     breaks = 15,
     xlab   = "Projection onto forced signal (Pearson r)",
     main   = "Detection Test: Observed vs Natural Variability")
abline(v   = da$projection_observed,
       col = "firebrick", lwd = 3)
legend("topright",
       legend = c("Natural ensemble",
                  sprintf("Observed (r = %.3f)", da$projection_observed)),
       fill   = c("steelblue", NA),
       border = c("white", NA),
       lty    = c(NA, 1),
       col    = c(NA, "firebrick"),
       lwd    = c(NA, 3), bty = "n")

## ----eof----------------------------------------------------------------------
set.seed(21)
mat_eof <- matrix(stats::rnorm(60 * 200), nrow = 60)
# Inject a dominant warming pattern in first 80 locations
mat_eof[, 1:80] <- mat_eof[, 1:80] +
                   outer(seq(0, 2, length.out = 60), rep(1, 80))

fp <- fingerprint_analysis(mat_eof, n_eof = 3)
cat(sprintf("EOF1 explains: %.1f%% of variance\n",
            fp$var_explained[1] * 100))
cat(sprintf("EOF2 explains: %.1f%% of variance\n",
            fp$var_explained[2] * 100))
cat(sprintf("Cumulative (3 EOFs): %.1f%%\n",
            fp$cumvar[3] * 100))

## ----eof_plot, fig.cap="Leading PC time series — EOF1 captures the forced warming trend."----
matplot(fp$pc[, 1:3], type = "l",
        lty = 1, lwd = 1.5,
        col = c("firebrick", "steelblue", "darkgreen"),
        xlab = "Time step", ylab = "PC score",
        main = "Leading EOF Principal Components")
legend("topleft",
       legend = sprintf("PC%d (%.1f%%)", 1:3,
                        fp$var_explained * 100),
       col    = c("firebrick", "steelblue", "darkgreen"),
       lty    = 1, lwd = 1.5, bty = "n")

## ----ofp----------------------------------------------------------------------
set.seed(22)
obs_c <- cumsum(stats::rnorm(50, 0.03, 0.10))
all_c <- cumsum(stats::rnorm(50, 0.028, 0.05)) +
         cumsum(stats::rnorm(50, 0.005, 0.03))
nat_c <- cumsum(stats::rnorm(50, 0, 0.12))

ofp <- optimal_fingerprint(obs_c, all_c, nat_c)
cat(sprintf("ANT scaling factor: %.3f\n", ofp$beta_all))
cat(sprintf("NAT scaling factor: %.3f\n", ofp$beta_nat))
cat(sprintf("Residual variance : %.4f\n", ofp$residual_variance))

## ----gaps---------------------------------------------------------------------
x_gaps <- c(10.2, NA, NA, NA, 14.0, 15.1, NA, 17.3, 18.0)
cat("Original  :", x_gaps, "\n")
cat("Filled    :", round(fill_gaps_climate(x_gaps), 2), "\n")

## ----homog--------------------------------------------------------------------
set.seed(25)
x_inh <- c(stats::rnorm(40, 14.0, 0.5),
             stats::rnorm(40, 15.8, 0.5))
x_hom <- homogenize_series(x_inh)
cat(sprintf("Before adjustment — mean of segment 1: %.2f\n",
            mean(x_inh[1:40])))
cat(sprintf("After  adjustment — mean of segment 1: %.2f\n",
            mean(x_hom[1:40])))
cat(sprintf("Mean of segment 2 (reference)       : %.2f\n",
            mean(x_hom[41:80])))

## ----agg----------------------------------------------------------------------
dates_agg <- seq(as.Date("2000-01-01"), by = "day",
                 length.out = 365 * 5)
temp_agg  <- stats::rnorm(length(dates_agg), 15, 6)

ann  <- aggregate_climate(temp_agg, dates_agg, to = "annual")
seas <- aggregate_climate(temp_agg, dates_agg, to = "seasonal")
cat("Annual means:\n"); print(ann)

## ----anom, fig.cap="Temperature anomalies relative to 1961–1990 baseline."----
yr   <- 1950:2020
temp_b <- 13.5 + 0.022 * seq_len(71) + stats::rnorm(71, 0, 0.45)
anom <- anomaly_baseline(temp_b, yr, 1961, 1990)

plot(yr, anom, type = "l", col = "steelblue", lwd = 1.5,
     xlab = "Year", ylab = "Anomaly (°C)",
     main = "Temperature Anomalies  (1961–1990 baseline)")
abline(h = 0, lty = 2, col = "gray50")
lines(stats::lowess(yr, anom, f = 0.3),
      col = "firebrick", lwd = 2.5)
legend("topleft",
       legend = c("Annual anomaly", "LOWESS smoother"),
       col    = c("steelblue", "firebrick"),
       lty    = 1, lwd = c(1.5, 2.5), bty = "n")

## ----std----------------------------------------------------------------------
x_raw <- stats::rnorm(120, mean = 18, sd = 5)
z     <- standardize_climate(x_raw)
cat(sprintf("Raw  — mean: %.2f  SD: %.2f\n",
            mean(x_raw), stats::sd(x_raw)))
cat(sprintf("Std  — mean: %.6f  SD: %.6f\n",
            mean(z), stats::sd(z)))

## ----csummary-----------------------------------------------------------------
temp_long <- 13.5 + 0.022 * seq_len(71) + stats::rnorm(71, 0, 0.45)
res <- climate_summary(temp_long,
                        variable_name = "Annual Mean Temperature (°C)")

## ----session------------------------------------------------------------------
sessionInfo()

