Esta página estima cuántos días calurosos y olas de calor habría con el clima proyectado bajo SSP2-4.5 y SSP5-8.5 Riahi et al., 2017O'Neill et al., 2017. A la serie diaria observada se le suma el cambio medio de la temperatura máxima de cada modelo de NEX-GDDP-CMIP6 Thrasher et al., 2022 (método delta aditivo) y se recuentan los días sobre el umbral histórico fijo (percentil 90 de 1991–2020): así, el cambio mide cuánto se desplaza el clima respecto del que el barrio conoció.
Deltas de temperatura máxima¶
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from IPython.display import display
from sidro.analisis import clima
from sidro.config import aoi_cfg, parametros
from sidro.fetch import obtener_capa
MODELOS = None # p. ej. ["ACCESS-CM2", "EC-Earth3"] para una prueba rápida
cfg = aoi_cfg()["clima"]
par = parametros()["calor"]
serie = intentar(obtener_capa, "clima_observado_diario", mensaje="No se pudo obtener la serie climática diaria")
tmax = serie["temperature_2m_max"] if serie is not None and "temperature_2m_max" in serie.columns else None
deltas = None
if tmax is not None:
deltas = intentar(clima.deltas_tabla, ["tasmax"], cfg["escenarios"], cfg["horizontes"], MODELOS, cache="deltas_calor",
mensaje="No se pudieron obtener las proyecciones CMIP6 (requiere acceso a Planetary Computer)")
if deltas is not None:
display(deltas.groupby(["escenario", "horizonte"])["delta"].describe()[["count", "min", "50%", "max"]].round(2))La tabla muestra el calentamiento (en °C) de la temperatura máxima diaria respecto del periodo histórico de cada modelo, para cada escenario y horizonte.
Días y olas de calor proyectados¶
resultados = None
if deltas is not None:
umbral = clima.umbral_percentil(tmax, par["percentil_umbral_tmax"])
base = clima.olas_calor(tmax, umbral, par["dias_minimos_ola"])
q = cfg["percentiles_ensamble"]
filas = []
for (esc, hor), g in deltas.groupby(["escenario", "horizonte"]):
por_modelo = [clima.olas_calor(clima.aplicar_delta(tmax, d, aditivo=True), umbral, par["dias_minimos_ola"])
[["dias_sobre_umbral", "eventos"]].mean() for d in g["delta"]]
t = pd.DataFrame(por_modelo)
for col in ("dias_sobre_umbral", "eventos"):
filas.append({"escenario": esc, "horizonte": hor, "indicador": col, "base": base[col].mean(),
**{f"p{x}": np.percentile(t[col], x) for x in q}})
resultados = pd.DataFrame(filas)
print(f"Umbral histórico: {umbral:.1f} °C (percentil {par['percentil_umbral_tmax']} de Tmax en {cfg['periodo_base'][0]}–{cfg['periodo_base'][1]})")
display(resultados.round(1))Umbral histórico: 20.5 °C (percentil 90 de Tmax en 1991–2020)
Cada fila es el promedio anual sobre la serie observada desplazada por el delta de cada modelo (p10,
p50 y p90 entre modelos), junto con el promedio de la serie sin desplazar (base). El umbral se fija
en el clima histórico, por lo que un aumento de los días sobre él es un aumento respecto de lo que el
barrio ha vivido.
if resultados is not None:
r = resultados[resultados["indicador"] == "dias_sobre_umbral"]
fig, ax = plt.subplots(figsize=(7, 3.4))
x = np.arange(len(r))
med = r[f"p{q[1]}"].values
ax.bar(x, med, yerr=[med - r[f"p{q[0]}"].values, r[f"p{q[2]}"].values - med], color="#d1495b", capsize=4)
ax.axhline(r["base"].iloc[0], ls="--", color="#555", label="historia observada")
ax.set_xticks(x, [f"{e}\n{h}" for e, h in zip(r.escenario, r.horizonte)], fontsize=8)
ax.set_ylabel("Días sobre el umbral por año"), ax.legend()
fig
Trayectoria de la temperatura hasta 2100¶
Los horizontes de CMIP6 dan solo dos instantáneas (2041–2060 y 2081–2100). Para ver el camino entre ellas, y cuántos años serán «extremos» según el clima de hoy, se arma un conjunto probabilístico en el espíritu del escalamiento por patrón de calentamiento Tebaldi & Arblaster, 2014, como en el cuaderno de Montería con CLIMADA Subdirección para el Conocimiento del Riesgo, 2026Aznar-Siguan & Bresch, 2019. La diferencia es que aquí el calentamiento local no se supone a partir de la temperatura global: se interpola entre los deltas reales de cada modelo de NEX-GDDP (cero hacia 2005, el delta de cada horizonte en su año central y constante después). Cada uno de 500 miembros toma un modelo al azar y le suma variabilidad interanual con la desviación observada en 1991–2020.
proyeccion = None
if deltas is not None:
base_anual = tmax.groupby(tmax.index.year).mean()
en_base = base_anual[(base_anual.index >= cfg["periodo_base"][0]) & (base_anual.index <= cfg["periodo_base"][1])]
media_base, sigma = float(en_base.mean()), float(en_base.std())
anios = np.arange(2024, 2101)
proyeccion = clima.trayectoria_temperatura(media_base, sigma, deltas, anios)
print(f"Tmax media anual 1991–2020: {media_base:.2f} °C; variabilidad interanual (σ): {sigma:.2f} °C")Tmax media anual 1991–2020: 18.61 °C; variabilidad interanual (σ): 0.44 °C
if proyeccion is not None:
colores = {"ssp245": "#2a9d8f", "ssp585": "#d1495b"}
fig, ax = plt.subplots(figsize=(8, 3.8))
ax.plot(base_anual.index, base_anual.values, color="#777", lw=1, label="observada (ERA5-Land)")
for esc, m in proyeccion.items():
ax.fill_between(anios, *np.percentile(m, [5, 95], axis=0), color=colores.get(esc), alpha=0.2)
ax.plot(anios, np.median(m, axis=0), color=colores.get(esc), lw=2, label=f"{esc} (mediana y 5–95 %)")
ax.axhline(media_base + 1.645 * sigma, ls=":", color="#555", label="año extremo (p95 de 1991–2020)")
ax.set_xlabel("Año"), ax.set_ylabel("Tmax media anual (°C)"), ax.legend(fontsize=8, loc="upper left")
fig
if proyeccion is not None:
umbral_extremo = media_base + 1.645 * sigma
freq = clima.frecuencia_extremos(proyeccion, umbral_extremo) * 100
fig, ax = plt.subplots(figsize=(8, 3.2))
for esc in freq:
ax.plot(anios, freq[esc], color=colores.get(esc), lw=2, label=esc)
ax.axhline(5, ls="--", color="#555", label="frecuencia climatológica (5 %)")
ax.set_ylim(0, 100), ax.set_xlabel("Año"), ax.set_ylabel("Probabilidad de año extremo (%)"), ax.legend(fontsize=8)
fig
pd.DataFrame({esc: {a: round(float(freq[esc].iloc[list(anios).index(a)]), 0) for a in (2030, 2050, 2070, 2090)}
for esc in freq}).rename_axis("año")
Cuando la probabilidad de un año extremo supera el 50 %, lo que hoy es excepcional pasa a ser lo normal. La banda del abanico mezcla la dispersión entre modelos (con solo cinco) y la variabilidad natural; no es un intervalo de confianza formal.
if proyeccion is not None:
sel = (anios >= 2041) & (anios <= 2060)
display(pd.DataFrame({esc: clima.nivel_retorno(m[:, sel]) for esc, m in proyeccion.items()})
.rename_axis("periodo de retorno (años)").round(2).add_prefix("Tmax anual 2041–2060, "))La tabla da la temperatura máxima media anual que se excede, en promedio, una vez cada T años en 2041–2060.
Guardar el resumen¶
if resultados is not None:
from sidro import export
extra = {}
if proyeccion is not None:
extra = {"prob_ano_extremo_pct": {esc: {int(a): float(freq[esc].iloc[i]) for i, a in enumerate(anios) if a % 10 == 0}
for esc in freq}}
export.guardar_resumen("calor_escenarios", {"umbral_c": float(umbral), "escenarios": resultados.to_dict("records"), **extra})- Riahi, K., van Vuuren, D. P., Kriegler, E., & others. (2017). The Shared Socioeconomic Pathways and their energy, land use, and greenhouse gas emissions implications: An overview. Global Environmental Change, 42, 153–168. 10.1016/j.gloenvcha.2016.05.009
- O’Neill, B. C., Kriegler, E., Ebi, K. L., & others. (2017). The roads ahead: Narratives for shared socioeconomic pathways describing world futures in the 21st century. Global Environmental Change, 42, 169–180. 10.1016/j.gloenvcha.2015.01.004
- Thrasher, B., Wang, W., Michaelis, A., Melton, F., Lee, T., & Nemani, R. (2022). NASA Global Daily Downscaled Projections, CMIP6. Scientific Data, 9, 262. 10.1038/s41597-022-01393-4
- Tebaldi, C., & Arblaster, J. M. (2014). Pattern scaling: Its strengths and limitations, and an update on the latest model simulations. Climatic Change, 122, 459–471. 10.1007/s10584-013-1032-9
- Subdirección para el Conocimiento del Riesgo. (2026). Proyección de temperatura en Montería, Córdoba, bajo escenarios de cambio climático (CRC001). Zenodo. 10.5281/zenodo.20061610
- Aznar-Siguan, G., & Bresch, D. N. (2019). CLIMADA v1: a global weather and climate risk assessment platform. Geoscientific Model Development, 12, 3085–3097. 10.5194/gmd-12-3085-2019