Rodrigo Zumaya Tapia
Ana Laura Salguero Melgar
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.
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.
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.
Un Sistema de Información Geográfica (SIG) es una herramienta tecnológica que permite:
🟢 En resumen:
> Un SIG combina base de datos + cartografía + análisis espacial
dentro de un entorno visual.
“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.
🔹 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.
👉 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. |
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"
En RStudio, puedes crear un nuevo chunk rápidamente con el atajo de teclado
Ctrl + Alt + I
(en Windows) oCmd + Option + I
(en Mac).
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.
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
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
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.
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"
)
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:
nom_loc
indica el total municipal con el texto “TOTAL
MUNICIPAL” (en mayúsculas).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:
nom_loc == "TOTAL MUNICIPAL"
.nom_loc == "Total del Municipio"
.cve_mun
, que combina la clave de entidad y municipio
(ENTIDAD
+ MUN
) para formar un identificador
único de cinco dígitos.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
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:
entidad
) —dos dígitos—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:
pad_code()
que:
"1"
→
"01"
, "7"
→ "007"
).with_cve_mun()
para:
entidad
y
mun
.cve_mun
concatenando ambas
claves.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
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.
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 👇
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 \]
Este indicador permite comparar el crecimiento relativo entre municipios, independientemente de su tamaño.
Expresa cuántas personas viven en promedio por kilómetro cuadrado.
\[ Densidad = \frac{Población \ total}{Superficie \ territorial} \]
Ayuda a visualizar los patrones de concentración o dispersión de la población.
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.
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.
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.
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.
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.
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.
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.
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. |
💡 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.
En este bloque realizamos tres pasos fundamentales:
_10
o _20
para distinguir el año.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:
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)"
)
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>
📝 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()
Con solo unas pocas líneas de código, R puede leer y visualizar información geográfica.
sf::read_sf()
lee un archivo espacial
(como un .shp
), conservando su geometría.tmap
nos permite crear mapas de forma
rápida y reproducible.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ámetrodriver = "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