Clase 9 — Datos geoespaciales#

Python y Políticas Públicas


Contenidos#

  1. ¿Qué son los datos geoespaciales?

  2. Instalación y primeros pasos con geopandas

  3. Leer y explorar un shapefile

  4. Mapas coropléticos

  5. Joins espaciales

  6. Datos geoespaciales del sector público argentino

  7. Ejercicios


1. ¿Qué son los datos geoespaciales?#

Los datos geoespaciales combinan información tabular (como en pandas) con geometrías que describen la forma y ubicación de objetos en el espacio.

Tipos de geometría:

  • Point: una ubicación (ej: una escuela, un hospital, un sensor)

  • LineString: una línea (ej: un camino, un río)

  • Polygon: un área (ej: una provincia, un municipio, un radio censal)

Formatos comunes:

  • Shapefile (.shp): formato clásico de ESRI, muy común en datos del INDEC y organismos públicos.

  • GeoJSON (.geojson): formato moderno basado en JSON, usado en APIs y web.

  • GeoPackage (.gpkg): alternativa moderna al shapefile.

¿Por qué es relevante para políticas públicas?

  • Distribución territorial de programas sociales

  • Acceso a servicios públicos (escuelas, hospitales, agua)

  • Análisis de cobertura y brechas regionales

  • Visualización de indicadores del Censo


2. Instalación y primeros pasos#

# Instalar geopandas (recomendado vía conda)
conda install geopandas

# O con pip
pip install geopandas

geopandas extiende pandas con soporte geoespacial. Un GeoDataFrame es como un DataFrame pero con una columna especial de geometría (geometry).

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors

try:
    import geopandas as gpd
    from shapely.geometry import Point, Polygon
    GEOPANDAS_OK = True
    print(f"geopandas {gpd.__version__} disponible")
except ImportError:
    GEOPANDAS_OK = False
    print("geopandas no instalado. Ejecutá: conda install geopandas")
    print("El notebook puede ejecutarse igualmente para ver los conceptos.")

%matplotlib inline
geopandas 1.1.3 disponible
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)

3. Leer y explorar un shapefile#

El INDEC publica shapefiles del país, provincias y departamentos en: https://www.indec.gob.ar/indec/web/Institucional-Indec-Codgeo

También están disponibles en datos.gob.ar.

if GEOPANDAS_OK:
    # Leer shapefile de provincias argentinas
    # Descargar de: https://www.indec.gob.ar/ftp/cuadros/territorio/codgeo/Codgeo_Pais_x_prov_con_datos.zip
    # gdf = gpd.read_file("provincias/provincia.shp")
    
    # Alternativa: usar el dataset incorporado de geopandas (países del mundo)
    world = gpd.read_file('https://raw.githubusercontent.com/nvkelso/natural-earth-vector/master/geojson/ne_110m_admin_0_countries.geojson')
    sudamerica = world[world['CONTINENT'] == 'South America']
    
    print(type(sudamerica))
    print(sudamerica.dtypes)
    print()
    sudamerica.head()
else:
    print("Instalar geopandas para ejecutar esta celda")
<class 'geopandas.geodataframe.GeoDataFrame'>
featurecla      object
scalerank        int32
LABELRANK        int32
SOVEREIGNT      object
SOV_A3          object
                ...   
FCLASS_NL       object
FCLASS_SE       object
FCLASS_BD       object
FCLASS_UA       object
geometry      geometry
Length: 169, dtype: object
if GEOPANDAS_OK:
    # La columna 'geometry' contiene los polígonos
    print("Tipo de geometría:", sudamerica.geometry.geom_type.unique())
    print("Sistema de coordenadas (CRS):", sudamerica.crs)
    print("Bounding box de Argentina:",
          sudamerica[sudamerica['NAME'] == 'Argentina'].geometry.bounds.values)
    
    # Graficar
    fig, ax = plt.subplots(figsize=(7, 9))
    sudamerica.plot(ax=ax, color='lightgray', edgecolor='white', linewidth=0.5)
    sudamerica[sudamerica['NAME'] == 'Argentina'].plot(ax=ax, color='steelblue', edgecolor='white')
    ax.set_title("América del Sur", fontsize=13, loc='left')
    ax.axis('off')
    plt.tight_layout()
    plt.show()
else:
    print("Instalar geopandas para ejecutar esta celda")
Tipo de geometría: ['MultiPolygon' 'Polygon']
Sistema de coordenadas (CRS): EPSG:4326
Bounding box de Argentina: [[-73.415436 -55.25     -53.628349 -21.83231 ]]
_images/9f284ba6d145985b76a098e90365f0e8e4b98b2729b0dbaacd5a7047d9f521da.png

4. Mapas coropléticos#

Un mapa coroplético colorea cada unidad geográfica según el valor de una variable. Es ideal para mostrar distribuciones territoriales de indicadores.

Para Argentina, el flujo es:

  1. Leer el shapefile de provincias del INDEC.

  2. Tener un DataFrame con indicadores por provincia.

  3. Hacer un merge.

  4. Graficar con gdf.plot(column='variable').

if GEOPANDAS_OK:
    # Ejemplo con datos del mundo (PIB per cápita de Sudamérica)
    fig, axes = plt.subplots(1, 2, figsize=(14, 8))
    
    # Mapa 1: PIB per cápita
    sudamerica.plot(
        column='GDP_MD',
        ax=axes[0],
        cmap='YlOrRd',
        legend=True,
        legend_kwds={'label': 'PIB (millones USD)', 'shrink': 0.6},
        edgecolor='white',
        linewidth=0.5,
        missing_kwds={'color': 'lightgray'}
    )
    axes[0].set_title("PIB estimado\npor país (USD millones)", fontsize=12)
    axes[0].axis('off')
    
    # Mapa 2: Población
    sudamerica.plot(
        column='POP_EST',
        ax=axes[1],
        cmap='Blues',
        legend=True,
        legend_kwds={'label': 'Población estimada', 'shrink': 0.6},
        edgecolor='white',
        linewidth=0.5,
    )
    axes[1].set_title("Población estimada\npor país", fontsize=12, loc='left')
    axes[1].axis('off')
    
    plt.suptitle("Indicadores de América del Sur", fontsize=14, y=1.01)
    plt.tight_layout()
    plt.show()
else:
    print("Instalar geopandas para ejecutar esta celda")
_images/2a606af97f0a4d20530df39aae47b31cf2d47bd56e76277fb108fc30907caab5.png

Mapa coroplético de provincias argentinas#

El flujo completo para datos de Argentina:

if GEOPANDAS_OK:
    # Paso 1: Cargar el shapefile de provincias
    # DESCARGAR DE: https://www.indec.gob.ar/ftp/cuadros/territorio/codgeo/Codgeo_Pais_x_prov_con_datos.zip
    # gdf_prov = gpd.read_file("provincias/provincia.shp")
    # gdf_prov = gdf_prov.rename(columns={'nam': 'provincia'})
    
    # Paso 2: Datos de indicadores por provincia
    np.random.seed(42)
    indicadores = pd.DataFrame({
        'provincia': [
            'Ciudad Autónoma de Buenos Aires', 'Buenos Aires', 'Catamarca', 'Chaco',
            'Chubut', 'Córdoba', 'Corrientes', 'Entre Ríos', 'Formosa', 'Jujuy',
            'La Pampa', 'La Rioja', 'Mendoza', 'Misiones', 'Neuquén',
            'Río Negro', 'Salta', 'San Juan', 'San Luis', 'Santa Cruz',
            'Santa Fe', 'Santiago del Estero', 'Tierra del Fuego', 'Tucumán'
        ],
        'pobreza_2023': [18.5, 40.1, 38.2, 55.6, 22.1, 35.4, 46.8, 38.7, 58.2, 45.3,
                         28.4, 40.1, 32.8, 44.1, 20.3, 25.7, 49.3, 36.2, 30.1, 19.8,
                         37.1, 52.4, 17.2, 45.2]
    })
    
    # Paso 3: Merge
    # gdf_mapa = gdf_prov.merge(indicadores, on='provincia', how='left')
    
    # Paso 4: Graficar
    # fig, ax = plt.subplots(figsize=(8, 10))
    # gdf_mapa.plot(column='pobreza_2023', ax=ax, cmap='RdYlGn_r', legend=True,
    #               legend_kwds={'label': 'Tasa de pobreza (%)'}, edgecolor='white')
    # ax.set_title("Tasa de pobreza por provincia, 2023", fontsize=13, loc='left')
    # ax.axis('off')
    # plt.tight_layout()
    # plt.show()
    
    print("Datos de indicadores preparados para el merge:")
    print(indicadores)
    print("\nPasos comentados → descomentarlos una vez descargado el shapefile del INDEC")
else:
    print("Instalar geopandas para ejecutar esta celda")
Datos de indicadores preparados para el merge:
                          provincia  pobreza_2023
0   Ciudad Autónoma de Buenos Aires          18.5
1                      Buenos Aires          40.1
2                         Catamarca          38.2
3                             Chaco          55.6
4                            Chubut          22.1
5                           Córdoba          35.4
6                        Corrientes          46.8
7                        Entre Ríos          38.7
8                           Formosa          58.2
9                             Jujuy          45.3
10                         La Pampa          28.4
11                         La Rioja          40.1
12                          Mendoza          32.8
13                         Misiones          44.1
14                          Neuquén          20.3
15                        Río Negro          25.7
16                            Salta          49.3
17                         San Juan          36.2
18                         San Luis          30.1
19                       Santa Cruz          19.8
20                         Santa Fe          37.1
21              Santiago del Estero          52.4
22                 Tierra del Fuego          17.2
23                          Tucumán          45.2

Pasos comentados → descomentarlos una vez descargado el shapefile del INDEC

5. Mapas de puntos#

Útil para mostrar la distribución de servicios públicos (escuelas, hospitales, comisarías).

if GEOPANDAS_OK:
    # Crear GeoDataFrame de puntos (hospitales simulados en Argentina)
    np.random.seed(7)
    n_hospitales = 50

    # Coordenadas aproximadas del territorio argentino
    lons = np.random.uniform(-73, -53, n_hospitales)
    lats = np.random.uniform(-55, -22, n_hospitales)

    hospitales = gpd.GeoDataFrame(
        {
            'nombre': [f"Hospital {i}" for i in range(n_hospitales)],
            'nivel': np.random.choice(['I', 'II', 'III'], n_hospitales),
            'camas': np.random.randint(20, 500, n_hospitales),
        },
        geometry=[Point(lon, lat) for lon, lat in zip(lons, lats)],
        crs='EPSG:4326'
    )

    fig, ax = plt.subplots(figsize=(7, 9))

    world = gpd.read_file('https://raw.githubusercontent.com/nvkelso/natural-earth-vector/master/geojson/ne_110m_admin_0_countries.geojson')
    arg = world[world['NAME'] == 'Argentina']
    arg.plot(ax=ax, color='#f0f0f0', edgecolor='gray', linewidth=0.5)

    colores_nivel = {'I': '#2ecc71', 'II': '#e67e22', 'III': '#e74c3c'}
    for nivel, grupo in hospitales.groupby('nivel'):
        grupo.plot(ax=ax, color=colores_nivel[nivel], markersize=grupo['camas'] / 20,
                   alpha=0.7, label=f'Nivel {nivel}')

    ax.set_title("Distribución de hospitales por nivel\n(tamaño ∝ número de camas)",
                 fontsize=12)
    ax.legend(title='Nivel', frameon=False)
    ax.axis('off')
    plt.tight_layout()
    plt.show()
else:
    print("Instalar geopandas para ejecutar esta celda")
_images/da79807bab27a3c157a62101d052038f1b2ac6e35354cacb4d3eef7e65066962.png

6. Fuentes de datos geoespaciales del sector público argentino#

Fuente

Contenido

URL

INDEC

Shapefiles de provincias, departamentos, radios censales

indec.gob.ar/codgeo

IGN (Instituto Geográfico Nacional)

Cartografía oficial de Argentina

ign.gob.ar

datos.gob.ar

Capas georreferenciadas de servicios públicos

datos.gob.ar

MINEDU

Ubicación de establecimientos educativos

datos.gob.ar

MINSAL

Red de efectores de salud

datos.gob.ar

OSM (OpenStreetMap)

Datos geográficos abiertos

openstreetmap.org

Cómo descargar el shapefile de provincias del INDEC:#

import urllib.request
import zipfile

url = "https://www.indec.gob.ar/ftp/cuadros/territorio/codgeo/Codgeo_Pais_x_prov_con_datos.zip"
urllib.request.urlretrieve(url, "provincias.zip")

with zipfile.ZipFile("provincias.zip", 'r') as z:
    z.extractall("shapefiles/")

gdf = gpd.read_file("shapefiles/provincia.shp")

7. Ejercicios#

Ejercicio 1#

Si tenés geopandas instalado:

  1. Descargá el shapefile de provincias del INDEC.

  2. Hacé un merge con el DataFrame indicadores definido en esta clase.

  3. Generá un mapa coroplético de la tasa de pobreza, con una paleta de colores divergente (verde = menor pobreza, rojo = mayor pobreza).

  4. Agregá el nombre de las provincias con gdf.apply(lambda x: ax.annotate(...)).

Ejercicio 2 (sin geopandas)#

Usando solo pandas y matplotlib, creá un mapa de calor por región (heatmap) que muestre la tasa de pobreza promedio de las provincias del NOA, NEA, Cuyo, Patagonia y Pampeana para los años 2021, 2022 y 2023. Usá el dataset de la Clase 7.

# Ejercicio 2: heatmap sin geopandas
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

np.random.seed(42)
provincias_sample = ['CABA', 'Buenos Aires', 'Córdoba', 'Santa Fe', 'Mendoza',
                     'Tucumán', 'Salta', 'Chaco', 'Misiones', 'Corrientes',
                     'Neuquén', 'Río Negro', 'Chubut']
regiones = ['AMBA', 'Pampeana', 'Pampeana', 'Pampeana', 'Cuyo',
            'NOA', 'NOA', 'NEA', 'NEA', 'NEA',
            'Patagonia', 'Patagonia', 'Patagonia']

df_ej = pd.DataFrame({
    'provincia': provincias_sample,
    'region': regiones,
    'pobreza_2021': np.random.uniform(15, 62, len(provincias_sample)).round(1),
    'pobreza_2022': np.random.uniform(14, 60, len(provincias_sample)).round(1),
    'pobreza_2023': np.random.uniform(13, 58, len(provincias_sample)).round(1),
})

# Tu solución aquí