Módulo 2 — Clase 6: Mapas interactivos para política pública con Folium#
Curso: Python y Políticas Públicas
Nivel: Avanzado
Duración estimada: 2 horas
Objetivos de la clase#
Entender las diferencias entre visualización geoespacial estática (matplotlib/geopandas) e interactiva (folium)
Crear mapas de Argentina con marcadores, coropléticos y capas de calor
Combinar múltiples capas en un panel de política pública interactivo
Exportar mapas como HTML para reportes y dashboards
1. Introducción a Folium#
¿Por qué Folium?#
En las clases anteriores usamos matplotlib y geopandas para visualizar datos geoespaciales. Estas herramientas producen mapas estáticos — imágenes PNG o SVG que se incrustan en reportes pero no permiten exploración interactiva.
Folium es una librería Python que genera mapas interactivos basados en Leaflet.js. Los mapas resultantes son archivos HTML que funcionan en cualquier navegador sin dependencias adicionales.
Característica |
matplotlib / geopandas |
folium |
|---|---|---|
Tipo de salida |
Imagen estática (PNG/SVG) |
HTML interactivo |
Zoom / pan |
No |
Sí |
Popups en marcadores |
No |
Sí |
Capas toggle |
No |
Sí |
Tiles de mapa base |
No (sin fondo) |
Sí (OpenStreetMap, CartoDB, etc.) |
Ideal para |
Publicaciones, informes impresos |
Dashboards, reportes web, presentaciones |
¿Cuándo usar cada uno en política pública?#
Publicación académica o informe oficial impreso → matplotlib/geopandas
Dashboard para funcionarios, mapa en sitio web → folium
Análisis exploratorio propio → cualquiera, dependiendo de la comodidad
Versión y compatibilidad#
Esta clase usa folium 0.20.0. La API cambió en versiones recientes: algunas funciones que encontrarán en tutoriales viejos (pre-0.15) ya no funcionan igual.
import folium
import folium.plugins
import pandas as pd
import numpy as np
import requests
import json
import warnings
warnings.filterwarnings('ignore')
print(f'Folium versión: {folium.__version__}')
Folium versión: 0.20.0
import matplotlib as mpl
import matplotlib.pyplot as plt
# --- Paleta de identidad del curso ---
C = ['#2A6496', '#E07B3F', '#3D9970', '#8E5EA2', '#C0A830', '#637A8A']
mpl.rcParams.update({
'figure.figsize' : (10, 5),
'font.size' : 11,
'axes.titlesize' : 12,
'axes.titleweight' : 'normal',
'axes.spines.top' : False,
'axes.spines.right' : False,
'legend.frameon' : False,
'axes.prop_cycle' : mpl.cycler(color=C),
'figure.dpi' : 110,
})
def tit(ax, t, **kw):
"""Título sin negrita, alineado a la izquierda."""
ax.set_title(t, loc='left', fontweight='normal', **kw)
2. Mapa base de Argentina#
Todo mapa folium empieza con folium.Map(). Los parámetros clave son:
location: coordenadas del centro[lat, lon]zoom_start: nivel de zoom inicial (1=mundo, 5=país, 10=ciudad)tiles: proveedor de tiles del mapa base
# Mapa base centrado en Argentina continental
m_base = folium.Map(
location=[-38.4, -63.6],
zoom_start=4,
tiles='CartoDB positron' # estilo limpio, ideal para datos superpuestos
)
# Mostrar en el notebook (renderiza el HTML inline)
m_base
# Tiles disponibles en folium sin token adicional:
tiles_disponibles = [
'OpenStreetMap', # Clásico OSM
'CartoDB positron', # Gris claro, minimalista
'CartoDB dark_matter' # Fondo oscuro
]
print('Tiles utilizables directamente (sin API key):')
for t in tiles_disponibles:
print(f' - {t}')
print('\nPara tiles con API key (Mapbox, Google Maps), se usa el parámetro tiles con URL template.')
Tiles utilizables directamente (sin API key):
- OpenStreetMap
- CartoDB positron
- CartoDB dark_matter
Para tiles con API key (Mapbox, Google Maps), se usa el parámetro tiles con URL template.
3. Marcadores: capitales provinciales por región#
Vamos a agregar las 24 capitales provinciales como marcadores circulares, coloreados según la región geográfica.
# Datos de las 24 provincias (capital, coordenadas, región, población aproximada)
capitales = [
# Provincia, Capital, Lat, Lon, Región, Población (miles)
('CABA', 'Ciudad de Buenos Aires', -34.60, -58.38, 'GBA', 3075),
('Buenos Aires', 'La Plata', -34.92, -57.95, 'Pampeana', 800),
('Córdoba', 'Córdoba', -31.42, -64.18, 'Pampeana', 1600),
('Santa Fe', 'Santa Fe', -31.63, -60.70, 'Pampeana', 550),
('Entre Ríos', 'Paraná', -31.73, -60.53, 'Pampeana', 360),
('Corrientes', 'Corrientes', -27.47, -58.83, 'NEA', 380),
('Misiones', 'Posadas', -27.37, -55.90, 'NEA', 370),
('Formosa', 'Formosa', -26.18, -58.17, 'NEA', 200),
('Chaco', 'Resistencia', -27.45, -58.99, 'NEA', 430),
('Jujuy', 'San Salvador de Jujuy', -24.18, -65.30, 'NOA', 320),
('Salta', 'Salta', -24.79, -65.41, 'NOA', 640),
('Tucumán', 'San Miguel de Tucumán', -26.82, -65.22, 'NOA', 950),
('Santiago del Estero', 'Santiago del Estero', -27.78, -64.27, 'NOA', 280),
('Catamarca', 'San Fernando del Valle', -28.47, -65.78, 'NOA', 180),
('La Rioja', 'La Rioja', -29.41, -66.86, 'NOA', 210),
('San Juan', 'San Juan', -31.54, -68.54, 'Cuyo', 550),
('Mendoza', 'Mendoza', -32.89, -68.84, 'Cuyo', 1100),
('San Luis', 'San Luis', -33.30, -66.34, 'Cuyo', 250),
('La Pampa', 'Santa Rosa', -36.62, -64.29, 'Pampeana', 140),
('Neuquén', 'Neuquén', -38.95, -68.06, 'Patagonia', 390),
('Río Negro', 'Viedma', -40.81, -63.00, 'Patagonia', 65),
('Chubut', 'Rawson', -43.30, -65.10, 'Patagonia', 35),
('Santa Cruz', 'Río Gallegos', -51.62, -69.22, 'Patagonia', 110),
('Tierra del Fuego','Ushuaia', -54.80, -68.30, 'Patagonia', 85),
]
df_capitales = pd.DataFrame(capitales, columns=['provincia', 'capital', 'lat', 'lon', 'region', 'poblacion_miles'])
# Paleta de colores por región
colores_region = {
'GBA': '#E84393', # rosa
'Pampeana': '#3A86FF', # azul
'NOA': '#FF9F43', # naranja
'NEA': '#2ECC71', # verde
'Cuyo': '#9B59B6', # violeta
'Patagonia': '#1ABC9C', # turquesa
}
print(df_capitales[['provincia', 'region', 'poblacion_miles']].to_string())
provincia region poblacion_miles
0 CABA GBA 3075
1 Buenos Aires Pampeana 800
2 Córdoba Pampeana 1600
3 Santa Fe Pampeana 550
4 Entre Ríos Pampeana 360
5 Corrientes NEA 380
6 Misiones NEA 370
7 Formosa NEA 200
8 Chaco NEA 430
9 Jujuy NOA 320
10 Salta NOA 640
11 Tucumán NOA 950
12 Santiago del Estero NOA 280
13 Catamarca NOA 180
14 La Rioja NOA 210
15 San Juan Cuyo 550
16 Mendoza Cuyo 1100
17 San Luis Cuyo 250
18 La Pampa Pampeana 140
19 Neuquén Patagonia 390
20 Río Negro Patagonia 65
21 Chubut Patagonia 35
22 Santa Cruz Patagonia 110
23 Tierra del Fuego Patagonia 85
# Crear mapa con marcadores
m_marcadores = folium.Map(
location=[-38.4, -63.6],
zoom_start=4,
tiles='CartoDB positron'
)
# Agregar leyenda de regiones
leyenda_html = '''
<div style="position:fixed; bottom:30px; left:30px; z-index:1000; background:white;
padding:12px 16px; border-radius:8px; box-shadow:2px 2px 6px rgba(0,0,0,0.3);
font-family:Arial; font-size:13px;">
<b>Regiones geográficas</b><br>
'''
for region, color in colores_region.items():
leyenda_html += f'<span style="color:{color};">●</span> {region}<br>'
leyenda_html += '</div>'
m_marcadores.get_root().html.add_child(folium.Element(leyenda_html))
# Agregar CircleMarker para cada capital
for _, row in df_capitales.iterrows():
color = colores_region[row['region']]
# Radio proporcional a la población (escala visual)
radio = 4 + np.log1p(row['poblacion_miles']) * 1.2
# Contenido del popup
popup_html = f"""
<div style='font-family:Arial; min-width:160px;'>
<b style='font-size:14px;'>{row['provincia']}</b><br>
<span style='color:#555;'>Capital:</span> {row['capital']}<br>
<span style='color:#555;'>Región:</span> <span style='color:{color};'>{row['region']}</span><br>
<span style='color:#555;'>Población:</span> {row['poblacion_miles']:,} mil hab.
</div>
"""
folium.CircleMarker(
location=[row['lat'], row['lon']],
radius=radio,
color=color,
fill=True,
fill_color=color,
fill_opacity=0.75,
weight=1.5,
tooltip=f"{row['capital']} ({row['provincia']})",
popup=folium.Popup(popup_html, max_width=220)
).add_to(m_marcadores)
print('Mapa con marcadores creado. Hacé click en un marcador para ver el popup.')
m_marcadores
Mapa con marcadores creado. Hacé click en un marcador para ver el popup.
4. Mapa coroplético: tasa de pobreza por provincia#
Un mapa coroplético colorea las áreas geográficas según el valor de una variable cuantitativa. Es el tipo de mapa más usado en política social para mostrar indicadores provinciales o municipales.
Necesitamos dos inputs:
Un GeoJSON con los polígonos de las provincias
Un DataFrame con los datos a mapear, con una columna de identificador que coincida con el GeoJSON
# Tasas de pobreza por provincia (datos simulados, en % de personas)
# Basado aproximadamente en datos EPH 2023
datos_pobreza = {
'Buenos Aires': 43.5, 'CABA': 16.4, 'Catamarca': 37.2, 'Chaco': 55.8,
'Chubut': 28.9, 'Córdoba': 35.6, 'Corrientes': 47.3, 'Entre Ríos': 38.4,
'Formosa': 51.2, 'Jujuy': 46.7, 'La Pampa': 26.1, 'La Rioja': 44.8,
'Mendoza': 39.1, 'Misiones': 49.3, 'Neuquén': 32.4, 'Río Negro': 34.7,
'Salta': 50.6, 'San Juan': 41.3, 'San Luis': 36.9,
'Santa Cruz': 24.3, 'Santa Fe': 37.8, 'Santiago del Estero': 52.1,
'Tierra del Fuego': 20.5, 'Tucumán': 48.9
}
df_pobreza = pd.DataFrame(list(datos_pobreza.items()), columns=['provincia', 'tasa_pobreza'])
print(df_pobreza.sort_values('tasa_pobreza', ascending=False).to_string(index=False))
provincia tasa_pobreza
Chaco 55.8
Santiago del Estero 52.1
Formosa 51.2
Salta 50.6
Misiones 49.3
Tucumán 48.9
Corrientes 47.3
Jujuy 46.7
La Rioja 44.8
Buenos Aires 43.5
San Juan 41.3
Mendoza 39.1
Entre Ríos 38.4
Santa Fe 37.8
Catamarca 37.2
San Luis 36.9
Córdoba 35.6
Río Negro 34.7
Neuquén 32.4
Chubut 28.9
La Pampa 26.1
Santa Cruz 24.3
Tierra del Fuego 20.5
CABA 16.4
# Intentar cargar GeoJSON de Argentina desde GitHub
GEOJSON_URL = 'https://raw.githubusercontent.com/codeforamerica/click_that_hood/master/public/data/argentina.geojson'
geojson_data = None
try:
resp = requests.get(GEOJSON_URL, timeout=10)
resp.raise_for_status()
geojson_data = resp.json()
print(f'GeoJSON cargado desde URL. Provincias encontradas: {len(geojson_data["features"])}')
# Mostrar cómo se llama la propiedad de nombre en el GeoJSON
sample = geojson_data['features'][0]['properties']
print(f'Propiedades disponibles en el GeoJSON: {list(sample.keys())}')
except Exception as e:
print(f'No se pudo cargar el GeoJSON desde URL: {e}')
print('Usando GeoJSON de fallback con 5 provincias...')
No se pudo cargar el GeoJSON desde URL: 404 Client Error: Not Found for url: https://raw.githubusercontent.com/codeforamerica/click_that_hood/master/public/data/argentina.geojson
Usando GeoJSON de fallback con 5 provincias...
# GeoJSON de fallback (polígonos simplificados de 5 provincias)
# Se usa si la URL no está disponible
geojson_fallback = {
"type": "FeatureCollection",
"features": [
{"type": "Feature", "properties": {"name": "Buenos Aires"},
"geometry": {"type": "Polygon", "coordinates": [[
[-63.0, -34.0], [-57.0, -34.0], [-57.0, -41.0], [-63.0, -41.0], [-63.0, -34.0]
]]}},
{"type": "Feature", "properties": {"name": "Córdoba"},
"geometry": {"type": "Polygon", "coordinates": [[
[-66.0, -29.0], [-62.0, -29.0], [-62.0, -35.0], [-66.0, -35.0], [-66.0, -29.0]
]]}},
{"type": "Feature", "properties": {"name": "Santa Fe"},
"geometry": {"type": "Polygon", "coordinates": [[
[-62.0, -28.0], [-59.0, -28.0], [-59.0, -34.0], [-62.0, -34.0], [-62.0, -28.0]
]]}},
{"type": "Feature", "properties": {"name": "Chaco"},
"geometry": {"type": "Polygon", "coordinates": [[
[-63.0, -24.0], [-59.0, -24.0], [-59.0, -29.0], [-63.0, -29.0], [-63.0, -24.0]
]]}},
{"type": "Feature", "properties": {"name": "Mendoza"},
"geometry": {"type": "Polygon", "coordinates": [[
[-70.5, -32.0], [-67.5, -32.0], [-67.5, -37.5], [-70.5, -37.5], [-70.5, -32.0]
]]}}
]
}
if geojson_data is None:
geojson_data = geojson_fallback
df_pobreza_mapa = df_pobreza[df_pobreza['provincia'].isin(
[f['properties']['name'] for f in geojson_fallback['features']]
)].copy()
else:
df_pobreza_mapa = df_pobreza.copy()
# Detectar el campo de nombre en el GeoJSON
props_keys = list(geojson_data['features'][0]['properties'].keys())
# Buscar el campo que más probablemente contenga el nombre de provincia
nombre_key = 'name'
for k in props_keys:
if 'name' in k.lower() or 'nombre' in k.lower() or 'provincia' in k.lower():
nombre_key = k
break
print(f'Campo de nombre usado para el join: "{nombre_key}"')
Campo de nombre usado para el join: "name"
# Crear mapa coroplético
m_corop = folium.Map(
location=[-38.4, -63.6],
zoom_start=4,
tiles='CartoDB positron'
)
folium.Choropleth(
geo_data=geojson_data,
name='Tasa de pobreza (%)',
data=df_pobreza_mapa,
columns=['provincia', 'tasa_pobreza'],
key_on=f'feature.properties.{nombre_key}',
fill_color='YlOrRd', # Amarillo -> Naranja -> Rojo (más pobreza = más oscuro)
fill_opacity=0.75,
line_opacity=0.3,
legend_name='Tasa de pobreza (% de personas, EPH 2023)',
nan_fill_color='lightgray',
nan_fill_opacity=0.4,
highlight=True,
).add_to(m_corop)
# Tooltips sobre los polígonos
folium.GeoJson(
geojson_data,
style_function=lambda x: {'fillOpacity': 0, 'weight': 0},
tooltip=folium.GeoJsonTooltip(
fields=[nombre_key],
aliases=['Provincia:'],
style='font-family: Arial; font-size: 13px;'
)
).add_to(m_corop)
folium.LayerControl().add_to(m_corop)
print('Mapa coroplético creado. Las provincias más oscuras tienen mayor tasa de pobreza.')
m_corop
Mapa coroplético creado. Las provincias más oscuras tienen mayor tasa de pobreza.
5. Mapa de calor: concentración de beneficiarios#
El mapa de calor (HeatMap) muestra la densidad de puntos en el espacio. Es útil para visualizar dónde se concentran los beneficiarios de un programa, oficinas de atención, incidentes de pobreza extrema, etc.
En este ejemplo, simulamos 500 beneficiarios de un programa social en el área metropolitana de Buenos Aires.
np.random.seed(99)
# Simular beneficiarios en el AMBA
# Dos núcleos de concentración: zona sur (La Matanza, Lomas) y zona oeste (Moreno, Merlo)
n_benef = 500
# Núcleo 1: conurbano sur (60% de los beneficiarios)
n1 = int(n_benef * 0.60)
lat1 = np.random.normal(-34.75, 0.18, n1)
lon1 = np.random.normal(-58.45, 0.25, n1)
# Núcleo 2: conurbano oeste (40%)
n2 = n_benef - n1
lat2 = np.random.normal(-34.58, 0.15, n2)
lon2 = np.random.normal(-58.70, 0.20, n2)
lats = np.concatenate([lat1, lat2])
lons = np.concatenate([lon1, lon2])
df_benef = pd.DataFrame({'lat': lats, 'lon': lons})
print(f'Beneficiarios simulados: {len(df_benef):,}')
print(df_benef.describe().round(3))
Beneficiarios simulados: 500
lat lon
count 500.000 500.000
mean -34.667 -58.545
std 0.193 0.258
min -35.304 -59.444
25% -34.796 -58.725
50% -34.654 -58.556
75% -34.533 -58.381
max -34.107 -57.813
# Crear mapa de calor
m_calor = folium.Map(
location=[-34.62, -58.55],
zoom_start=10,
tiles='CartoDB dark_matter' # Fondo oscuro resalta mejor el heatmap
)
# HeatMap espera una lista de [lat, lon] o [lat, lon, weight]
heat_data = df_benef[['lat', 'lon']].values.tolist()
folium.plugins.HeatMap(
heat_data,
name='Densidad de beneficiarios',
min_opacity=0.3,
radius=18,
blur=12,
gradient={0.2: 'blue', 0.5: 'lime', 0.8: 'yellow', 1.0: 'red'}
).add_to(m_calor)
# Añadir título
titulo_html = '''
<div style="position:fixed; top:15px; left:50%; transform:translateX(-50%);
z-index:1000; background:rgba(0,0,0,0.7); color:white;
padding:8px 16px; border-radius:6px; font-family:Arial; font-size:14px;">
<b>Concentración de beneficiarios — Programa Social AMBA</b>
</div>
'''
m_calor.get_root().html.add_child(folium.Element(titulo_html))
folium.LayerControl().add_to(m_calor)
print('Mapa de calor creado. Zonas más brillantes = mayor concentración de beneficiarios.')
m_calor
Mapa de calor creado. Zonas más brillantes = mayor concentración de beneficiarios.
6. Guardar mapas como HTML#
# Guardar cada mapa como archivo HTML independiente
m_marcadores.save('mapa_capitales.html')
m_corop.save('mapa_coropletico_pobreza.html')
m_calor.save('mapa_calor_beneficiarios.html')
print('Mapas guardados:')
print(' → mapa_capitales.html')
print(' → mapa_coropletico_pobreza.html')
print(' → mapa_calor_beneficiarios.html')
print()
print('Estos archivos HTML son autocontenidos: incluyen todo el JavaScript de Leaflet.')
print('Se pueden compartir por email o subir a un servidor web sin dependencias adicionales.')
Mapas guardados:
→ mapa_capitales.html
→ mapa_coropletico_pobreza.html
→ mapa_calor_beneficiarios.html
Estos archivos HTML son autocontenidos: incluyen todo el JavaScript de Leaflet.
Se pueden compartir por email o subir a un servidor web sin dependencias adicionales.
7. Panel de política pública: mapa con múltiples capas#
En la práctica, los dashboards de política pública requieren mostrar múltiples capas de información que el usuario pueda activar o desactivar según sus necesidades. Folium permite hacer esto con FeatureGroup y LayerControl.
# Mapa base del panel
m_panel = folium.Map(
location=[-36.0, -63.6],
zoom_start=4,
tiles='CartoDB positron'
)
# =====================================================
# CAPA 1: Coroplético de pobreza
# =====================================================
capa_corop = folium.FeatureGroup(name='Tasa de pobreza provincial (%)', show=True)
folium.Choropleth(
geo_data=geojson_data,
data=df_pobreza_mapa,
columns=['provincia', 'tasa_pobreza'],
key_on=f'feature.properties.{nombre_key}',
fill_color='YlOrRd',
fill_opacity=0.65,
line_opacity=0.3,
legend_name='Tasa de pobreza (%)',
nan_fill_color='lightgray',
highlight=True,
name='_corop_interno' # nombre interno para la leyenda de folium
).add_to(m_panel) # El Choropleth va directo al mapa para que la leyenda aparezca
# =====================================================
# CAPA 2: Marcadores de capitales
# =====================================================
capa_marcadores = folium.FeatureGroup(name='Capitales provinciales', show=True)
for _, row in df_capitales.iterrows():
color = colores_region[row['region']]
radio = 4 + np.log1p(row['poblacion_miles']) * 1.0
# Buscar tasa de pobreza para esta provincia
match = df_pobreza[df_pobreza['provincia'] == row['provincia']]
tasa_str = f"{match['tasa_pobreza'].values[0]:.1f}%" if len(match) > 0 else 'N/D'
popup_html = f"""
<div style='font-family:Arial; min-width:180px;'>
<b style='font-size:14px;'>{row['provincia']}</b><br>
<span style='color:#555;'>Capital:</span> {row['capital']}<br>
<span style='color:#555;'>Región:</span> <span style='color:{color}; font-weight:bold;'>{row['region']}</span><br>
<span style='color:#555;'>Población capital:</span> {row['poblacion_miles']:,} mil<br>
<span style='color:#555;'>Tasa de pobreza:</span> <span style='color:#e74c3c; font-weight:bold;'>{tasa_str}</span>
</div>
"""
folium.CircleMarker(
location=[row['lat'], row['lon']],
radius=radio,
color=color,
fill=True,
fill_color=color,
fill_opacity=0.8,
weight=1.5,
tooltip=f"{row['capital']} — Pobreza: {tasa_str}",
popup=folium.Popup(popup_html, max_width=230)
).add_to(capa_marcadores)
capa_marcadores.add_to(m_panel)
# =====================================================
# CAPA 3: Mapa de calor AMBA
# =====================================================
capa_calor = folium.FeatureGroup(name='Beneficiarios AMBA (densidad)', show=False)
folium.plugins.HeatMap(
heat_data,
min_opacity=0.35,
radius=16,
blur=10,
gradient={0.2: '#ffffb2', 0.4: '#fecc5c', 0.6: '#fd8d3c', 0.8: '#f03b20', 1.0: '#bd0026'}
).add_to(capa_calor)
capa_calor.add_to(m_panel)
# =====================================================
# Control de capas + título del panel
# =====================================================
folium.LayerControl(collapsed=False, position='topright').add_to(m_panel)
titulo_panel = '''
<div style="position:fixed; top:12px; left:55px; z-index:1000;
background:white; padding:10px 16px; border-radius:8px;
box-shadow:2px 2px 8px rgba(0,0,0,0.25); font-family:Arial;">
<b style='font-size:15px; color:#2c3e50;'>Panel de Política Social — Argentina</b><br>
<span style='font-size:11px; color:#777;'>Fuente: EPH-INDEC 2023 (datos ilustrativos)</span>
</div>
'''
m_panel.get_root().html.add_child(folium.Element(titulo_panel))
# Guardar
m_panel.save('mapa_interactivo.html')
print('Panel guardado como mapa_interactivo.html')
print('Usá el control de capas (arriba a la derecha) para activar/desactivar cada capa.')
m_panel
Panel guardado como mapa_interactivo.html
Usá el control de capas (arriba a la derecha) para activar/desactivar cada capa.
Resumen de las capas del panel#
Capa |
Tipo |
Variable |
Visible por defecto |
|---|---|---|---|
Tasa de pobreza |
Coroplético |
% personas pobres por provincia |
Sí |
Capitales provinciales |
CircleMarker |
Población, región, tasa de pobreza |
Sí |
Beneficiarios AMBA |
HeatMap |
Densidad de puntos |
No |
Nota de diseño: En dashboards para funcionarios, es recomendable comenzar con pocas capas visibles para no sobrecargar la visualización. El usuario activa las que necesita.
Ejercicios#
Ejercicio 1 — Mapa de oficinas de atención ciudadana#
Simulá y mapeá las oficinas de atención de un programa social ficticio en la Provincia de Buenos Aires.
a) Creá un DataFrame con al menos 30 oficinas. Para cada una, generá aleatoriamente:
Nombre del municipio (usá una lista de 10 municipios bonaerenses)
Coordenadas dentro de la provincia (lat entre -34 y -41, lon entre -57 y -63)
Horario de atención (mañana / tarde / completo)
Cantidad de trámites mensuales (número entre 50 y 800)
b) Mapeá las oficinas con CircleMarker:
Color según horario de atención
Radio proporcional a la cantidad de trámites
Popup con todos los datos
c) Identificá visualmente los municipios con mayor demanda y proponé una ubicación para una nueva oficina.
# Tu código aquí
Ejercicio 2 — Coroplético comparativo#
a) Tomá el DataFrame df_pobreza y agregá dos columnas nuevas:
desempleo: tasa de desempleo simulada por provincia (entre 5% y 25%)informalidad: tasa de empleo informal simulada (entre 30% y 70%)
b) Creá un mapa con tres capas coropléticas (una por indicador), cada una con su propio folium.Choropleth. Las tres deben ser accesibles desde el LayerControl, pero solo una visible por defecto.
c) ¿Qué ventajas y limitaciones tiene mostrar múltiples indicadores en un mismo panel coroplético?
# Pista: cada Choropleth es una FeatureGroup separada
# Tu código aquí
Ejercicio 3 — Análisis de cobertura territorial#
Un programa de primera infancia tiene 200 centros de atención distribuidos en Argentina. Querés analizar si hay zonas con baja cobertura.
a) Generá aleatoriamente las coordenadas de los 200 centros con distribución no uniforme: el 70% dentro de la región Pampeana y GBA, el 30% restante distribuido en el resto del país. (Pista: generá los dos grupos por separado con np.random.normal con diferentes parámetros y concatenalos.)
b) Creá un mapa que combine:
HeatMap de los centros (muestra la densidad actual)
Marcadores de las capitales provinciales (para referencia geográfica)
c) Identificá visualmente qué región o provincias tienen menor cobertura relativa a su superficie. ¿Qué datos adicionales necesitarías para cuantificar el déficit de cobertura por habitante?
# Tu código aquí