Skip to contents

Acerca de este tutorial Este documento demuestra el pipeline completo del paquete climasus4r para la generación de índices bioclimáticos a partir de datos horarios INMET: descarga → relleno de lagunas → cálculo de 15 indicadores → visualización y categorización regional de riesgo térmico.


Fundamentos Teóricos

¿Por qué índices bioclimáticos?

La temperatura del aire (T) por sí sola no representa adecuadamente el estrés térmico humano. El sistema termorregulador responde a un balance energético complejo que integra:

  • Temperatura del aire (T, °C)
  • Humedad relativa (RH, %): afecta la eficiencia de la evaporación del sudor.
  • Velocidad del viento (WS, m/s): modifica la capa límite de convección.
  • Radiación solar (SR, W/m²): añade carga térmica radiante.

Índices como WBGT, UTCI y PET traducen esta interacción en un valor escalar con significado fisiológico directo, siendo herramientas estándar en salud ocupacional, vigilancia de olas de calor y planificación urbana.

📊 Indicadores Bioclimáticos y de Estrés Térmico

Código Nombre Variables de Entrada Dominio de Validez Referencia Principal
wbgt Wet-Bulb Globe Temperature T, RH, SR, WS General Liljegren et al. (2008)
hi Heat Index T, RH T ≥ 26.7 °C Rothfusz (1990); NWS
thi Temperature-Humidity Index T, RH General Thom (1959)
wcet Wind Chill Equivalent Temp. (CA) T, WS T ≤ 0 °C Environment Canada (2001)
wct Wind Chill Temp. (NWS) T, WS T ≤ 10 °C NWS (2001)
et Effective Temperature T, RH, WS General Missenard (1937) apud Auliciems (2007)
utci Universal Thermal Climate Index T, RH, WS, SR -60 a +50 °C Bröde et al. (2012)
pet Physiological Equivalent Temp. T, RH, WS, SR General Matzarakis et al. (1999)
cdd Cooling Degree Days T T > T_base ASHRAE (2021)
hdd Heating Degree Days T T < T_base ASHRAE (2021)
gdd Growing Degree Days T T_base < T < T_upper McMaster & Wilhelm (1997)
diurnal_range Amplitud Térmica Diurna T (min, max) General -
vapor_pressure Presión de Vapor Actual T, RH General Magnus-Tetens (1930)
heat_stress_risk Riesgo de Estrés Térmico WBGT, UTCI Categórico OSHA/ACGIH (2017)
koppen_humidity Clase de Humedad (Köppen) RH Categórico Köppen (1936)

Fórmulas Matemáticas

1. WBGT (Wet-Bulb Globe Temperature)

Modelo implementado para ambiente exterior bajo carga solar directa.

WBGTout = 0.7 × Tnwb + 0.2 × Tg + 0.1 × Tdb

Donde:

  • Tdb = Temperatura de bulbo seco (°C).
  • Tnwb = Temperatura de bulbo húmedo natural (°C). Para robustez numérica, se utiliza el promedio de dos estimaciones:
    • Liljegren: T_nwb_lilj = T * atan(0.16 * sqrt(e_a + 0.1)) + 3.0
    • Bernard & Pourmoghani: T_nwb_bp = T + 0.33 * (RH/100) * exp(0.0514 * T) - 4.0
    • T_nwb = (T_nwb_lilj + T_nwb_bp) / 2
    • Donde e_a es la presión de vapor actual (kPa), calculada a partir de T y RH.
  • Tg = Temperatura de globo (°C), que modela la carga radiante:
    • T_g = T + 0.0144 * max(G_0, 0)^0.6 / WS^0.2 - 2.0
    • G_0 es la irradiancia solar en W/m², convertida de SR (kJ/m²) mediante SR / 3.6.

Ponderación: Los coeficientes 0.7, 0.2 y 0.1 representan la contribución relativa del enfriamiento evaporativo (bulbo húmedo), de la carga radiante (globo) y del calor sensible (bulbo seco) para el estrés térmico en trabajadores al aire libre.

2. Heat Index (HI)

Implementación del modelo de regresión polinomial del National Weather Service (NWS), con ajustes para condiciones de humedad extrema.

HI°F = −42.379 + 2.04901523·T + 10.14333127·RH − 0.22475541·T·RH − 0.00683783·T² − 0.05481717·RH² + 0.00122874·T²·RH + 0.00085282·T·RH² − 0.00000199·T²·RH²

El valor final se convierte a Celsius: HI = (HI°F - 32) * 5/9.

Umbral de Validez Adaptado por Bioma: * Estándar: T ≥ 26.7 °C. * Regiones tropicales (Amazonía, Mata Atlántica): T ≥ 24.0 °C, debido a la aclimatación de las poblaciones locales.

Ajustes Post-Cálculo: * Si RH < 13% y 26.7 ≤ T ≤ 44.4 °C: HI = HI - ((13 - RH)/4) * sqrt((17 - |T - 95|) / 17). * Si RH > 85% y 26.7 ≤ T ≤ 30.6 °C: HI = HI + ((RH - 85)/10) * ((87 - T)/5).

3. Universal Thermal Climate Index (UTCI)

Aproximación polinomial del modelo de referencia Fiala, optimizada para aplicaciones operacionales a gran escala.

  1. Temperatura Radiante Media (Tmrt): T_mrt = T + 0.07 * G_0 - 1.5 (aproximación para radiación de onda corta).
  2. Presión de Vapor (VP, kPa): VP = 0.6108 * exp(17.27 * T / (T + 237.3)) * (RH / 100) @Tetens1930.
  3. Velocidad del Viento a 10 m (WS10m): WS_10m = max(WS * 1.5, 0.5) (conversión de la altura estándar de 2m a 10m).

UTCI (°C) = T + 0.048 * ((VP1000) - 1000)/100 - 0.29 ln(WS_10m + 0.1) + 0.016 * ΔT_mrt + 0.010 * (ΔT_mrt²/10) - 6e-5 * ΔT_mrt² * WS_10m + δregional

Donde ΔT_mrt = T_mrt - T. El término δ<sub>regional</sub> es una corrección empírica calibrada para biomas brasileños.

4. Grados-Día (CDD, HDD, GDD)

  • CDD (Cooling Degree Days): CDD = max(0, T - T_base_cdd). Base regional: 18–20 °C.
  • HDD (Heating Degree Days): HDD = max(0, T_base_hdd - T). Base regional: 14–18 °C.
  • GDD (Growing Degree Days): GDD = max(0, min(T, T_upper) - T_base_gdd). Base regional: 8–15 °C. Límite superior regional: 30–35 °C.

Pipeline Completo

Paso 1 · Importación INMET

library(climasus4r)
library(dplyr)
# ── Pipeline para 4 biomas representativos ───────────────────────────────────
# Amazônia (AM), Caatinga (CE), Mata Atlântica (SP), Pampa (RS)

df_am <- sus_climate_inmet(
  years   = 2023,
  uf      = "AM",          # Amazônia
  parallel = TRUE,
  workers  = 4,
  verbose  = TRUE
)

df_ce <- sus_climate_inmet(years = 2023, uf = "CE")   # Caatinga
df_sp <- sus_climate_inmet(years = 2023, uf = "SP")   # Mata Atlântica
df_rs <- sus_climate_inmet(years = 2023, uf = "RS")   # Pampa

# Inspecionar estrutura
head(df_am)

Paso 2 · Relleno de Lagunas

# ── XGBoost gap-filling — todas as variáveis ─────────────────────────────────
df_am_filled <- df_am |>
  sus_climate_fill_inmet(
    target_var        = "all",
    quality_threshold = 0.4,
    parallel          = TRUE,
    workers           = 4,
    verbose           = TRUE
  )

df_ce_filled <- df_ce |> sus_climate_fill_inmet(target_var = "all")
df_sp_filled <- df_sp |> sus_climate_fill_inmet(target_var = "all")
df_rs_filled <- df_rs |> sus_climate_fill_inmet(target_var = "all")

Paso 3 · Cálculo de los Indicadores

# ── Carregar função ───────────────────────────────────────────────────────────
# Em produção: já disponível via library(climasus4r)
# Aqui: source do arquivo do pacote
# source("sus_climate_compute_indicators.R")  # descomente se necessário

# ── Configuração de indicadores por bioma ────────────────────────────────────

# Amazônia — adaptação tropical, foco em estresse por calor e umidade
ind_am <- df_am_filled |>
  sus_climate_compute_indicators(
    region           = "amazon",
    confidence_flags = TRUE,
    verify_physics   = TRUE,
    verbose          = FALSE
  )

# Caatinga — semiárido, limiares rebaixados para RH
ind_ce <- df_ce_filled |>
  sus_climate_compute_indicators(
    region           = "caatinga",
    confidence_flags = TRUE,
    verbose          = FALSE
  )

# Mata Atlântica — subtropical úmido
ind_sp <- df_sp_filled |>
  sus_climate_compute_indicators(
    region           = "atlantic_forest",
    confidence_flags = TRUE,
    verbose          = FALSE
  )

# Pampa — subtropical temperado, wind chill relevante no inverno
ind_rs <- df_rs_filled |>
  sus_climate_compute_indicators(
    region           = "pampa",
    confidence_flags = TRUE,
    verbose          = FALSE
  )

# ── Combinar todos os biomas ──────────────────────────────────────────────────
ind_all <- bind_rows(
  mutate(ind_am, bioma = "Amazônia",      regiao = "Norte"),
  mutate(ind_ce, bioma = "Caatinga",      regiao = "Nordeste"),
  mutate(ind_sp, bioma = "Mata Atlântica",regiao = "Sudeste"),
  mutate(ind_rs, bioma = "Pampa",         regiao = "Sul")
)

glimpse(ind_all |> select(date, bioma, wbgt_c, hi_c, thi_c, utci_c,
                           pet_c, heat_stress_risk, koppen_humidity))

Visualizaciones

4.1 Visión General de los Indicadores

# ── Distribuições dos principais índices por bioma ───────────────────────────
library(tidyr)
library(ggplot2)
library(ggridges)
library(patchwork)
library(scales)


bioma_colors <- c(
  "Amazônia"       = "#1a7a4a",
  "Caatinga"       = "#c0392b",
  "Mata Atlântica" = "#2471a3",
  "Pampa"          = "#7d3c98"
)

ind_long <- ind_all |>
  select(date, bioma, wbgt_c, hi_c, thi_c, utci_c, pet_c, et_c) |>
  pivot_longer(
    cols      = c(wbgt_c, hi_c, thi_c, utci_c, pet_c, et_c),
    names_to  = "indice",
    values_to = "valor"
  ) |>
  filter(!is.na(valor)) |>
  mutate(indice = recode(indice,
    wbgt_c = "WBGT (°C)",
    hi_c   = "Heat Index (°C)",
    thi_c  = "THI (°C)",
    utci_c = "UTCI (°C)",
    pet_c  = "PET (°C)",
    et_c   = "ET (°C)"
  ))

ggplot(ind_long, aes(x = valor, y = bioma, fill = bioma)) +
  ggridges::geom_density_ridges(
    alpha = 0.75, scale = 0.9,
    quantile_lines = TRUE, quantiles = c(0.25, 0.5, 0.75),
    quantile_fun = function(x, ...) quantile(x, ...)
  ) +
  scale_fill_manual(values = bioma_colors, guide = "none") +
  facet_wrap(~indice, scales = "free_x", ncol = 3) +
  labs(
    title    = "Distribuição dos Índices Bioclimáticos por Bioma — 2023",
    subtitle = "Linhas internas = quartis (Q1, mediana, Q3)",
    x = "Valor do índice", y = NULL,
    caption  = "Fonte: INMET / climasus4r | Dados horários 2023"
  ) +
  theme(strip.background = element_rect(fill = "#eaf4fb", colour = "grey80"))

Nota: el bloque anterior usa ggridges; instálelo con install.packages("ggridges") si es necesario.

# Versão alternativa sem ggridges
ggplot(ind_long |> filter(!is.na(valor)),
       aes(x = valor, colour = bioma, fill = bioma)) +
  geom_density(alpha = 0.35, linewidth = 0.9) +
  scale_fill_manual(values  = bioma_colors, name = "Bioma") +
  scale_colour_manual(values = bioma_colors, name = "Bioma") +
  facet_wrap(~indice, scales = "free", ncol = 3) +
  labs(
    title   = "Distribuição dos Índices Bioclimáticos — 2023",
    x = "Valor do índice (°C equiv.)", y = "Densidade",
    caption = "Fonte: INMET / climasus4r"
  )

4.2 Serie Temporal Estacional

# ── Média diária de WBGT por bioma ───────────────────────────────────────────
diario <- ind_all |>
  mutate(data_d = as.Date(date)) |>
  group_by(bioma, data_d) |>
  summarise(
    wbgt_med  = mean(wbgt_c,  na.rm = TRUE),
    utci_med  = mean(utci_c,  na.rm = TRUE),    .groups   = "drop"
  )

# Bandas de risco WBGT (OSHA/ACGIH)
bandas_wbgt <- tibble(
  ymin  = c(-Inf, 20, 25, 28, 30, 33),
  ymax  = c(20,   25, 28, 30, 33, Inf),
  risco = factor(
    c("Nenhum","Baixo","Moderado","Alto","Muito Alto","Extremo"),
    levels = c("Nenhum","Baixo","Moderado","Alto","Muito Alto","Extremo")
  )
)
cores_risco <- c(
  "Nenhum"    = "#d5f5e3",
  "Baixo"     = "#a9dfbf",
  "Moderado"  = "#f9e79f",
  "Alto"      = "#f0b27a",
  "Muito Alto"= "#ec7063",
  "Extremo"   = "#922b21"
)

p1 <- ggplot() +
  geom_rect(
    data = bandas_wbgt,
    aes(xmin = as.Date("2022-12-31"), xmax = as.Date("2024-01-01"),
        ymin = ymin, ymax = ymax, fill = risco),
    alpha = 0.25
  ) +
  scale_fill_manual(values = cores_risco, name = "Risco WBGT") +
  #new_scale_fill() +
  geom_line(data = diario,
            aes(x = data_d, y = wbgt_med, colour = bioma), linewidth = 0.8) +
  scale_colour_manual(values = bioma_colors, name = "Bioma") +
  scale_x_date(date_labels = "%b", date_breaks = "1 month") +
  labs(
    title    = "WBGT Médio Diário — Biomas Brasileiros 2023",
    subtitle = "Bandas de risco: OSHA/ACGIH (adaptadas por bioma)",
    x = NULL, y = "WBGT (°C)",
    caption  = "Zonas de risco: Baixo = 20–25°C | Moderado = 25–28°C | Alto = 28–30°C | Muito Alto = 30–33°C | Extremo > 33°C"
  )

# UTCI e WBGT sobrepostos para Caatinga
p2 <- diario |>
  filter(bioma == "Caatinga") |>
  pivot_longer(c(wbgt_med, utci_med),
               names_to = "var", values_to = "val") |>
  mutate(var = recode(var,
    wbgt_med = "WBGT", utci_med = "UTCI")) |>
  ggplot(aes(data_d, val, colour = var)) +
  geom_line(linewidth = 0.9) +
  scale_colour_manual(values = c("WBGT"="#c0392b","UTCI"="#8e44ad","T ar"="#2980b9"),
                      name = "Índice") +
  scale_x_date(date_labels = "%b", date_breaks = "2 months") +
  labs(
    title    = "Caatinga — WBGT vs UTCI vs T ar",
    subtitle = "WBGT e UTCI consistentemente acima da temperatura seca",
    x = NULL, y = "°C"
  )

p1 / p2

4.4 Categorías de Riesgo Regional

# ── Distribuição mensal das categorias de risco por bioma ────────────────────
risco_mensal <- ind_all |>
  mutate(mes = month(date, label = TRUE, abbr = TRUE, locale = "pt_BR.UTF-8")) |>
  group_by(bioma, mes) |>
  count(heat_stress_risk) |>
  mutate(pct = n / sum(n) * 100) |>
  ungroup() |>
  mutate(
    heat_stress_risk = factor(heat_stress_risk,
      levels = c("None","Low","Moderate","High","Very High","Extreme")),
    risco_label = recode(heat_stress_risk,
      "None"      = "Nenhum",
      "Low"       = "Baixo",
      "Moderate"  = "Moderado",
      "High"      = "Alto",
      "Very High" = "Muito Alto",
      "Extreme"   = "Extremo"
    ),
    risco_label = factor(risco_label,
      levels = c("Nenhum","Baixo","Moderado","Alto","Muito Alto","Extremo"))
  )

ggplot(risco_mensal, aes(mes, pct, fill = risco_label)) +
  geom_col(position = "stack", width = 0.85, colour = "white", linewidth = 0.2) +
  scale_fill_manual(
    values = c(
      "Nenhum"    = "#abebc6",
      "Baixo"     = "#82e0aa",
      "Moderado"  = "#f9e79f",
      "Alto"      = "#f0b27a",
      "Muito Alto"= "#ec7063",
      "Extremo"   = "#7b241c"
    ),
    name = "Risco\ntérmico"
  ) +
  scale_y_continuous(labels = percent_format(scale = 1)) +
  facet_wrap(~bioma, ncol = 2) +
  labs(
    title    = "Composição Mensal do Risco de Estresse Térmico (WBGT-base)",
    subtitle = "Adaptado por bioma: limiares OSHA/ACGIH + correção regional",
    x = NULL, y = "% horas por categoria",
    caption  = paste("Caatinga: pico de risco Extremo em set–out (seca)",
                     "| Amazônia: risco Alto-Muito Alto persistente",
                     "| Pampa: dominado por Nenhum no inverno",
                     sep = "\n")
  ) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

4.5 Grados-Día Agrícolas

# ── Acumulado de GDD, CDD e HDD por bioma ────────────────────────────────────
grausdia <- ind_all |>
  mutate(data_d = as.Date(date)) |>
  group_by(bioma, data_d) |>
  summarise(
    cdd = sum(cdd_c, na.rm = TRUE),
    hdd = sum(hdd_c, na.rm = TRUE),
    gdd = sum(gdd_c, na.rm = TRUE),
    .groups = "drop"
  ) |>
  group_by(bioma) |>
  arrange(data_d) |>
  mutate(
    cdd_acum = cumsum(cdd),
    hdd_acum = cumsum(hdd),
    gdd_acum = cumsum(gdd)
  ) |>
  ungroup()

p_gdd1 <- grausdia |>
  select(bioma, data_d, cdd_acum, hdd_acum) |>
  pivot_longer(c(cdd_acum, hdd_acum), names_to = "tipo", values_to = "val") |>
  mutate(tipo = recode(tipo,
    cdd_acum = "CDD (resfriamento)",
    hdd_acum = "HDD (aquecimento)"
  )) |>
  ggplot(aes(data_d, val, colour = bioma, linetype = tipo)) +
  geom_line(linewidth = 0.9) +
  scale_colour_manual(values = bioma_colors, name = "Bioma") +
  scale_linetype_manual(values = c("solid","dashed"), name = NULL) +
  scale_x_date(date_labels = "%b") +
  labs(
    title = "Graus-dia Acumulados — CDD e HDD",
    subtitle = "Base regional (ASHRAE adaptado): 18–20°C para CDD | 14–18°C para HDD",
    x = NULL, y = "Graus-dia acumulados"
  )

p_gdd2 <- grausdia |>
  ggplot(aes(data_d, gdd_acum, colour = bioma)) +
  geom_line(linewidth = 0.9) +
  scale_colour_manual(values = bioma_colors, name = "Bioma") +
  scale_x_date(date_labels = "%b") +
  labs(
    title = "GDD Acumulados — Potencial Agrícola",
    subtitle = "Base 8–15°C (regional) | Limite superior 30–35°C",
    x = NULL, y = "GDD acumulados"
  )

p_gdd1 | p_gdd2

4.7 Amplitud Térmica y Vapor

vapor_dtr <- ind_all |>
  mutate(
    mes = month(date),
    trimestre = case_when(
      mes %in% 1:3  ~ "T1 (Jan–Mar)",
      mes %in% 4:6  ~ "T2 (Abr–Jun)",
      mes %in% 7:9  ~ "T3 (Jul–Set)",
      TRUE          ~ "T4 (Out–Dez)"
    )
  ) |>
  filter(!is.na(diurnal_range_c), !is.na(vapor_pressure_kpa))

p_vap <- ggplot(vapor_dtr,
       aes(diurnal_range_c, vapor_pressure_kpa, colour = bioma)) +
  geom_point(alpha = 0.08, size = 0.6) +
  geom_smooth(method = "lm", se = FALSE, linewidth = 1.1) +
  scale_colour_manual(values = bioma_colors, name = "Bioma") +
  scale_y_continuous(labels = label_number(suffix = " kPa")) +
  labs(
    title    = "Amplitude Térmica Diurna × Pressão de Vapor Atual",
    subtitle = "Caatinga: alta amplitude + baixa pressão de vapor (aridez)\nAmazônia: baixa amplitude + alta pressão (carga evaporativa máxima)",
    x = "DTR — Amplitude Diurna (°C)", y = "Pressão de vapor ea (kPa)"
  ) +
  facet_wrap(~bioma, ncol = 2) +
  theme(legend.position = "none")

p_vap

4.8 Clasificaciones Categóricas

# ── Köppen Humidity e Heat Stress Risk — proporção anual por bioma ────────────

koppen_prop <- ind_all |>
  count(bioma, koppen_humidity) |>
  group_by(bioma) |>
  mutate(pct = n / sum(n) * 100) |>
  ungroup()

risk_anual <- ind_all |>
  count(bioma, heat_stress_risk) |>
  group_by(bioma) |>
  mutate(
    pct = n / sum(n) * 100,
    heat_stress_risk = factor(heat_stress_risk,
      levels = c("None","Low","Moderate","High","Very High","Extreme"))
  ) |>
  ungroup()

p_kopp <- ggplot(koppen_prop, aes(bioma, pct, fill = koppen_humidity)) +
  geom_col(width = 0.7, colour = "white", linewidth = 0.3) +
  scale_fill_manual(
    values = c("Arid"="#f0b27a","Semi-arid"="#f9e79f",
               "Humid"="#82e0aa","Perhumid"="#2ecc71"),
    name = "Classe Köppen"
  ) +
  scale_y_continuous(labels = percent_format(scale = 1)) +
  coord_flip() +
  labs(
    title = "Classificação Köppen de Umidade",
    subtitle = "% horas anuais por classe",
    x = NULL, y = "% horas"
  )

p_risk <- ggplot(risk_anual, aes(bioma, pct, fill = heat_stress_risk)) +
  geom_col(width = 0.7, colour = "white", linewidth = 0.3) +
  scale_fill_manual(
    values = c(
      "None"      = "#abebc6",
      "Low"       = "#82e0aa",
      "Moderate"  = "#f9e79f",
      "High"      = "#f0b27a",
      "Very High" = "#ec7063",
      "Extreme"   = "#7b241c"
    ),
    name = "Risco Térmico\n(WBGT-base)"
  ) +
  scale_y_continuous(labels = percent_format(scale = 1)) +
  coord_flip() +
  labs(
    title = "Risco de Estresse Térmico (OSHA/ACGIH)",
    subtitle = "% horas anuais por nível de risco",
    x = NULL, y = "% horas"
  )

p_kopp | p_risk

Efecto del Umbral Regional en el HI

# ── Demonstração do efeito do limiar regional no HI ──────────────────────────
t_seq  <- seq(22, 38, 0.2)
rh_val <- 65

calc_hi_vec <- function(airT, rh, min_temp) {
  tf <- (airT * 9/5) + 32
  hi_f <- -42.379 + 2.04901523*tf + 10.14333127*rh - 0.22475541*tf*rh -
           6.83783e-3*tf^2 - 5.481717e-2*rh^2 + 1.22874e-3*tf^2*rh +
           8.5282e-4*tf*rh^2 - 1.99e-6*tf^2*rh^2
  hi_c <- (hi_f - 32) * 5 / 9
  ifelse(airT < min_temp, NA, hi_c)
}

df_hi_reg <- tibble(
  airT = rep(t_seq, 3),
  bioma = rep(c("Sul/Pampa (26°C)","Padrão (26.7°C)","Amazônia (24°C)"), each = length(t_seq)),
  hi    = c(
    calc_hi_vec(t_seq, rh_val, 26.0),
    calc_hi_vec(t_seq, rh_val, 26.7),
    calc_hi_vec(t_seq, rh_val, 24.0)
  )
)

ggplot(df_hi_reg, aes(airT, hi, colour = bioma)) +
  geom_line(linewidth = 1.3, na.rm = TRUE) +
  geom_vline(xintercept = c(24.0, 26.0, 26.7),
             linetype = "dotted", colour = c("#1a7a4a","#7d3c98","#2471a3"),
             linewidth = 0.8) +
  annotate("text", x = c(24.2, 26.2, 26.9), y = 22,
           label = c("Amazônia\n24°C","Sul\n26°C","Padrão\n26.7°C"),
           hjust = 0, size = 3, colour = c("#1a7a4a","#7d3c98","#2471a3")) +
  scale_colour_manual(
    values = c("Sul/Pampa (26°C)"="#7d3c98",
               "Padrão (26.7°C)"="#2471a3",
               "Amazônia (24°C)"="#1a7a4a"),
    name = "Limiar regional"
  ) +
  labs(
    title    = "Impacto do Limiar Regional no Heat Index (RH = 65%)",
    subtitle = "Populações tropicais aclimatadas percebem desconforto a T mais baixas",
    x = "Temperatura do ar (°C)", y = "Heat Index (°C)",
    caption  = "Rothfusz (1990) — limiar padrão 26.7°C | adaptado: 24–26°C para trópicos"
  )

Incertidumbre y Flags de Confianza

Estimativa de Incertidumbre (Monte Carlo)

# ── Incerteza para São Paulo — 200 simulações ─────────────────────────────────
ind_sp_unc <- df_sp_filled |>
  sus_climate_compute_indicators(
    indicators          = c("wbgt", "hi", "utci"),
    region              = "atlantic_forest",
    compute_uncertainty = TRUE,      # ← 200 simulações MC
    confidence_flags    = TRUE,
    verbose             = TRUE
  )

# Colunas geradas: wbgt_c, wbgt_c_ci_low, wbgt_c_ci_high
# Interpretação: IC 95% propagando incertezas T±0.5°C, RH±3%, WS±0.3 m/s, SR±10%

ind_sp_unc |>
  filter(!is.na(wbgt_c_ci_low)) |>
  mutate(ci_width = wbgt_c_ci_high - wbgt_c_ci_low) |>
  summary()

Flags de Confianza

# ── Visualizar os flags gerados ───────────────────────────────────────────────
flags_summary <- ind_all |>
  select(bioma, date, wbgt_c,
         wbgt_c_flag_extreme, wbgt_c_flag_high, wbgt_c_flag_low) |>
  group_by(bioma) |>
  summarise(
    pct_extreme = 100 * mean(wbgt_c_flag_extreme, na.rm = TRUE),
    pct_high    = 100 * mean(wbgt_c_flag_high,    na.rm = TRUE),
    pct_low     = 100 * mean(wbgt_c_flag_low,     na.rm = TRUE),
    .groups     = "drop"
  ) |>
  pivot_longer(-bioma, names_to = "flag", values_to = "pct") |>
  mutate(
    flag = recode(flag,
      pct_extreme = "Flag: Extremo (> 31°C)",
      pct_high    = "Flag: Alto (> 28°C)",
      pct_low     = "Flag: Frio (< 15°C)"
    )
  )

ggplot(flags_summary, aes(bioma, pct, fill = flag)) +
  geom_col(position = "dodge", width = 0.7,
           colour = "white", linewidth = 0.3) +
  scale_fill_manual(
    values = c("Flag: Extremo (> 31°C)"="#922b21",
               "Flag: Alto (> 28°C)"   ="#e67e22",
               "Flag: Frio (< 15°C)"  ="#2980b9"),
    name = NULL
  ) +
  scale_y_continuous(labels = percent_format(scale = 1)) +
  labs(
    title    = "Frequência dos Flags de Confiança WBGT por Bioma",
    subtitle = "Flags: _flag_extreme | _flag_high | _flag_low — gerados automaticamente",
    x = NULL, y = "% horas com flag ativo",
    caption  = paste("Limiares adaptados por bioma:",
                     "Amazônia: extremo>31°C | Caatinga: extremo>33°C | Sul: frio<15°C",
                     sep = "\n")
  )

Patrones de Uso Avanzados

Integración completa en el pipeline SUS

# ── Pipeline de produção para análise epidemiológica ─────────────────────────

library(climasus4r)

# 1. Download multi-UF, multi-ano
df_raw <- sus_climate_inmet(
  years    = 2020:2023,
  uf       = c("AM","PA","CE","BA","SP","MG","RS"),
  parallel = TRUE,
  workers  = 6
)

# 2. Preenchimento XGBoost
df_filled <- df_raw |>
  sus_climate_fill_inmet(
    target_var        = "all",
    quality_threshold = 0.40,
    parallel          = TRUE,
    workers           = 6
  )

# 3. Indicadores com adaptação regional automática
df_indicators <- df_filled |>
  sus_climate_compute_indicators(
    region              = "auto",       # detecta bioma por UF/latitude
    confidence_flags    = TRUE,
    verify_physics      = TRUE,
    compute_uncertainty = FALSE,        # TRUE para análise de sensibilidade
    keep_source_vars    = FALSE
  )

# 4. Resumo mensal por estação — para linkage com dados de mortalidade SIM
resumo_mensal <- df_indicators |>
  mutate(
    ano_mes = format(date, "%Y-%m"),
    station = station_code
  ) |>
  group_by(station, ano_mes) |>
  summarise(
    across(c(wbgt_c, hi_c, utci_c, pet_c, thi_c),
           list(med = ~mean(.x, na.rm=TRUE),
                p90 = ~quantile(.x, 0.90, na.rm=TRUE),
                p95 = ~quantile(.x, 0.95, na.rm=TRUE)),
           .names = "{.col}_{.fn}"),
    horas_risco_alto    = sum(heat_stress_risk %in% c("High","Very High","Extreme"),
                              na.rm = TRUE),
    horas_risco_extremo = sum(heat_stress_risk == "Extreme", na.rm = TRUE),
    cdd_acum            = sum(cdd_c, na.rm = TRUE),
    gdd_acum            = sum(gdd_c, na.rm = TRUE),
    .groups             = "drop"
  )

Umbrales Personalizados para Investigación

# ── Thresholds customizados para estudo específico ───────────────────────────
ind_custom <- df_am_filled |>
  sus_climate_compute_indicators(
    region = "amazon",
    custom_thresholds = list(
      hi_min_temp = 22.0,   # limiar ainda mais conservador
      hi_min_rh   = 35,
      cdd_base    = 22,     # base climática local calibrada
      gdd_base    = 18,
      gdd_upper   = 33
    ),
    confidence_flags = TRUE
  )

Solo Indicadores Seleccionados

# ── Cálculo seletivo — mais eficiente para análises focadas ──────────────────

# Só indicadores de saúde pública ocupacional
ind_ocupacional <- df_ce_filled |>
  sus_climate_compute_indicators(
    indicators       = c("wbgt", "utci", "heat_stress_risk"),
    region           = "caatinga",
    confidence_flags = TRUE,
    verbose          = FALSE
  )

# Só graus-dia para modelagem energética/agrícola
ind_energia <- df_rs_filled |>
  sus_climate_compute_indicators(
    indicators = c("cdd", "hdd", "gdd", "diurnal_range"),
    region     = "pampa",
    verbose    = FALSE
  )

# Só indicadores que não dependem de radiação solar (dados incompletos)
ind_sem_sr <- df_sp_filled |>
  sus_climate_compute_indicators(
    indicators = c("hi", "thi", "et", "vapor_pressure", "koppen_humidity"),
    region     = "atlantic_forest",
    verbose    = FALSE
  )

cat("Colunas geradas (ocupacional):", names(ind_ocupacional), "\n")

Tabla de Referencia de Categorías de Riesgo

🌡️ Clasificación de Riesgo Térmico por Índices Bioclimáticos

Índice Categoría Umbral (°C) Significado Fisiológico Fuente
WBGT Ninguno < 20 Sin riesgo térmico perceptible OSHA/ACGIH
WBGT Bajo 20–25 Estrés leve en actividad intensa OSHA/ACGIH
WBGT Moderado 25–28 Pausas recomendadas para trabajadores OSHA/ACGIH
WBGT Alto 28–30 Riesgo real de trastornos térmicos OSHA/ACGIH
WBGT Muy Alto 30–33 Reducción severa del tiempo de trabajo OSHA/ACGIH
WBGT Extremo > 33 Riesgo de colapso por calor OSHA/ACGIH
UTCI Frío extremo < −27 Hipotermia inminente Bröde et al. (2012)
UTCI Neutro 9–26 Zona de confort térmico Bröde et al. (2012)
UTCI Calor leve 26–32 Estrés leve por calor Bröde et al. (2012)
UTCI Calor fuerte 32–38 Estrés moderado por calor Bröde et al. (2012)
UTCI Calor extremo > 46 Estrés térmico extremo Bröde et al. (2012)
PET Frío extremo < 4 Disconfort térmico por frío Matzarakis et al. (1999)
PET Confort 18–23 Zona térmica neutra Matzarakis et al. (1999)
PET Calor leve 23–29 Leve disconfort por calor Matzarakis et al. (1999)
PET Calor extremo > 41 Estrés térmico muy fuerte Matzarakis et al. (1999)
HI Precaución 27–32 Fatiga posible con exposición prolongada NWS
HI Peligro extremo > 54 Insolación altamente probable NWS

Consideraciones Científicas y Limitaciones

⚠️ Limitaciones Importantes a Reportar

  1. UTCI y PET son aproximaciones — el UTCI completo (Fiala) exige un polinomio de 210+ términos; esta implementación puede divergir 3–5°C en condiciones extremas (Bröde et al., 2012). Para estudios epidemiológicos críticos, use reticulate + pythermalcomfort para el UTCI completo.

  2. Radiación solar nocturna = 0 — los valores NA en sr_kj_m2 durante la noche se imputan como 0 (sin carga solar). El WBGT nocturno refleja solo temperatura, humedad y viento — físicamente correcto.

  3. PET y vestimenta — el modelo simplificado asume vestimenta media estacional (clo 0.5–0.9); poblaciones con hábitos muy distintos (ej.: mineros, trabajadores rurales) requieren calibración específica.

  4. Las adaptaciones regionales son empíricas — los desplazamientos (+1.5°C Amazonía, −0.5°C Caatinga, etc.) reflejan consenso bibliográfico; se recomienda validación local contra mediciones de globo termómetro para publicaciones.

  5. Datos INMET — las estaciones automáticas presentan problemas de mantenimiento; el relleno de lagunas con XGBoost reduce las lagunas pero no elimina la incertidumbre de medición. Use compute_uncertainty = TRUE para propagar esta incertidumbre.


Referencias

  • Alduchov, O. A., & Eskridge, R. E. (1996). Improved Magnus form approximation of saturation vapor pressure. Journal of Applied Meteorology, 35, 601–609.

  • Bernard, T. E., & Pourmoghani, M. (1999). Prediction of workplace wet bulb globe temperature. AIHAJ, 60(1), 32–37.

  • Bröde, P., Fiala, D., Błażejczyk, K., Holmér, I., Jendritzky, G., Kampmann, B., et al. (2012). Deriving the operational procedure for the Universal Thermal Climate Index (UTCI). International Journal of Biometeorology, 56(4), 481–494.

  • International Organization for Standardization. (1989). ISO 7243: Hot environments—Estimation of the heat stress on working man based on the WBGT-index.

  • Joint Action Group for Temperature Indices. (2001). Development of the wind chill temperature index.

  • Liljegren, J. C., Carhart, R. A., Lawday, P., Tschopp, S., & Sharp, R. (2008). Modeling the wet bulb globe temperature using standard meteorological measurements. Journal of Occupational and Environmental Hygiene, 5(10), 645–655.

  • Magnus, G. (1844). Versuche über die spannkräfte des wasserdampfs.

  • Matzarakis, A., Mayer, H., & Iziomon, M. G. (1999). Applications of a universal thermal index: Physiological equivalent temperature. International Journal of Biometeorology, 43, 76–84.

  • Missenard, A. (1937). Température sensible.

  • McMaster, G. S., & Wilhelm, W. W. (1997). Growing degree-days: One equation, two interpretations. Agricultural and Forest Meteorology, 87, 291–300.

  • Missenard, A. (1937). Température sensible.

  • National Weather Service. (n.d.). Heat index equation. https://www.wpc.ncep.noaa.gov/html/heatindex_equation.shtml

  • Occupational Safety and Health Administration. (2017). Protecting workers from the effects of heat (OSHA 3154).

  • Rothfusz, L. P. (1990). The heat index equation (Technical Attachment SR/SSD 90-23). National Weather Service.

  • Tetens, O. (1930). Über einige meteorologische Begriffe. Zeitschrift für Geophysik, 6, 297–309.

  • Thom, E. C. (1959). The discomfort index. Weatherwise, 12(2), 57–59.

🌲 ¿Tienes comentarios o sugerencias?

¿Tienes alguna idea de mejora o encontraste algún error? Haz clic en el botón de abajo para abrir un nuevo issue en GitHub y compartir tus sugerencias directamente con nosotros.

Abrir issue en GitHub