Heatmap folium

dataviz
code
folium
Author

Marco Aguirre

Published

October 27, 2025

Un heatmap o mapa de calor es una visualización que utiliza una escala de colores para representar la intensidad o concentración de un fenómeno sobre una superficie. Su objetivo es revelar patrones espaciales de forma rápida e intuitiva.

A diferencia de los mapas coropléticos, que colorean regiones administrativas (provincias, cantones, países), el heatmap utiliza puntos geográficos individuales (latitud y longitud) y genera zonas más claras u oscuras según la densidad de datos (hotspots).

Buenas prácticas y errores comunes

  • Ajustar radio y blur para una visualización adecuada.
  • Usar colores progresivos y claros.
  • Incluir leyenda si es posible.
  • Normalizar datos si los puntos tienen distinto peso o importancia.
  • Usar pocos datos → mapa sin patrones reales.
  • Interpretar el heatmap como un valor exacto por región.
  • No ajustar los parámetros → manchas confusas o invisibles.
  • Colores mal seleccionados que dificultan la lectura.

Dataset

Quito Morpho-Climatic Events Database (1900–2020)
Zapata, C., Cupueran, M. I., Sevilla, E., Jiménez, E., Espinoza, T., & Taipe, R. (2024).
Quito Morpho-Climatic Events Database 1900–2020 Dataset. Harvard Dataverse.
https://doi.org/10.7910/DVN/7VXOQK

import folium
from folium.plugins import HeatMap ,HeatMapWithTime
import pandas as pd
df = pd.read_csv("Eventos_Morfo_UIO.csv", encoding='latin-1')
df.head()
AÑO MES DIA DPA_PROVIN DPA_DESPRO DPA_CANTON DPA_DESCAN DPA_PARR_1 DPA_DESP_1 longitud ... Descripcio SECTOR / B OBSERVACIO GRAVEDAD Intensidad Notas # Muertos #Heridos #Desaparec #Damnifica
0 1900 3 21 P17 PICHINCHA C1701 QUITO PR170113 ITCHIMBIA -78.50222 ... NaN La Alameda Daños en varias caa, caidas de redes telefonicas 3. molestia en el tráfico, daños ligeros 1 NaN 0 0 0 0
1 1900 5 19 P17 PICHINCHA C1701 QUITO PR170103 CENTRO HISTORICO -78.51142 ... NaN Quito El agua lluvia arraza gran cantidad de materia... 3. molestia en el tráfico, daños ligeros 1 NaN 0 0 0 0
2 1902 3 19 P17 PICHINCHA C1701 QUITO PR170103 CENTRO HISTORICO -78.51588 ... NaN Casco Colonia los meses de enero a mayo en que hay lluvias f... 3. molestia en el tráfico, daños ligeros 1 NaN 0 0 0 0
3 1904 4 4 P17 PICHINCHA C1701 QUITO PR170103 CENTRO HISTORICO -78.50526 ... lluvia y desaseo de vecinos La Marin la carrera León intersección con la Oriental s... 2. daños materiales comentados por el periodis... 2 NaN 0 0 0 0
4 1904 4 15 P17 PICHINCHA C1701 QUITO PR170125 PUENGASI -78.49193 ... NaN s. Chiryacu - Luluncoto El carretero antiguio a los Chillos( act. Ana ... 3. molestia en el tráfico, daños ligeros 1 NaN 0 0 0 0

5 rows × 30 columns

df.columns
Index(['AÑO', 'MES', 'DIA', 'DPA_PROVIN', 'DPA_DESPRO', 'DPA_CANTON',
       'DPA_DESCAN', 'DPA_PARR_1', 'DPA_DESP_1', 'longitud', 'latitud', 'X',
       'Y', 'No.', 'Codigo en', 'Fuente Sec', 'Fuente Pri', 'EVENTO',
       'SUBCATEGOR', 'CAUSA/DISP', 'Descripcio', 'SECTOR / B', 'OBSERVACIO',
       'GRAVEDAD', 'Intensidad', 'Notas', '# Muertos', '#Heridos',
       '#Desaparec', '#Damnifica'],
      dtype='object')
dfmap=df[["latitud","longitud","Intensidad","GRAVEDAD","EVENTO"]]
dfmap.head()
latitud longitud Intensidad GRAVEDAD EVENTO
0 -0.21665 -78.50222 1 3. molestia en el tráfico, daños ligeros Inundacion
1 -0.22154 -78.51142 1 3. molestia en el tráfico, daños ligeros Inundacion
2 -0.22003 -78.51588 1 3. molestia en el tráfico, daños ligeros Inundacion
3 -0.21969 -78.50526 2 2. daños materiales comentados por el periodis... Hundimiento
4 -0.24189 -78.49193 1 3. molestia en el tráfico, daños ligeros Movimiento en Masa
dfmap.EVENTO.unique()
array(['Inundacion', 'Hundimiento', 'Movimiento en Masa', 'Aluvión'],
      dtype=object)
dfmap=dfmap[dfmap["EVENTO"]=="Inundacion"]
dfmap = dfmap[["latitud","longitud","Intensidad"]]
# Mapa base centrado en Ecuador
m = folium.Map(
    [-0.19899731681836336, -78.4428000494774], # PUNTO INICIAL
    zoom_start=10)


# Añadir capa de calor
HeatMap(
    dfmap,
    radius=15,   # Influencia de cada punto
    blur=15,     # Suavizado
    #max_zoom=12  # Precisión según el nivel de zoom
).add_to(m)

m
Make this Notebook Trusted to load map: File -> Trust Notebook
dfmap=df[df["EVENTO"]=="Inundacion"]
dfmap = dfmap[["latitud","longitud","EVENTO"]]
# puntos exactos en el mapa "latitud","longitud"
for i, row in dfmap.iterrows():
    folium.CircleMarker(
        location=[row['latitud'], row['longitud']],
        radius=2,               # Tamaño del punto
        color='blue',            # Borde del punto
        fill=True,
        fill_opacity=0.8,
        popup=f"Lat:{row['EVENTO']}"  
    ).add_to(m)
                           
m
Make this Notebook Trusted to load map: File -> Trust Notebook

Linea de tiempo

dfmap=df[df["EVENTO"]=="Inundacion"]
dfmap = dfmap[["latitud","longitud","EVENTO",'AÑO', 'MES', 'DIA']]

dfmap = dfmap.rename(columns=lambda x: x.strip())

# Crear la columna FECHA como texto: 'YYYY-MM-DD'
dfmap['FECHA'] = (
    dfmap['AÑO'].astype(int).astype(str) + '-' +
    dfmap['MES'].astype(int).astype(str).str.zfill(2) + '-' +
    dfmap['DIA'].astype(int).astype(str).str.zfill(2)
)
dfmap = dfmap.dropna(subset=['FECHA'])
dfmap.head()
latitud longitud EVENTO AÑO MES DIA FECHA
0 -0.21665 -78.50222 Inundacion 1900 3 21 1900-03-21
1 -0.22154 -78.51142 Inundacion 1900 5 19 1900-05-19
2 -0.22003 -78.51588 Inundacion 1902 3 19 1902-03-19
9 -0.22329 -78.51295 Inundacion 1909 3 7 1909-03-07
12 -0.22048 -78.51566 Inundacion 1911 1 26 1911-01-26
# Lista de fechas
dfmap['FECHA'] = pd.to_datetime(dfmap['FECHA'], errors='coerce')

# 3️⃣ Eliminar fechas inválidas
dfmap = dfmap.dropna(subset=['FECHA'])

# 4️⃣ Ahora sí, crear time_index correctamente
time_index = sorted(dfmap['FECHA'].dt.strftime('%Y-%m-%d').unique())

# Lista 
data = []

for fecha in time_index:
    sub = dfmap[dfmap['FECHA'] == fecha]
    puntos = sub[['latitud', 'longitud']].values.tolist()
    data.append(puntos)
# puntos exactos en el mapa "latitud","longitud"
# Mapa base centrado en Ecuador
m = folium.Map(
    [-0.19899731681836336, -78.4428000494774], # PUNTO INICIAL
    zoom_start=10)
    
HeatMapWithTime(
    data=data,
    index=time_index,
    auto_play=True,
    radius=20,
    max_opacity=0.6
).add_to(m)

m
Make this Notebook Trusted to load map: File -> Trust Notebook