✨ Introducción

En el marco del 50 aniversario de la Licenciatura en Planeación Territorial, este taller celebra la evolución de las herramientas técnicas y conceptuales que han acompañado el quehacer del planeador territorial.

Desde los croquis a mano y la cartografía analógica de los primeros años, hasta la actual integración de los Sistemas de Información Geográfica (SIG), la Ciencia de Datos Espaciales y las Ciencias de Información Geoespacial (CIG), este taller forma parte de las actividades que conmemoran cómo la disciplina ha transitado hacia nuevas formas de análisis y representación del territorio.

El propósito es mostrar de manera práctica cómo R permite automatizar el cálculo y la visualización de indicadores territoriales, optimizando procesos que tradicionalmente se realizaban manualmente o en software SIG de escritorio como ArcGIS o QGIS.

Durante el taller, nos guiaremos por el libro Indicadores para la caracterización y el ordenamiento territorial del Instituto de Geografía - UNAM, específicamente el apartado de Indicadores del subsistema social y urbano-regional.


🎯 Objetivo general

Mostrar cómo el uso de R y sus librerías geoespaciales permite automatizar la generación de indicadores territoriales y mapas temáticos, fortaleciendo la toma de decisiones en la planeación urbana y regional.


🎯 Objetivos específicos

  • Aplicar herramientas contemporáneas de análisis espacial para el estudio de procesos de urbanización y sistemas urbano-regionales.
  • Comprender la utilidad de los indicadores territoriales (sociales, económicos, urbanos y ambientales) en la planeación y gestión del territorio.
  • Presentar las principales librerías de R para el análisis geoespacial y la visualización de datos.
  • Demostrar la automatización del cálculo de indicadores demográficos y urbanos.
  • Visualizar los resultados mediante mapas interactivos y gráficos reproducibles.
  • Comparar la eficiencia del flujo de trabajo en R frente a los procesos tradicionales en SIG.

🧠 Justificación

El contexto contemporáneo de la planeación demanda profesionales capaces de interpretar y procesar grandes volúmenes de información territorial, provenientes de censos, sensores remotos y plataformas digitales.

La geoinformática se ha convertido en un pilar del diagnóstico y la planeación, articulando la estadística espacial, el modelado urbano y la visualización de datos.

Este taller busca ofrecer una experiencia demostrativa sobre cómo la automatización puede transformar el tiempo de análisis en tiempo de reflexión.


💬 Preguntas conductoras

  1. ¿Qué es un SIG?
  2. ¿R es un SIG?
  3. ¿Por qué R no es un SIG, pero sí una plataforma geoespacial?
  4. Si R no es un SIG… ¿cómo puede operar datos espaciales?

🗺️ ¿Qué es un SIG?

Un Sistema de Información Geográfica (SIG) es una herramienta tecnológica que permite:

  • Capturar, almacenar, analizar y visualizar información georreferenciada.
  • Trabajar con capas de información (puntos, líneas, polígonos).
  • Realizar análisis espaciales: proximidad, superposición, buffers, densidades, etc.
  • Producir cartografía temática y apoyar la toma de decisiones territoriales.

🟢 En resumen:
> Un SIG combina base de datos + cartografía + análisis espacial dentro de un entorno visual.


💻 ¿R es un SIG?

“Si en R puedo abrir shapefiles, hacer mapas, calcular áreas y exportar cartografía… ¿entonces R es un SIG?”

Respuesta:
No exactamente.
R no es un SIG en sentido estricto, pero sí puede comportarse como uno gracias a sus librerías geoespaciales.


🧩 ¿Por qué R no es un SIG, pero sí una plataforma geoespacial?

🔹 R no es un SIG porque:
- No tiene interfaz gráfica GIS tradicional (no trabajas arrastrando capas como en ArcGIS o QGIS).
- No administra bases de datos espaciales integradas (aunque puede conectarse a ellas, como PostGIS o GeoPackage).
- No está diseñado exclusivamente para cartografía o geoprocesamiento, sino como un lenguaje estadístico flexible.

💬 En pocas palabras:
> Mientras un SIG tradicional te permite analizar el territorio una vez,
> R te permite automatizar ese análisis y repetirlo mil veces con distintos datos.


🧭 ¿Cómo puede R operar datos espaciales?

👉 Gracias a sus librerías especializadas, que le permiten interpretar coordenadas, geometrías y proyecciones espaciales.

Función Librería Descripción
Lectura y manejo de geometrías sf (simple features) Lee shapefiles, GeoPackage, GeoJSON y maneja puntos, líneas y polígonos.
Análisis y operaciones espaciales sf, terra, rmapshaper Calcula distancias, buffers, uniones espaciales, intersecciones, áreas, centroides, etc.
Estadística espacial spdep, spatialreg, gstat Autocorrelación espacial (Moran, LISA), modelos SAR/SEM, interpolaciones.
Visualización y cartografía tmap, ggplot2, leaflet, plotly Mapas estáticos, temáticos e interactivos.
Conexión y datos externos readr, geojsonio, httr, RPostgres Conecta R con APIs, bases PostGIS o archivos externos.

🧩 Actividad práctica: Preparar el entorno de trabajo

Antes de calcular indicadores o hacer mapas, necesitamos decirle a R dónde vamos a trabajar.
Esto se llama definir el directorio de trabajo, y es como decirle:

“Todos los archivos que use o guarde estarán dentro de esta carpeta.”

Estas y otras funciones se ejecutarán dentro de un chunk.

# Definir la ruta del taller
ruta_trabajo <- "C:/SIG_RO/Taller_R"

# Establecer el directorio de trabajo
setwd(ruta_trabajo)

# Confirmar el cambio
getwd()
## [1] "C:/SIG_RO/Taller_R"

⚡ Nota práctica:

En RStudio, puedes crear un nuevo chunk rápidamente con el atajo de teclado
Ctrl + Alt + I (en Windows) o Cmd + Option + I (en Mac).

🧱 ¿Qué es un data frame (df)?

En R, los data frames son la estructura básica para manejar datos tabulares —es decir, filas y columnas, como en una hoja de cálculo o una tabla de atributos en un SIG.

Cada fila representa una observación (por ejemplo, un municipio) y cada columna representa una variable (por ejemplo, población total, densidad, índice de envejecimiento, etc.).

💡 En resumen:

Un data frame = una tabla donde cada columna puede tener un tipo distinto de dato (números, texto, factores, coordenadas, etc.).

🔄 Datos de entrada (inputs) y múltiples data frames

En un proyecto podemos tener varios data frames a la vez (por ejemplo, uno por censo: iter_2000, iter_2010, iter_2020). A partir de ellos podemos crear nuevos data frames (filtrando, seleccionando variables, uniendo tablas, calculando indicadores). Piensa en R como un taller de ensamble: entran datos de entrada (CSV, shapefile, etc.) y salen tablas nuevas listas para análisis y mapas.

🧮 Cargando los datos del censo

Ahora que ya configuramos el entorno de trabajo, vamos a leer los archivos de los censos que usaremos para construir nuestros indicadores territoriales.

Estos archivos se encuentran en la carpeta censos

1️⃣ Cargar librerías básicas

Estas librerías permiten leer, limpiar y explorar los datos.

library(readr)     # para leer archivos CSV
library(dplyr)     # para manipular datos (filtrar, seleccionar, agrupar)
## 
## Adjuntando el paquete: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
library(janitor)   # para limpiar nombres de columnas fácilmente
## 
## Adjuntando el paquete: 'janitor'
## The following objects are masked from 'package:stats':
## 
##     chisq.test, fisher.test

2️⃣ Leer los archivos de censo

iter_2000 <- read_csv("C:/SIG_RO/Taller_R/Censos/iter_2000.csv")
## Rows: 205681 Columns: 132
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (128): entidad, nom_ent, mun, nom_mun, loc, nom_loc, longitud, pmascul, ...
## dbl   (4): latitud, altitud, pobtot, totvivhab
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
iter_2010 <- read_csv("C:/SIG_RO/Taller_R/Censos/iter_2010.csv")
## Rows: 198488 Columns: 200
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (195): entidad, nom_ent, mun, nom_mun, loc, nom_loc, longitud, altitud, ...
## dbl   (5): latitud, pobtot, vivtot, tvivhab, tam_loc
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
iter_2020 <- read_csv("C:/SIG_RO/Taller_R/Censos/iter_2020.csv")
## Rows: 195662 Columns: 286
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (283): ENTIDAD, NOM_ENT, MUN, NOM_MUN, LOC, NOM_LOC, LONGITUD, LATITUD, ...
## dbl   (3): POBTOT, VIVTOT, TVIVHAB
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

3️⃣ Limpiar los nombres de columnas

A veces los encabezados tienen espacios o mayúsculas inconsistentes. janitor::clean_names() los convierte a formato uniforme (snake_case).

iter_2000 <- clean_names(iter_2000)
iter_2010 <- clean_names(iter_2010)
iter_2020 <- clean_names(iter_2020)
# 4️⃣ Vista rápida de los datos -------------------------------
head(iter_2020)
## # A tibble: 6 × 286
##   entidad nom_ent    mun   nom_mun loc   nom_loc longitud latitud altitud pobtot
##   <chr>   <chr>      <chr> <chr>   <chr> <chr>   <chr>    <chr>   <chr>    <dbl>
## 1 00      Total nac… 000   Total … 0000  Total … <NA>     <NA>    <NA>    1.26e8
## 2 00      Total nac… 000   Total … 9998  Locali… <NA>     <NA>    <NA>    2.50e5
## 3 00      Total nac… 000   Total … 9999  Locali… <NA>     <NA>    <NA>    1.47e5
## 4 01      Aguascali… 000   Total … 0000  Total … <NA>     <NA>    <NA>    1.43e6
## 5 01      Aguascali… 000   Total … 9998  Locali… <NA>     <NA>    <NA>    3.70e3
## 6 01      Aguascali… 000   Total … 9999  Locali… <NA>     <NA>    <NA>    3.02e3
## # ℹ 276 more variables: pobfem <chr>, pobmas <chr>, p_0a2 <chr>, p_0a2_f <chr>,
## #   p_0a2_m <chr>, p_3ymas <chr>, p_3ymas_f <chr>, p_3ymas_m <chr>,
## #   p_5ymas <chr>, p_5ymas_f <chr>, p_5ymas_m <chr>, p_12ymas <chr>,
## #   p_12ymas_f <chr>, p_12ymas_m <chr>, p_15ymas <chr>, p_15ymas_f <chr>,
## #   p_15ymas_m <chr>, p_18ymas <chr>, p_18ymas_f <chr>, p_18ymas_m <chr>,
## #   p_3a5 <chr>, p_3a5_f <chr>, p_3a5_m <chr>, p_6a11 <chr>, p_6a11_f <chr>,
## #   p_6a11_m <chr>, p_8a14 <chr>, p_8a14_f <chr>, p_8a14_m <chr>, …

###🧮 Opción 2: DT::datatable() — tabla interactiva en HTML (¡recomendada!)

Permite ver, buscar, filtrar y ordenar tus datos directamente en tu documento o dashboard.

library(DT)

# Tabla interactiva del iter_2020
DT::datatable(
  head(iter_2020, 100),  # muestra las primeras 100 filas
  options = list(
    pageLength = 10,     # número de filas por página
    scrollX = TRUE       # permite desplazamiento horizontal
  ),
  caption = "Visualización interactiva de los primeros registros del Censo 2020"
)

🧩 Limpieza y selección de los totales municipales

En este paso nos quedamos únicamente con los totales por municipio de cada censo (2000, 2010 y 2020).
Aunque parezca algo sencillo, este proceso tiene su ciencia 🧠, porque los nombres y estructuras del ITER no están completamente homologados entre los distintos años.

Por ejemplo:

  • En el Censo 2000, la columna nom_loc indica el total municipal con el texto “TOTAL MUNICIPAL” (en mayúsculas).
  • En los Censos 2010 y 2020, ese mismo valor aparece como “Total del Municipio” (con mayúsculas y minúsculas).
  • Además, algunas tablas incluyen una columna loc con el valor 0 para representar el total municipal, pero no siempre está presente ni se llama igual.

Esto hace que un mismo dato (“el total del municipio”) esté escrito de tres maneras distintas dependiendo del año.
Por eso, en lugar de aplicar un solo filtro genérico, creamos tres filtros específicos, uno para cada base, asegurando que todos los resultados correspondan efectivamente al total del municipio.

En términos prácticos:

  • Para el año 2000, filtramos las filas donde nom_loc == "TOTAL MUNICIPAL".
  • Para los años 2010 y 2020, usamos nom_loc == "Total del Municipio".
  • En los tres casos, generamos una nueva variable cve_mun, que combina la clave de entidad y municipio (ENTIDAD + MUN) para formar un identificador único de cinco dígitos.
  • Finalmente, agregamos una columna llamada anio para distinguir de qué censo proviene cada observación.

🧭 ¿Por qué es importante este paso?

Porque nos asegura que estamos trabajando con una unidad espacial consistente (el municipio) en los tres censos, lo que permitirá comparar variables y calcular indicadores de manera coherente.
Si no lo hiciéramos, correríamos el riesgo de mezclar datos de localidades, cabeceras o zonas rurales con los totales municipales.

# ------------------------------------------------------------
# TOTALES MUNICIPALES POR AÑO (filtros específicos por nom_loc)
# ------------------------------------------------------------
library(dplyr)
library(janitor)
library(stringr)

# Utilidad pequeña: limpiar nombres y corregir posible BOM en la 1ª columna
prep_names <- function(df){
  df <- janitor::clean_names(df)
  names(df)[1] <- sub("^\ufeff", "", names(df)[1])  # quita BOM si aparece
  df
}

# --- 2000: nom_loc == "TOTAL MUNICIPAL"
mun_2000 <- iter_2000 %>%
  prep_names() %>%
  filter(nom_loc == "TOTAL MUNICIPAL") %>%        # filtro exacto
  mutate(
    entidad = suppressWarnings(as.integer(entidad)),
    mun     = suppressWarnings(as.integer(mun)),
    cve_mun = paste0(sprintf("%02d", entidad), sprintf("%03d", mun)),
    anio    = 2000
  )

# --- 2010: nom_loc == "Total del Municipio"
mun_2010 <- iter_2010 %>%
  prep_names() %>%
  filter(nom_loc == "Total del Municipio") %>%    # filtro exacto
  mutate(
    entidad = suppressWarnings(as.integer(entidad)),
    mun     = suppressWarnings(as.integer(mun)),
    cve_mun = paste0(sprintf("%02d", entidad), sprintf("%03d", mun)),
    anio    = 2010
  )

# --- 2020: nom_loc == "Total del Municipio"
mun_2020 <- iter_2020 %>%
  prep_names() %>%
  filter(nom_loc == "Total del Municipio") %>%    # filtro exacto
  mutate(
    entidad = suppressWarnings(as.integer(entidad)),
    mun     = suppressWarnings(as.integer(mun)),
    cve_mun = paste0(sprintf("%02d", entidad), sprintf("%03d", mun)),
    anio    = 2020
  )

# Contamos cuántos municipios hay en cada año
tabla_municipios <- bind_rows(
  dplyr::count(mun_2000, anio),
  dplyr::count(mun_2010, anio),
  dplyr::count(mun_2020, anio)
) %>%
  arrange(anio) %>%
  mutate(
    incremento = n - lag(n),                     # diferencia con respecto al censo anterior
    variacion_pct = round((incremento / lag(n)) * 100, 2) # cambio porcentual
  )

# Mostramos la tabla resultante
tabla_municipios
## # A tibble: 3 × 4
##    anio     n incremento variacion_pct
##   <dbl> <int>      <int>         <dbl>
## 1  2000  2427         NA         NA   
## 2  2010  2456         29          1.19
## 3  2020  2469         13          0.53

🔑 Creando una clave única para cada municipio

Visto el problema anterior —donde observamos que el número de municipios ha cambiado entre censos— necesitamos ahora una forma única de identificar a cada municipio sin ambigüedades.

Esto es fundamental porque en México los nombres de municipios se repiten con frecuencia:
por ejemplo, hay varios municipios llamados Álvaro Obregón, Benito Juárez o Miguel Hidalgo distribuidos en distintos estados.
Si solo usáramos el nombre, podríamos confundir municipios completamente diferentes.

Por ello, el INEGI utiliza una clave única de municipio, conocida como cve_mun, que combina:

  • La clave de entidad federativa (entidad) —dos dígitos—
  • La clave de municipio (mun) —tres dígitos—

Al unirlas, obtenemos un código de cinco dígitos que identifica de forma unívoca cada municipio del país.

Ejemplo:
Entidad = 01 (Aguascalientes)
Municipio = 001 (Aguascalientes)
Resultado → 01001

Sin embargo, los archivos originales del ITER presentan un pequeño reto:
las claves suelen venir sin ceros a la izquierda —por ejemplo, 1 en lugar de 01, o 7 en lugar de 007.
Por eso, antes de unir las columnas, debemos normalizarlas, añadiendo los ceros que faltan para mantener el formato oficial.

El siguiente código resuelve este problema:

  1. Crea una función llamada pad_code() que:
    • Limpia caracteres no numéricos o espacios.
    • Convierte los valores a número entero.
    • Rellena con ceros a la izquierda ("1""01", "7""007").
  2. Usa la función with_cve_mun() para:
    • Aplicar el formato correcto a las columnas entidad y mun.
    • Generar una nueva columna cve_mun concatenando ambas claves.
  3. Finalmente, volvemos a unir los campos en cada base (mun_2000, mun_2010, mun_2020) para asegurarnos de que cada registro tenga su identificador único municipal.

Con este paso, nuestros datos quedan listos para unirse con información espacial (como shapefiles o GeoPackages del INEGI) y para realizar análisis comparativos entre censos sin riesgo de duplicar o mezclar municipios con el mismo nombre.

En resumen: el código nos permite pasar del mundo de los nombres confusos
al universo ordenado de las claves geográficas 🔢🗺️

# ------------------------------------------------------------
# NORMALIZAR CLAVES Y CREAR cve_mun = ENTIDAD(2) + MUN(3)
# - Soluciona el problema de ceros a la izquierda (01, 003, etc.)
# - Limpia espacios y caracteres no numéricos
# ------------------------------------------------------------
library(dplyr)
library(stringr)

# Función para pad con ceros (robusta a num/char/factor y basura no numérica)
pad_code <- function(x, width){
  v <- as.character(x) |> 
    str_trim() |> 
    str_replace_all("[^0-9]", "") |>          # deja solo dígitos
    suppressWarnings(as.integer())             # a entero (pierde ceros, está bien)
  ifelse(is.na(v), NA_character_, stringr::str_pad(as.character(v), width, pad = "0"))
}

# Función para estandarizar y crear cve_mun en un df municipal
with_cve_mun <- function(df){
  df %>%
    mutate(
      entidad = pad_code(entidad, 2),          # "1" -> "01"
      mun     = pad_code(mun, 3),              # "7" -> "007"
      cve_mun = ifelse(is.na(entidad) | is.na(mun), NA_character_, paste0(entidad, mun))
    )
}

# Aplicar a cada año
mun_2000 <- with_cve_mun(mun_2000)
mun_2010 <- with_cve_mun(mun_2010)
mun_2020 <- with_cve_mun(mun_2020)
# ------------------------------------------------------------
# 🔑 Generar la clave municipal (cve_mun)
# ------------------------------------------------------------

# Ya tenemos las columnas 'entidad' y 'mun' estandarizadas.
# Ahora combinamos ambas para formar la clave municipal de 5 dígitos:
# Ejemplo: Aguascalientes (entidad = 01) + Aguascalientes (mun = 001) -> 01001

mun_2000 <- mun_2000 %>%
  mutate(cve_mun = paste0(entidad, mun))

mun_2010 <- mun_2010 %>%
  mutate(cve_mun = paste0(entidad, mun))

mun_2020 <- mun_2020 %>%
  mutate(cve_mun = paste0(entidad, mun))

# Verificamos que las claves se hayan creado correctamente
mun_2000 %>% select(entidad, mun, cve_mun) %>% head()
## # A tibble: 6 × 3
##   entidad mun   cve_mun
##   <chr>   <chr> <chr>  
## 1 01      001   01001  
## 2 01      002   01002  
## 3 01      003   01003  
## 4 01      004   01004  
## 5 01      005   01005  
## 6 01      006   01006
mun_2010 %>% select(entidad, mun, cve_mun) %>% head()
## # A tibble: 6 × 3
##   entidad mun   cve_mun
##   <chr>   <chr> <chr>  
## 1 01      001   01001  
## 2 01      002   01002  
## 3 01      003   01003  
## 4 01      004   01004  
## 5 01      005   01005  
## 6 01      006   01006
mun_2020 %>% select(entidad, mun, cve_mun) %>% head()
## # A tibble: 6 × 3
##   entidad mun   cve_mun
##   <chr>   <chr> <chr>  
## 1 01      001   01001  
## 2 01      002   01002  
## 3 01      003   01003  
## 4 01      004   01004  
## 5 01      005   01005  
## 6 01      006   01006

🧮 Del dato al indicador: construyendo información territorial con R

🧩 El reto de trabajar con datos del INEGI

Uno de los retos más grandes al trabajar con información del INEGI es que, aunque nos proporciona una enorme cantidad de datos, rara vez están en el formato que necesitamos.
Los datos vienen dispersos, con nombres distintos y estructuras que cambian entre censos o encuestas.
Antes de analizarlos o mapearlos, debemos ordenarlos, estandarizarlos y seleccionar las variables que realmente nos interesan.

En este proceso, construiremos indicadores territoriales a partir de los censos de 2000, 2010 y 2020.
Estos indicadores nos permitirán entender la dinámica demográfica y urbana de nuestros municipios y regiones.


📊 De las tablas al análisis espacial

A partir de las bases del ITER, seleccionaremos las variables necesarias para generar nuestros mapas e indicadores.
El objetivo es traducir los datos censales en información que nos hable de cómo crecen, envejecen, se transforman y se articulan los territorios.

A continuación, revisaremos los principales indicadores que calcularemos durante el taller 👇


📈 1. Tasa de crecimiento medio anual de la población

Mide la velocidad de crecimiento de la población en un periodo determinado.

\[ TCMA = \left( \frac{P_2}{P_1} \right)^{\frac{1}{t}} - 1 \]

  • P₁ = población inicial (año 2000).
  • P₂ = población final (año 2020).
  • t = número de años entre censos (20 años).

Este indicador permite comparar el crecimiento relativo entre municipios, independientemente de su tamaño.


🌆 2. Densidad de población

Expresa cuántas personas viven en promedio por kilómetro cuadrado.

\[ Densidad = \frac{Población \ total}{Superficie \ territorial} \]

  • Población total = del censo correspondiente.
  • Superficie = obtenida de la capa espacial municipal (INEGI).

Ayuda a visualizar los patrones de concentración o dispersión de la población.


👵 3. Índice de envejecimiento

Mide la proporción de personas mayores respecto a la población joven.

\[ Índice = \frac{Población_{65+}}{Población_{0-64}} \times 100 \]

Valores altos indican una población envejecida; bajos, una población predominantemente joven.


👶 4. Índice de juventud

Mide la proporción de población joven frente a los adultos.

\[ Índice = \frac{Población_{0-14}}{Población_{15+}} \times 100 \]

Ambos índices (juventud y envejecimiento) permiten analizar la estructura por edades de la población y prever demandas sociales, educativas o de salud.


🧭 5. Atracción migratoria reciente

Mide la llegada de población proveniente de otras entidades en los últimos años.

\[ A.M. \ Reciente = \frac{Población \ residente \ en \ otra \ entidad}{Población \ total \ del \ municipio} \times 100 \]

Este indicador refleja la dinámica de atracción de los municipios en el corto plazo.


🧳 6. Atracción migratoria acumulada

Evalúa la proporción de población nacida en otra entidad federativa.

\[ A.M. \ Acumulada = \frac{Población \ nacida \ en \ otra \ entidad}{Población \ total \ del \ municipio} \times 100 \]

Muestra el atractivo histórico de un municipio como destino migratorio.


⚙️ 7. Tasa de actividad económica

Mide qué porcentaje de la población mayor de 12 años participa en actividades económicas.

\[ Tasa \ de \ actividad = \frac{Población \ económicamente \ activa}{Población_{12+}} \times 100 \]

Indica el grado de participación laboral y el potencial productivo del territorio.


⚖️ 8. Índice de dependencia económica

Compara la población dependiente (niños y adultos mayores) con la población en edad laboral.

\[ Índice = \frac{P_{0-14} + P_{65+}}{P_{15-64}} \times 100 \]

Un valor alto implica más personas dependientes por cada 100 en edad laboral, lo que influye en la sostenibilidad económica del municipio.


🏙️ 9. Grado de urbanización

Mide la proporción de población que vive en localidades urbanas respecto al total municipal.

Este indicador refleja el proceso de concentración urbana y el crecimiento de las zonas metropolitanas, siendo clave para la planeación territorial.

🧩 Selección y homologación de variables censales

Uno de los mayores retos al trabajar con los censos del INEGI es que, aunque las variables son similares entre años, sus nombres y estructuras cambian ligeramente.
Por eso, antes de calcular indicadores debemos homologar las variables que representen los mismos conceptos en los tres censos (2000, 2010 y 2020).

En este taller, identificamos las variables necesarias para construir nuestros indicadores del subsistema social y urbano-regional, y encontramos que algunas no están disponibles en todos los años o tienen nombres distintos.

Por ejemplo:

Concepto Censo 2010 Censo 2020 Descripción
Población total pobtot POBTOT Total de personas que residen habitualmente en el país, entidad, municipio y localidad.
Población de 12 años y más p_12ymas P_12YMAS Personas de 12 años o más.
Población de 15 años y más P_15YMAS Personas de 15 años o más (solo en 2020).
Población de 0 a 14 años pob0_14 POB0_14 Personas de 0 a 14 años de edad.
Población de 15 a 64 años pob15_64 POB15_64 Personas en edad laboral.
Población de 65 años y más pob65_mas POB65_MAS Personas adultas mayores.
Población nacida en otra entidad pnacoe PNACOE Población migrante interna.
Población económicamente activa pea PEA Población de 12 años y más que trabaja o busca trabajo.

🧠 Reflexión metodológica

💡 Aunque los censos comparten estructura general, los nombres de las variables cambian con cada edición.
Por eso, homologar los nombres es un paso indispensable antes de unir o comparar información entre censos.

En este taller haremos dos pasos fundamentales: 1. Seleccionar solo las variables necesarias para los indicadores.
2. Renombrarlas con nombres uniformes que sean válidos para todos los años (por ejemplo, pob_total, pob_15_64, pob_65_mas, etc.).

Esto nos permitirá automatizar los cálculos y asegurar que las fórmulas funcionen sin importar el año del censo.

🔗 Unión de censos 2010 y 2020 por clave municipal

En este bloque realizamos tres pasos fundamentales:

  1. Seleccionamos las variables clave de cada censo (2010 y 2020), estandarizando los nombres para que sean consistentes.
  2. Renombramos las columnas añadiendo el sufijo _10 o _20 para distinguir el año.
  3. Unimos ambas bases mediante full_join() usando la clave municipal cve_mun.

El resultado es una tabla comparativa donde cada fila representa un municipio, y cada columna refleja los valores de ambos censos, por ejemplo:

cve_mun | pob_total_10 | pob_total_20 | pea_10 | pea_20 | pob_65_mas_10 | pob_65_mas_20 | … |

Esto nos permite calcular indicadores de crecimiento, envejecimiento, dependencia, actividad económica y migración con base en los datos de ambos censos.

💡 Si un municipio aparece en un censo y no en otro, quedará con valores NA, lo que también nos indica la creación o modificación de límites municipales entre 2010 y 2020.

# ============================================================
# 🧩 SELECCIÓN, HOMOLOGACIÓN Y UNIÓN DE VARIABLES CENSALES
# ============================================================
# Objetivo: dejar un solo dataframe con las variables de 2010 y 2020
# comparables por municipio (cve_mun), incluyendo el nombre del municipio.
# ============================================================

library(dplyr)
library(janitor)
library(stringr)
library(tidyr)

# ------------------------------------------------------------
# 1️⃣ LIMPIAR NOMBRES DE COLUMNAS
# ------------------------------------------------------------
mun_2010 <- mun_2010 %>% clean_names()
mun_2020 <- mun_2020 %>% clean_names()

# ------------------------------------------------------------
# 2️⃣ SELECCIONAR VARIABLES CLAVE PARA 2010
# ------------------------------------------------------------
mun_2010_vars <- mun_2010 %>%
  select(
    cve_mun,
    anio,
    pob_total      = pobtot,      # población total
    pob_12_mas     = p_12ymas,    # población de 12 años y más
    pob_0_14       = pob0_14,     # población de 0 a 14 años
    pob_15_64      = pob15_64,    # población de 15 a 64 años
    pob_65_mas     = pob65_mas,   # población de 65 años y más
    pob_nac_ot     = pnacoe,      # nacidos en otra entidad
    pob_econ_act   = pea          # población económicamente activa
  )

# ------------------------------------------------------------
# 3️⃣ SELECCIONAR VARIABLES CLAVE PARA 2020
# ------------------------------------------------------------
mun_2020_vars <- mun_2020 %>%
  select(
    cve_mun,
    anio,
    nom_ent,
    nom_mun,
    pob_total      = pobtot,      # población total (POBTOT)
    pob_12_mas     = p_12ymas,    # población de 12 años y más
    pob_15_mas     = p_15ymas,    # población de 15 años y más
    pob_0_14       = pob0_14,     # población de 0 a 14 años
    pob_15_64      = pob15_64,    # población de 15 a 64 años
    pob_65_mas     = pob65_mas,   # población de 65 años y más
    pob_nac_ot     = pnacoe,      # nacidos en otra entidad
    pob_econ_act   = pea          # población económicamente activa
  )

# ------------------------------------------------------------
# 4️⃣ RENOMBRAR VARIABLES CON SUFIJO DE AÑO (_10 y _20)
# ------------------------------------------------------------
mun_2010_wide <- mun_2010_vars %>%
  rename_with(~ paste0(., "_10"), .cols = -cve_mun)

mun_2020_wide <- mun_2020_vars %>%
  rename_with(~ paste0(., "_20"), .cols = -cve_mun)

# ------------------------------------------------------------
# 5️⃣ UNIR POR CLAVE MUNICIPAL (cve_mun)
# ------------------------------------------------------------
mun_10_20 <- full_join(mun_2010_wide, mun_2020_wide, by = "cve_mun")

# ------------------------------------------------------------
# 6️⃣ CHEQUEOS RÁPIDOS
# ------------------------------------------------------------
glimpse(mun_10_20)
## Rows: 2,469
## Columns: 20
## $ cve_mun         <chr> "01001", "01002", "01003", "01004", "01005", "01006", …
## $ anio_10         <dbl> 2010, 2010, 2010, 2010, 2010, 2010, 2010, 2010, 2010, …
## $ pob_total_10    <dbl> 797010, 45492, 54136, 15042, 99590, 41862, 49156, 8443…
## $ pob_12_mas_10   <chr> "602719", "32320", "39898", "10801", "69970", "30172",…
## $ pob_0_14_10     <chr> "238193", "16209", "17800", "5255", "35746", "14373", …
## $ pob_15_64_10    <chr> "515262", "26623", "32248", "8930", "59840", "25250", …
## $ pob_65_mas_10   <chr> "40309", "2627", "4034", "844", "3640", "2169", "2520"…
## $ pob_nac_ot_10   <chr> "186972", "5194", "6379", "1378", "16300", "4636", "37…
## $ pob_econ_act_10 <chr> "336974", "14319", "19310", "4819", "39315", "14892", …
## $ anio_20         <dbl> 2020, 2020, 2020, 2020, 2020, 2020, 2020, 2020, 2020, …
## $ nom_ent_20      <chr> "Aguascalientes", "Aguascalientes", "Aguascalientes", …
## $ nom_mun_20      <chr> "Aguascalientes", "Asientos", "Calvillo", "Cosío", "Je…
## $ pob_total_20    <dbl> 948990, 51536, 58250, 17000, 129929, 47646, 57369, 955…
## $ pob_12_mas_20   <chr> "756970", "38399", "44778", "12820", "99250", "36250",…
## $ pob_15_mas_20   <chr> "707473", "35250", "41495", "11817", "91487", "33468",…
## $ pob_0_14_20     <chr> "240583", "16266", "16720", "5183", "38303", "14140", …
## $ pob_15_64_20    <chr> "639532", "31919", "35854", "10699", "84949", "30491",…
## $ pob_65_mas_20   <chr> "67941", "3331", "5641", "1118", "6538", "2977", "3579…
## $ pob_nac_ot_20   <chr> "214908", "6025", "6566", "1385", "23898", "4416", "49…
## $ pob_econ_act_20 <chr> "486675", "21440", "25976", "7010", "65839", "22097", …
# Municipios presentes solo en un año
missing_2010 <- mun_10_20 %>% filter(is.na(pob_total_10)) %>% nrow()
missing_2020 <- mun_10_20 %>% filter(is.na(pob_total_20)) %>% nrow()

cat("🟡 Municipios que aparecen en 2020 pero no en 2010:", missing_2010, "\n")
## 🟡 Municipios que aparecen en 2020 pero no en 2010: 13
cat("🟡 Municipios que aparecen en 2010 pero no en 2020:", missing_2020, "\n")
## 🟡 Municipios que aparecen en 2010 pero no en 2020: 0
# Vista rápida de las columnas principales
mun_10_20 %>%
  select(cve_mun, nom_ent_20, nom_mun_20, 
         starts_with("pob_total"), starts_with("pob_econ_act")) %>%
 head() 
## # A tibble: 6 × 7
##   cve_mun nom_ent_20     nom_mun_20    pob_total_10 pob_total_20 pob_econ_act_10
##   <chr>   <chr>          <chr>                <dbl>        <dbl> <chr>          
## 1 01001   Aguascalientes Aguascalient…       797010       948990 336974         
## 2 01002   Aguascalientes Asientos             45492        51536 14319          
## 3 01003   Aguascalientes Calvillo             54136        58250 19310          
## 4 01004   Aguascalientes Cosío                15042        17000 4819           
## 5 01005   Aguascalientes Jesús María          99590       129929 39315          
## 6 01006   Aguascalientes Pabellón de …        41862        47646 14892          
## # ℹ 1 more variable: pob_econ_act_20 <chr>
# ------------------------------------------------------------
# 📊 Vista interactiva con DT de columnas clave (2010 vs 2020)
# ------------------------------------------------------------
library(DT)

tabla_vista <- mun_10_20 %>%
  select(
    cve_mun, nom_ent_20, nom_mun_20,
    pob_total_10, pob_total_20,
    pob_econ_act_10, pob_econ_act_20
  )

DT::datatable(
  tabla_vista,
  options = list(
    pageLength = 10,
    scrollX = TRUE,
    autoWidth = TRUE,
    language = list(url = '//cdn.datatables.net/plug-ins/1.13.1/i18n/es-ES.json')
  ),
  filter = "top",
  caption = "Comparativo municipal 2010 vs 2020 (población total y PEA)"
)

🧭 Identificando nuevos municipios

Una forma sencilla de explorar la dinámica territorial entre censos es revisar si existen municipios nuevos (creados, divididos o renombrados).

El siguiente bloque compara los datos de 2010 y 2020, filtrando aquellos que tienen información solo en 2020 (pob_total_20 no es NA) pero no aparecen en 2010 (pob_total_10 sí es NA).

De este modo obtenemos una lista de municipios incorporados en el periodo 2010–2020, junto con su población y entidad correspondiente.

# ------------------------------------------------------------
# 🆕 DETECTAR NUEVOS MUNICIPIOS (presentes en 2020, ausentes en 2010)
# ------------------------------------------------------------
library(dplyr)

# Filtramos los municipios que existen en 2020 pero no en 2010
nuevos_municipios <- mun_10_20 %>%
  filter(is.na(pob_total_10) & !is.na(pob_total_20)) %>%
  select(cve_mun, nom_ent_20, nom_mun_20, pob_total_20)

# Mostramos los resultados
cat("Número de nuevos municipios en 2020:", nrow(nuevos_municipios), "\n")
## Número de nuevos municipios en 2020: 13
# Vista rápida
nuevos_municipios %>% 
  arrange(nom_ent_20, nom_mun_20) %>% 
  head(20)
## # A tibble: 13 × 4
##    cve_mun nom_ent_20      nom_mun_20               pob_total_20
##    <chr>   <chr>           <chr>                           <dbl>
##  1 02006   Baja California San Quintín                    117568
##  2 04012   Campeche        Seybaplaya                      15297
##  3 07120   Chiapas         Capitán Luis Ángel Vidal         4315
##  4 07122   Chiapas         El Parral                       15587
##  5 07123   Chiapas         Emiliano Zapata                 10783
##  6 07125   Chiapas         Honduras de la Sierra           11650
##  7 07124   Chiapas         Mezcalapa                       23847
##  8 07121   Chiapas         Rincón Chamula San Pedro         8718
##  9 17034   Morelos         Coatetelco                      11347
## 10 17036   Morelos         Hueyapan                         7855
## 11 17035   Morelos         Xoxocotla                       27805
## 12 23010   Quintana Roo    Bacalar                         41754
## 13 23011   Quintana Roo    Puerto Morelos                  26921

###🧮 Cálculo de indicadores territoriales

En este bloque de código se realiza uno de los pasos más importantes del taller: convertir los datos censales en indicadores cuantitativos que nos permitan analizar y comparar los municipios de México.

1️⃣ Conversión a formato numérico

Los datos del INEGI suelen venir en formato de texto (por ejemplo, “12,345” en lugar de 12345), lo que impide hacer cálculos directamente. Por eso, usamos la función parse_number() del paquete readr, que:

Elimina comas y espacios,

Convierte los valores a números,

Y permite hacer operaciones matemáticas sin errores.

Esto se aplica a todas las columnas que contienen la palabra “pob” (población).

2️⃣ Cálculo de indicadores demográficos y sociales

A partir de los datos de los censos 2010 y 2020, se calculan distintos indicadores básicos del subsistema social y urbano-regional, tomando como referencia la metodología del Instituto de Geografía de la UNAM.

# 🧮 CONVERSIÓN A NUMÉRICO Y CÁLCULO DE INDICADORES
# ------------------------------------------------------------
library(dplyr)
library(readr)

# 1️⃣ Convertir columnas de población a numéricas
# (quita comas, espacios y convierte a numeric)
mun_10_20_num <- mun_10_20 %>%
  mutate(across(
    matches("^pob_|pobtot|p_"), 
    ~ parse_number(as.character(.))
  ))

# 2️⃣ Calcular indicadores
indicadores_mun <- mun_10_20_num %>%
  mutate(
    # --- Tasa de Crecimiento Medio Anual (TCMA)
    tcma = ((pob_total_20 / pob_total_10)^(1/10) - 1) * 100,

    # --- Índice de Envejecimiento
    ind_envejec_10 = (pob_65_mas_10 / pob_0_14_10) * 100,
    ind_envejec_20 = (pob_65_mas_20 / pob_0_14_20) * 100,

    # --- Índice de Juventud
    ind_juventud_10 = (pob_0_14_10 / (pob_15_64_10 + pob_65_mas_10)) * 100,
    ind_juventud_20 = (pob_0_14_20 / (pob_15_64_20 + pob_65_mas_20)) * 100,

    # --- Índice de Dependencia Económica
    ind_depend_10 = ((pob_0_14_10 + pob_65_mas_10) / pob_15_64_10) * 100,
    ind_depend_20 = ((pob_0_14_20 + pob_65_mas_20) / pob_15_64_20) * 100,

    # --- Atracción migratoria acumulada
    atr_migr_10 = (pob_nac_ot_10 / pob_total_10) * 100,
    atr_migr_20 = (pob_nac_ot_20 / pob_total_20) * 100,

    # --- Tasa de actividad económica
    tasa_act_10 = (pob_econ_act_10 / pob_12_mas_10) * 100,
    tasa_act_20 = (pob_econ_act_20 / pob_12_mas_20) * 100
  )

# 3️⃣ Revisar resultados
indicadores_mun %>%
  select(nom_ent_20, nom_mun_20, tcma, ind_envejec_20, ind_depend_20, atr_migr_20, tasa_act_20) %>%
  head()
## # A tibble: 6 × 7
##   nom_ent_20     nom_mun_20        tcma ind_envejec_20 ind_depend_20 atr_migr_20
##   <chr>          <chr>            <dbl>          <dbl>         <dbl>       <dbl>
## 1 Aguascalientes Aguascalientes   1.76            28.2          48.2       22.6 
## 2 Aguascalientes Asientos         1.26            20.5          61.4       11.7 
## 3 Aguascalientes Calvillo         0.735           33.7          62.4       11.3 
## 4 Aguascalientes Cosío            1.23            21.6          58.9        8.15
## 5 Aguascalientes Jesús María      2.69            17.1          52.8       18.4 
## 6 Aguascalientes Pabellón de Art… 1.30            21.1          56.1        9.27
## # ℹ 1 more variable: tasa_act_20 <dbl>

🛰️ R como plataforma geoespacial: librerías clave

  • sf (simple features): lee/escribe datos espaciales (Shapefile, GeoJSON, GeoPackage), maneja geometrías (puntos, líneas, polígonos) y proyecciones (CRS).
  • tmap: crea mapas rápidos (estáticos o interactivos) con una sintaxis muy clara.
  • dplyr: une y transforma tablas; lo usamos para unir estadísticas con geometría.
  • janitor (opcional): estandariza nombres de columnas (evita errores por mayúsculas/acentos).

📝 Tip de rutas en Windows: usa “/” o doble “\”.
Ejemplo: C:/SIG_RO/Taller_R/Datos espaciales/00mun.shp

library(sf)
## Linking to GEOS 3.13.1, GDAL 3.11.0, PROJ 9.6.0; sf_use_s2() is TRUE
library(dplyr)
library(stringr)
library(janitor)
library(tmap)

# 1) Leer la capa municipal (ajusta la ruta si es necesario)
ruta_shp <- "C:/SIG_RO/Taller_R/Datos espaciales/00mun.shp"
mun_sf <- sf::read_sf(ruta_shp) %>%
  janitor::clean_names()

🗺️ Mapeando municipios con R

Con solo unas pocas líneas de código, R puede leer y visualizar información geográfica.

  1. sf::read_sf() lee un archivo espacial (como un .shp), conservando su geometría.
  2. tmap nos permite crear mapas de forma rápida y reproducible.
  3. Con tmap_mode("plot") obtenemos un mapa estático; cambiando a "view" generamos un mapa interactivo con zoom y desplazamiento.

Este primer mapa muestra todos los municipios de México con su delimitación básica, demostrando que R puede comportarse como una plataforma geoespacial completa sin necesidad de interfaces gráficas.

# 3️⃣ Mapa simple con tmap
tmap_mode("plot") # modo 'plot' para mapa estático; usar 'view' para interactivo
## ℹ tmap modes "plot" - "view"
## ℹ toggle with `tmap::ttm()`
tm_shape(mun_sf) +
  tm_polygons(
    col = "lightblue",
    border.col = "white",
    lwd = 0.1,
    title = "Municipios"
  ) +
  tm_layout(
    title = "Mapa básico de municipios de México",
    title.size = 1.2,
    frame = FALSE
  )
## 
## ── tmap v3 code detected ───────────────────────────────────────────────────────
## [v3->v4] `tm_polygons()`: use 'fill' for the fill color of polygons/symbols
## (instead of 'col'), and 'col' for the outlines (instead of 'border.col').[v3->v4] `tm_polygons()`: migrate the argument(s) related to the legend of the
## visual variable `fill` namely 'title' to 'fill.legend = tm_legend(<HERE>)'[v3->v4] `tm_layout()`: use `tm_title()` instead of `tm_layout(title = )`

###🔗 Integrando datos espaciales y estadísticos

Hasta este punto del taller, hemos trabajado con dos tipos de información complementaria:

🗺️ El shapefile municipal (mun_sf): Contiene la geometría (los polígonos de los municipios), sus límites y nombres oficiales.

📊 La tabla de indicadores (indicadores_mun): Contiene los datos estadísticos calculados a partir de los censos (población, índices, tasas, etc.).

El objetivo de este bloque es unir ambos conjuntos de información para obtener una sola capa espacial con toda la información lista para mapear y analizar: geometría + indicadores.

# 🔗 UNIÓN mun_sf (cvegeo) + indicadores_mun (cve_mun)
# ------------------------------------------------------------
library(sf)
library(dplyr)
library(stringr)
library(janitor)

# 1️⃣ Aseguramos nombres limpios
mun_sf <- mun_sf %>% janitor::clean_names()

# 2️⃣ Función para normalizar a 5 dígitos (caracter)
pad5 <- function(x){
  x <- as.character(x)
  x <- stringr::str_replace_all(x, "[^0-9]", "")
  dplyr::if_else(nchar(x) == 0, NA_character_, stringr::str_pad(x, 5, pad = "0"))
}

# 3️⃣ Crear llave común 'cve_mun' en ambos objetos
mun_sf_key <- mun_sf %>%
  mutate(
    cve_mun = pad5(cvegeo)
  ) %>%
  select(cvegeo, cve_mun, nomgeo, everything())  # 👈 forzamos a mantener nomgeo

indicadores_key <- indicadores_mun %>%
  mutate(cve_mun = pad5(cve_mun))

# 4️⃣ Diagnóstico rápido de empates
faltan_en_ind <- mun_sf_key %>%
  st_drop_geometry() %>%
  anti_join(indicadores_key %>% select(cve_mun), by = "cve_mun")

sobran_en_ind <- indicadores_key %>%
  anti_join(mun_sf_key %>% st_drop_geometry() %>% select(cve_mun), by = "cve_mun")

cat("Claves en SHP sin match en indicadores:", nrow(faltan_en_ind), "\n")
## Claves en SHP sin match en indicadores: 0
cat("Claves en indicadores sin match en SHP:", nrow(sobran_en_ind), "\n")
## Claves en indicadores sin match en SHP: 0
# 5️⃣ Unión (manteniendo geometría y nombre del municipio)
mun_indicadores_sf <- mun_sf_key %>%
  left_join(indicadores_key, by = "cve_mun")

# 6️⃣ Comprobación de columnas clave tras el join
mun_indicadores_sf %>%
  select(cvegeo, cve_mun, nomgeo, 
         any_of(c("nom_ent_20", "nom_mun_20")),
         starts_with("pob_total"), starts_with("tcma"), starts_with("ind_")) %>%
  head()
## Simple feature collection with 6 features and 14 fields
## Geometry type: MULTIPOLYGON
## Dimension:     XY
## Bounding box:  xmin: 2410092 ymin: 1067540 xmax: 2514978 ymax: 1159778
## Projected CRS: MEXICO_ITRF_2008_LCC
## # A tibble: 6 × 15
##   cvegeo cve_mun nomgeo    nom_ent_20 nom_mun_20 pob_total_10 pob_total_20  tcma
##   <chr>  <chr>   <chr>     <chr>      <chr>             <dbl>        <dbl> <dbl>
## 1 01001  01001   Aguascal… Aguascali… Aguascali…       797010       948990 1.76 
## 2 01002  01002   Asientos  Aguascali… Asientos          45492        51536 1.26 
## 3 01003  01003   Calvillo  Aguascali… Calvillo          54136        58250 0.735
## 4 01004  01004   Cosío     Aguascali… Cosío             15042        17000 1.23 
## 5 01005  01005   Jesús Ma… Aguascali… Jesús Mar…        99590       129929 2.69 
## 6 01006  01006   Pabellón… Aguascali… Pabellón …        41862        47646 1.30 
## # ℹ 7 more variables: ind_envejec_10 <dbl>, ind_envejec_20 <dbl>,
## #   ind_juventud_10 <dbl>, ind_juventud_20 <dbl>, ind_depend_10 <dbl>,
## #   ind_depend_20 <dbl>, geometry <MULTIPOLYGON [m]>

Con este código, exportamos el objeto espacial mun_indicadores_sf, que ya contiene geometría y variables censales, como un shapefile estándar.

🔹 st_write() es la función del paquete sf para escribir archivos espaciales.
🔹 El parámetro driver = "ESRI Shapefile" indica el formato de salida.
🔹 delete_layer = TRUE permite sobrescribir el archivo si ya existe, evitando errores.

El resultado quedará en la ruta de trabajo o donde tu le des la dirección

# ------------------------------------------------------------
# 💾 EXPORTAR LA CAPA UNIDA COMO SHAPEFILE
# ------------------------------------------------------------
# Objetivo:
# Guardar la capa final 'mun_indicadores_sf' (municipios + indicadores)
# como shapefile para abrirla después en QGIS o ArcGIS.

library(sf)

# 1️⃣ Definir la ruta de salida
ruta_salida_shp <- "C:/SIG_RO/Taller_R/Salidas/indicadores_municipales.shp"

# 2️⃣ Exportar usando st_write()
st_write(
  obj = mun_indicadores_sf,
  dsn = ruta_salida_shp,
  driver = "ESRI Shapefile",
  delete_layer = TRUE  # sobrescribe si ya existe
)
## Deleting layer `indicadores_municipales' using driver `ESRI Shapefile'
## Writing layer `indicadores_municipales' to data source 
##   `C:/SIG_RO/Taller_R/Salidas/indicadores_municipales.shp' using driver `ESRI Shapefile'
## Writing 2469 features with 34 fields and geometry type Multi Polygon.
# 3️⃣ Confirmación visual
cat("✅ Shapefile exportado correctamente en:", ruta_salida_shp)
## ✅ Shapefile exportado correctamente en: C:/SIG_RO/Taller_R/Salidas/indicadores_municipales.shp