Propósito

Calcular el SPEI-6 de 2021-01 a 2026-07 respecto a una línea base fija 2001–2020, y guardar los coeficientes ajustados en la línea base para poder agregar meses nuevos más adelante sin volver a ajustar. Esta nota prueba el procedimiento en un píxel y verifica que reproduce exactamente el SPEI calculado con la serie completa.

# Cargar librerías --------------------------------------------------------
require(pacman)
pacman::p_load(terra, tidyverse, SPEI, geodata, knitr)
options(scipen = 999)

# Funciones ---------------------------------------------------------------

## Agrega los puntos donde la línea cruza el cero, para que el color cambie justo en 0
add_zero <- function(tbl){
  tbl  <- tbl |> filter(!is.na(value)) |> arrange(date)
  zero <- tbl |>
    mutate(nxtv = lead(value), nxtd = lead(date)) |>
    filter(value * nxtv < 0) |>
    transmute(date = date + (nxtd - date) * value / (value - nxtv),
              value = 0,
              sign = if_else(nxtv > 0, 'Positive', 'Negative'))
  tbl |>
    mutate(sign = if_else(value >= 0, 'Positive', 'Negative')) |>
    select(date, value, sign) |>
    bind_rows(zero) |>
    arrange(date)
}

## Línea coloreada por signo: azul sobre 0, marrón bajo 0 (bg: capas de fondo opcionales)
plot_sign <- function(tbl, ylab, bg = NULL){
  ggplot(data = add_zero(tbl), aes(x = date, y = value)) +
    bg +
    geom_hline(yintercept = 0, linetype = 'dashed', color = 'grey50') +
    geom_line(aes(color = sign, group = 1)) +
    scale_color_manual(values = c(Positive = '#4393C3', Negative = '#A6611A'), guide = 'none') +
    scale_x_date(breaks = seq(as.Date('2001-01-01'), as.Date('2026-07-01'), by = '2 years'), date_labels = '%Y') +
    labs(x = 'Fecha', y = ylab) +
    theme_bw() +
    theme(axis.text.y = element_text(angle = 90, hjust = 0.5))
}

Datos

# Cargar datos ------------------------------------------------------------
rstr <- terra::rast('./tif/spei/climate_bsl_baln_v2.tif')
col0 <- geodata::gadm(country = 'COL', level = 0, path = './tmpr')

# Colombia ----------------------------------------------------------------
rstr <- terra::crop(rstr, col0)
rstr <- terra::mask(rstr, col0)

# Píxel de prueba (gid = 1), antes de pasar a formato largo ---------------
dfrm <- rstr %>%
  terra::as.data.frame(xy = T) %>%
  as_tibble() %>%
  mutate(gid = 1:nrow(.)) |>
  filter(gid == 1) |>
  gather(var, value, -c(gid, x, y)) |>
  mutate(date = seq(as.Date('2001-01-01'), as.Date('2026-07-01'), by = 'month')) |>
  dplyr::select(x, y, date, value)

Balance hídrico climático mensual (precipitación menos evapotranspiración potencial, mm) de climate_bsl_baln_v2.tif, de 2001-01 a 2026-07, recortado a Colombia. Píxel de prueba: la primera celda válida del raster (x = -71.675, y = 12.475).

plot_sign(tbl = dfrm, ylab = 'Balance (mm)')
Figura 1. Balance hídrico climático mensual del píxel de prueba. Azul: excedente; marrón: déficit.

Figura 1. Balance hídrico climático mensual del píxel de prueba. Azul: excedente; marrón: déficit.

Método

El SPEI-6 se calcula con el paquete SPEI en dos pasos:

  1. Línea base (2001–2020). El balance se acumula a 6 meses y, para cada mes calendario, se ajusta una distribución log-logística a sus 20 valores de la línea base. Los coeficientes se guardan.
  2. Periodo nuevo (2021-01 a 2026-07). Cada valor acumulado se estandariza con los coeficientes guardados de su mes, sin volver a ajustar. La serie de entrada empieza en 2020-08 solo para que los primeros meses de 2021 tengan completo su acumulado de 6 meses.

Como la distribución no se vuelve a ajustar, los meses nuevos siempre se comparan con el mismo clima de referencia, y los valores pasados no cambian cuando se agregan datos nuevos.

# Calcular el SPEI --------------------------------------------------------

## Parámetros
scl <- 6   # escala del SPEI (meses)

## Control: una fila por mes, de 2001-01 a 2026-07, en orden
dts  <- seq(as.Date('2001-01-01'), as.Date('2026-07-01'), by = 'month')
dfrm <- dfrm |> filter(date %in% dts) |> arrange(date)
stopifnot(nrow(dfrm) == length(dts), all(dfrm$date == dts))

## Serie de tiempo
tsr <- ts(dfrm$value, start = c(2001, 1), frequency = 12)

## 1. Línea base 2001-01 a 2020-12: ajustar la distribución y guardar sus coeficientes
bse <- spei(window(tsr, start = c(2001, 1), end = c(2020, 12)), scale = scl, verbose = FALSE)
cfn <- bse$coefficients

## 2. Periodo nuevo: coeficientes guardados, sin reajuste (empieza scl - 1 meses antes de 2021-01)
rec <- spei(window(tsr, start = 2021 - (scl - 1) / 12), scale = scl, params = cfn, verbose = FALSE)

r1 <- tibble(
  dates = seq(from = as.Date('2001-01-01'), to = as.Date('2020-12-01'), by = 'month'),
  spei1 = as.numeric(bse$fitted)
)

r2 <- tibble(
  dates = seq(from = as.Date('2021-01-01'), to = as.Date('2026-07-01'), by = 'month'),
  spei1 = as.numeric(window(rec$fitted, start = c(2021, 1)))
)

Coeficientes

Cada distribución mensual tiene tres parámetros: xi (posición), alpha (escala) y kappa (forma). spei() los devuelve en bse$coefficients, un arreglo de 3 parámetros × 1 serie × 12 meses:

str(cfn)
##  num [1:3, 1, 1:12] -525.967 137.141 -0.186 -506.681 130.466 ...
##  - attr(*, "dimnames")=List of 3
##   ..$ par: chr [1:3] "xi" "alpha" "kappa"
##   ..$    : NULL
##   ..$    : NULL
tibble(Mes = c('Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', 'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic'),
       xi = cfn['xi', 1, ], alpha = cfn['alpha', 1, ], kappa = cfn['kappa', 1, ]) |>
  kable(digits = 3, caption = 'Tabla 1. Coeficientes de la línea base (2001-2020): una distribución log-logística por mes calendario.')
Tabla 1. Coeficientes de la línea base (2001-2020): una distribución log-logística por mes calendario.
Mes xi alpha kappa
Ene -525.967 137.141 -0.186
Feb -506.681 130.466 -0.178
Mar -546.349 111.199 -0.095
Abr -680.630 67.982 -0.191
May -763.880 37.938 -0.313
Jun -815.170 26.560 -0.392
Jul -842.225 26.051 -0.414
Ago -862.581 31.582 -0.421
Sep -809.497 56.263 -0.334
Oct -677.135 101.493 -0.194
Nov -588.538 126.370 -0.165
Dic -543.907 135.217 -0.175

Este objeto (36 números por píxel) es todo lo que hay que guardar, por ejemplo con saveRDS(). Solo sirve para la misma escala (6 meses) y la misma distribución.

SPEI-6

newp <- list(
  annotate('rect', xmin = as.Date('2021-01-01'), xmax = as.Date('2026-07-01'), ymin = -Inf, ymax = Inf, fill = 'grey90'),
  annotate('text', x = as.Date('2011-01-01'), y = Inf, vjust = 1.5, size = 3.2, color = 'grey30', label = 'Línea base 2001-2020: coeficientes ajustados'),
  annotate('text', x = as.Date('2023-10-15'), y = Inf, vjust = 1.2, size = 3.2, color = 'grey30', label = '2021-2026:\ncoeficientes guardados'),
  scale_y_continuous(expand = expansion(mult = c(0.05, 0.25)))
)

bind_rows(r1, r2) |>
  transmute(date = dates, value = spei1) |>
  plot_sign(ylab = 'SPEI-6', bg = newp)
Figura 2. SPEI-6 del píxel de prueba. Blanco: línea base, coeficientes ajustados; gris: periodo nuevo, coeficientes guardados. Azul: húmedo; marrón: seco.

Figura 2. SPEI-6 del píxel de prueba. Blanco: línea base, coeficientes ajustados; gris: periodo nuevo, coeficientes guardados. Azul: húmedo; marrón: seco.

Validación

El mismo SPEI se calculó de una sola vez con la serie completa 2001–2026, usando 2001–2020 como periodo de referencia (ref.start / ref.end), y se comparó con el resultado en dos pasos:

## SPEI total, mismo periodo de referencia
fll <- spei(tsr, scale = scl, ref.start = c(2001, 1), ref.end = c(2020, 12), verbose = FALSE)
rr  <- rbind(r1, r2)
rr  <- mutate(rr, spei2 = as.numeric(fll$fitted))
all.equal(rr$spei1, rr$spei2)
## [1] TRUE

En los 302 meses con valor, la diferencia absoluta máxima entre ambos cálculos es 0: los resultados son idénticos. Meses alrededor del cambio de periodo:

rr |>
  filter(dates >= as.Date('2020-10-01'), dates <= as.Date('2021-03-01')) |>
  transmute(Mes = format(dates, '%Y-%m'), `Dos pasos` = spei1, `Serie completa` = spei2, Diferencia = spei1 - spei2) |>
  kable(digits = 4, caption = 'Tabla 2. SPEI-6 con ambos cálculos.')
Tabla 2. SPEI-6 con ambos cálculos.
Mes Dos pasos Serie completa Diferencia
2020-10 -0.0993 -0.0993 0
2020-11 0.6481 0.6481 0
2020-12 0.5510 0.5510 0
2021-01 0.5756 0.5756 0
2021-02 0.5726 0.5726 0
2021-03 0.7080 0.7080 0

Conclusión

Guardar los coeficientes de la línea base basta para calcular el SPEI de cualquier mes nuevo respecto al clima de 2001–2020, con exactamente el mismo resultado que recalcular la serie completa. El procedimiento se puede aplicar píxel por píxel, guardando un juego de coeficientes por cada uno.