Esta página combina la temperatura de superficie por manzana (3.5.b) con un índice de vulnerabilidad al calor y los cruza en un mapa bivariado, siguiendo el flujo «Urban heatwaves» de CLIMAAX CLIMAAX consortium, 2026. Como la amenaza es una temperatura y no tiene una función de daño, el resultado es una clasificación relativa, no una pérdida.
Temperatura de superficie por unidad¶
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from IPython.display import Markdown, display
from sidro import exposicion, process, viz
from sidro.analisis import calor, espacial, indices
from sidro.config import parametros, rutas
from sidro.fetch import obtener_capa
ruta = intentar(obtener_capa, "temperatura_superficial", mensaje="No se pudo obtener Landsat (requiere Planetary Computer)")
unidades = process.unidades_analisis() if ruta is not None else None
if ruta is not None:
bandas = espacial.bandas_landsat(process.abrir_raster(ruta))
agg = unidades[["id_unidad", "geometry"]].copy()
agg["lst_media_c"] = process.estadistica_zonal(bandas["lst"], unidades, "mean").values
agg["ndvi_medio"] = process.estadistica_zonal(bandas["ndvi"], unidades, "mean").values
agg["lst_anomalia_c"] = agg["lst_media_c"] - agg["lst_media_c"].mean()
print(f"Unidades: {len(agg)} (origen: {unidades.attrs.get('origen')}); "
f"sin dato de LST: {int(agg['lst_media_c'].isna().sum())}")
display(agg.drop(columns="geometry").sort_values("lst_media_c", ascending=False).head(10).round(2))WARNING sidro.fetch: manzanas_censo: falló nivel nacional/usuario -> Archivo de usuario no encontrado: /Users/mauricio/Documents/Data-science-local/SIDRo/data/user/dane_mgn_manzanas_cnpv2018.gpkg
WARNING sidro.process: Unidades: manzanas_censo no disponible (No se pudo obtener 'manzanas_censo'. Intentos: [{'nivel': 'nacional', 'tipo': 'usuario', 'error': 'Archivo de usuario no encontrado: /Users/mauricio/Documents/Data-science-local/SIDRo/data/user/dane_mgn_manzanas_cnpv2018.gpkg'}])
Unidades: 57 (origen: estratificacion); sin dato de LST: 1
La anomalía es la diferencia de cada unidad con el promedio del barrio: muestra dónde se concentra el calor dentro del barrio, no cuánto calor hace en términos absolutos.
if ruta is not None:
m = viz.mapa_base()
viz.capa(m, agg, "lst_media_c", nombre="LST media (°C)", cmap="inferno",
tooltip=["id_unidad", "lst_media_c", "lst_anomalia_c", "ndvi_medio"])
viz.cerrar(m)
else:
m = None
mVulnerabilidad al calor¶
edif = intentar(exposicion.edificaciones, mensaje="Sin edificaciones: la densidad de población no se podrá estimar")
arboles = intentar(obtener_capa, "arbolado", mensaje="Sin censo de arbolado: la variable falta_arbolado se omite")if ruta is not None:
personas = exposicion.ocupantes_por_unidad(unidades, edif) if edif is not None else None
var = calor.variables_calor(unidades, arboles, personas)
agg["vulnerabilidad_calor"] = calor.vulnerabilidad_calor(var).values
usados = calor.vulnerabilidad_calor(var).attrs.get("pesos_usados", {})
pesos = parametros()["calor"]["pesos_vulnerabilidad"]
display(pd.DataFrame({"peso base": pd.Series(pesos), "peso usado": pd.Series(usados),
"unidades con dato": var[list(pesos)].notna().sum()}).fillna(0).round(2))
agg = agg.join(var.drop(columns="id_unidad"))Las variables sin datos reciben peso 0 y los demás pesos se renormalizan. Sin censo, las proporciones de mayores de 65 y menores de 5 no existen, y la densidad de población se estima con los residentes calculados desde el área habitacional (110 m² brutos por residente); en ese caso el índice describe sobre todo densidad y falta de árboles, y no recoge la vulnerabilidad por edad que motiva el análisis. Cárguelos desde 2.2 antes de interpretar el mapa.
if ruta is not None:
display(agg.drop(columns="geometry").sort_values("vulnerabilidad_calor", ascending=False).head(10).round(2))Calor × vulnerabilidad¶
if ruta is not None:
biv = indices.bivariado(agg["lst_media_c"], agg["vulnerabilidad_calor"])
agg = agg.join(biv)
m = viz.mapa_base()
viz.capa_bivariada(m, agg)
viz.cerrar(m)
else:
m = None
mLeyenda: temperatura de superficie en el eje vertical, vulnerabilidad al calor en el horizontal.
Las unidades de la esquina superior derecha (calor alto y vulnerabilidad alta) son las que más necesitan sombra, arbolado y atención a la población sensible. Los terciles son relativos al barrio.
if ruta is not None and agg["arboles_ha"].notna().any():
fig, ax = plt.subplots(figsize=(5, 3.6))
ok = agg["arboles_ha"].notna() & agg["lst_media_c"].notna()
ax.scatter(agg.loc[ok, "arboles_ha"], agg.loc[ok, "lst_media_c"], s=14, color="#8c2d04", alpha=0.7)
ax.set_xlabel("Árboles por hectárea"), ax.set_ylabel("LST media (°C)"), ax.grid(alpha=0.3)
fig
Si la nube de puntos baja hacia la derecha, las unidades con más árboles son más frescas. Es una asociación entre unidades y no demuestra cuánto enfriaría plantar un árbol adicional.
Exportación¶
if ruta is not None:
process.guardar(agg[["id_unidad", "geometry", "lst_media_c", "lst_anomalia_c", "vulnerabilidad_calor"]]
.rename(columns={"lst_media_c": "riesgo", "vulnerabilidad_calor": "vulnerabilidad_social"}), "riesgo_calor")
print("Guardado:", (rutas().processed / "riesgo_calor.parquet").relative_to(rutas().raiz))Guardado: data/processed/riesgo_calor.parquet
riesgo_calor.parquet guarda la LST media como riesgo y la vulnerabilidad al calor, para la
síntesis 3.6. Esa columna de vulnerabilidad es específica del calor y no es la
vulnerabilidad social general de 1.6.
- CLIMAAX consortium. (2026). CLIMAAX Climate Risk Assessment Handbook. Zenodo. 10.5281/zenodo.18186357