library(readxl)
library(dplyr)
library(sf)
library(ggplot2)
library(janitor)
library(tidyr)
library(viridis)
library(knitr)
library(stringr)
library(scales)
library(grid)
knitr::opts_chunk$set(
echo = TRUE,
message = FALSE,
warning = FALSE,
fig.align = "center",
dpi = 150,
out.width = "100%"
)
theme_set(theme_minimal(base_family = "Arial"))
proyecto_dir <- "C:/Users/Usuario/Documents/PROYECTOS GEO/ACTIVIDAD 1"
if (!dir.exists(file.path(proyecto_dir, "Casos"))) {
proyecto_dir <- getwd()
}
paletas <- list(
origen = c("#fff7bc", "#fec44f", "#fe9929", "#d95f0e", "#8c2d04"),
destino = c("#edf8fb", "#b2e2e2", "#66c2a4", "#2ca25f", "#006d2c"),
bicicleta = c("#f7fcf5", "#c7e9c0", "#74c476", "#238b45", "#005a32"),
moto = c("#fff5eb", "#fdd0a2", "#fd8d3c", "#e6550d", "#a63603"),
automovil = c("#eff3ff", "#bdd7e7", "#6baed6", "#3182bd", "#08519c"),
flujo = c("#f7f7f7", "#cccccc", "#969696", "#636363", "#252525")
)
formato_num <- function(x) comma(x, big.mark = ".", decimal.mark = ",")
nota_html <- function(titulo, texto) {
sprintf(
'<div class="nota"><strong>%s.</strong> %s</div>',
titulo,
texto
)
}
Este reporte analiza los viajes origen-destino registrados en la encuesta, usando como unidad espacial las comunas de Cali. El interés principal es identificar las comunas desde donde sale la mayor cantidad de personas, diferenciar el patrón según tipo de vehículo y observar hacia dónde se dirigen los viajes que parten de los principales lugares de origen.
El ejercicio se conecta con el objetivo de la unidad: reconocer áreas de la estadística espacial y tipos de datos espaciales mediante una aplicación real. En este caso se combinan datos tabulares de encuesta con datos vectoriales de polígonos comunales.
encuesta <- read_excel(file.path(proyecto_dir, "Casos/EncuestaOrigenDestino.xlsx"), sheet = "Hoja1") %>%
clean_names()
comunas <- st_read(file.path(proyecto_dir, "Casos/cali/Comunas.shp"), quiet = TRUE)
encuesta_limpia %>%
count(vehiculo_nombre, sort = TRUE, name = "total_viajes") %>%
mutate(porcentaje = percent(total_viajes / sum(total_viajes), accuracy = 0.1, decimal.mark = ",")) %>%
kable(caption = "Distribución de viajes por tipo de vehículo")
| vehiculo_nombre | total_viajes | porcentaje |
|---|---|---|
| Moto | 16077 | 45,9% |
| Automóvil | 14100 | 40,2% |
| Otro | 3146 | 9,0% |
| Bicicleta | 1731 | 4,9% |
cat(nota_html(
"Criterio espacial",
"Los registros marcados como 'Fuera de Cali', valores cero o valores vacíos se conservan en el diagnóstico, pero no se dibujan en los mapas de comunas porque no tienen un polígono equivalente en la cartografía usada."
))
Para solucionar el caso se aplicaron técnicas descriptivas y cartográficas propias del análisis espacial. Primero se integró una base tabular de viajes con una capa vectorial de comunas; luego se limpiaron y estandarizaron los códigos de origen, destino y tipo de vehículo; finalmente se construyeron mapas temáticos para representar la intensidad de viajes por comuna.
Las técnicas usadas en el reporte son:
Estas técnicas son adecuadas porque el problema combina datos de movilidad con unidades espaciales discretas. El mapa coroplético permite reconocer concentración territorial, mientras que la desagregación por vehículo ayuda a detectar patrones espaciales diferenciados.
Las siguientes funciones construyen una estética común para los mapas, asignan paletas diferenciadas por tema y resaltan las comunas con mayor cantidad de viajes. El código queda plegado por defecto y se puede desplegar desde el botón de visualización del reporte HTML.
tema_mapa <- function() {
theme_void(base_family = "Arial") +
theme(
plot.title = element_text(face = "bold", size = 17, color = "#12343b"),
plot.subtitle = element_text(size = 11, color = "#455a64", margin = margin(b = 8)),
plot.caption = element_text(size = 8.5, color = "#607d8b", hjust = 0),
legend.position = "right",
legend.title = element_text(face = "bold", size = 9),
legend.text = element_text(size = 8),
panel.grid = element_blank(),
plot.margin = margin(10, 12, 10, 12)
)
}
preparar_mapa <- function(tabla, variable_tabla) {
comunas_limpias %>%
left_join(tabla, by = c("comuna_num" = variable_tabla)) %>%
mutate(
total_viajes = replace_na(total_viajes, 0L),
etiqueta_top = if_else(
total_viajes > 0 & min_rank(desc(total_viajes)) <= 5,
paste0("C", comuna_num, "\n", formato_num(total_viajes)),
NA_character_
)
)
}
dibujar_mapa_coropletico <- function(mapa, titulo, subtitulo, paleta, leyenda = "Viajes",
resaltar_comuna = NULL) {
base <- ggplot(mapa) +
geom_sf(aes(fill = total_viajes), color = "#ffffff", linewidth = 0.28) +
geom_sf(data = filter(mapa, total_viajes == 0), fill = NA, color = "#d8d8d8",
linewidth = 0.22, linetype = "dotted") +
geom_sf_text(aes(label = comuna_num), size = 2.6, color = "#263238", alpha = 0.75) +
geom_sf_label(
data = filter(mapa, !is.na(etiqueta_top)),
aes(label = etiqueta_top),
size = 3,
label.size = 0.15,
label.padding = unit(0.12, "lines"),
fill = "white",
color = "#1d2b2f",
alpha = 0.94
)
if (!is.null(resaltar_comuna)) {
base <- base +
geom_sf(
data = filter(mapa, comuna_num == resaltar_comuna),
fill = NA,
color = "#111111",
linewidth = 1
)
}
base +
scale_fill_gradientn(
colors = paleta,
labels = formato_num,
name = leyenda,
guide = guide_colorbar(barheight = unit(70, "pt"), barwidth = unit(10, "pt"))
) +
labs(
title = titulo,
subtitle = subtitulo,
caption = "Fuente: Encuesta Origen-Destino y cartografía de comunas de Cali."
) +
coord_sf(datum = NA) +
tema_mapa()
}
crear_mapa_origen <- function(nombre_vehiculo = NULL) {
datos <- encuesta_limpia %>%
filter(!is.na(comuna_origen_num))
titulo <- "Mapa general de origen de viajes"
subtitulo <- "Cantidad de viajes que salen desde cada comuna"
paleta <- paletas$origen
if (!is.null(nombre_vehiculo)) {
datos <- datos %>% filter(vehiculo_nombre == nombre_vehiculo)
titulo <- paste("Origen de viajes en", nombre_vehiculo)
subtitulo <- paste("Comunas desde donde salen los viajes realizados en", nombre_vehiculo)
paleta <- switch(
nombre_vehiculo,
"Bicicleta" = paletas$bicicleta,
"Moto" = paletas$moto,
"Automóvil" = paletas$automovil,
paletas$origen
)
}
tabla <- datos %>%
count(comuna_origen_num, name = "total_viajes")
preparar_mapa(tabla, "comuna_origen_num") %>%
dibujar_mapa_coropletico(titulo, subtitulo, paleta)
}
crear_mapa_destino <- function(nombre_vehiculo = NULL) {
datos <- encuesta_limpia %>%
filter(!is.na(comuna_destino_num))
titulo <- "Mapa general de destino de viajes"
subtitulo <- "Cantidad de viajes que llegan a cada comuna"
paleta <- paletas$destino
if (!is.null(nombre_vehiculo)) {
datos <- datos %>% filter(vehiculo_nombre == nombre_vehiculo)
titulo <- paste("Destino de viajes en", nombre_vehiculo)
subtitulo <- paste("Comunas a donde llegan los viajes realizados en", nombre_vehiculo)
paleta <- switch(
nombre_vehiculo,
"Bicicleta" = paletas$bicicleta,
"Moto" = paletas$moto,
"Automóvil" = paletas$automovil,
paletas$destino
)
}
tabla <- datos %>%
count(comuna_destino_num, name = "total_viajes")
preparar_mapa(tabla, "comuna_destino_num") %>%
dibujar_mapa_coropletico(titulo, subtitulo, paleta)
}
crear_mapa_destino_desde_origen <- function(origen_elegido, nombre_vehiculo = NULL) {
datos <- encuesta_limpia %>%
filter(
comuna_origen_num == origen_elegido,
!is.na(comuna_destino_num)
)
titulo <- paste("Destinos de viajes que salen desde la comuna", origen_elegido)
subtitulo <- "Distribución espacial de los destinos asociados al origen seleccionado"
paleta <- paletas$flujo
if (!is.null(nombre_vehiculo)) {
datos <- datos %>% filter(vehiculo_nombre == nombre_vehiculo)
titulo <- paste("Destinos desde la comuna", origen_elegido, "en", nombre_vehiculo)
subtitulo <- paste("Flujos de destino para viajes realizados en", nombre_vehiculo)
paleta <- switch(
nombre_vehiculo,
"Bicicleta" = paletas$bicicleta,
"Moto" = paletas$moto,
"Automóvil" = paletas$automovil,
paletas$flujo
)
}
tabla <- datos %>%
count(comuna_destino_num, name = "total_viajes")
preparar_mapa(tabla, "comuna_destino_num") %>%
dibujar_mapa_coropletico(
titulo,
subtitulo,
paleta,
leyenda = "Viajes destino",
resaltar_comuna = origen_elegido
)
}
crear_mapa_origen()
cat(sprintf(
'<div class="conclusion"><strong>Lectura del mapa.</strong> La comuna con mayor número de viajes de origen es la comuna %s (%s), con %s viajes registrados. Esto indica una mayor intensidad de salida frente al resto de comunas cartografiadas.</div>',
origen_principal,
origen_principal_nombre,
formato_num(top_origenes$total_viajes[1])
))
top_origenes %>%
transmute(
comuna = comuna_origen_num,
nombre_comuna,
viajes_salida = total_viajes
) %>%
slice_head(n = 10) %>%
kable(caption = "Diez comunas con mayor cantidad de viajes de origen")
| comuna | nombre_comuna | viajes_salida |
|---|---|---|
| 2 | Comuna 2 | 2968 |
| 19 | Comuna 19 | 2850 |
| 17 | Comuna 17 | 2377 |
| 3 | Comuna 3 | 2121 |
| 18 | Comuna 18 | 1536 |
| 4 | Comuna 4 | 1493 |
| 10 | Comuna 10 | 1401 |
| 13 | Comuna 13 | 1238 |
| 15 | Comuna 15 | 1171 |
| 22 | Comuna 22 | 1153 |
La desagregación por vehículo permite comparar patrones espaciales distintos. Bicicleta, moto y automóvil no necesariamente salen de las mismas comunas con la misma intensidad; por eso cada mapa usa una paleta propia.
crear_mapa_origen("Bicicleta")
crear_mapa_origen("Moto")
crear_mapa_origen("Automóvil")
tabla_origenes_vehiculo <- encuesta_limpia %>%
filter(
vehiculo_nombre %in% vehiculos_interes,
!is.na(comuna_origen_num)
) %>%
count(vehiculo_nombre, comuna_origen_num, name = "total_viajes") %>%
left_join(st_drop_geometry(comunas_limpias) %>% select(comuna_num, nombre_comuna),
by = c("comuna_origen_num" = "comuna_num")) %>%
arrange(vehiculo_nombre, desc(total_viajes))
tabla_origenes_vehiculo %>%
group_by(vehiculo_nombre) %>%
slice_max(order_by = total_viajes, n = 5, with_ties = FALSE) %>%
ungroup() %>%
transmute(
vehiculo = vehiculo_nombre,
comuna = comuna_origen_num,
nombre_comuna,
viajes_salida = total_viajes
) %>%
kable(caption = "Cinco principales comunas de origen por tipo de vehículo")
| vehiculo | comuna | nombre_comuna | viajes_salida |
|---|---|---|---|
| Automóvil | 19 | Comuna 19 | 1165 |
| Automóvil | 2 | Comuna 2 | 1121 |
| Automóvil | 17 | Comuna 17 | 899 |
| Automóvil | 3 | Comuna 3 | 854 |
| Automóvil | 18 | Comuna 18 | 559 |
| Bicicleta | 2 | Comuna 2 | 159 |
| Bicicleta | 17 | Comuna 17 | 141 |
| Bicicleta | 19 | Comuna 19 | 141 |
| Bicicleta | 18 | Comuna 18 | 111 |
| Bicicleta | 3 | Comuna 3 | 99 |
| Moto | 2 | Comuna 2 | 1414 |
| Moto | 19 | Comuna 19 | 1280 |
| Moto | 17 | Comuna 17 | 1136 |
| Moto | 3 | Comuna 3 | 970 |
| Moto | 18 | Comuna 18 | 746 |
top_origen_vehiculo <- tabla_origenes_vehiculo %>%
group_by(vehiculo_nombre) %>%
slice_max(order_by = total_viajes, n = 1, with_ties = FALSE) %>%
ungroup()
texto_origen_vehiculo <- top_origen_vehiculo %>%
mutate(
frase = sprintf(
"%s presenta su mayor origen en la comuna %s (%s), con %s viajes",
vehiculo_nombre,
comuna_origen_num,
nombre_comuna,
formato_num(total_viajes)
)
) %>%
pull(frase) %>%
paste(collapse = "; ")
cat(sprintf(
'<div class="conclusion"><strong>Interpretación comparativa.</strong> %s. Esto muestra que la movilidad no tiene una distribución única: cada modo de transporte concentra sus salidas en comunas específicas, por lo que el análisis por vehículo aporta más detalle que el mapa general.</div>',
texto_origen_vehiculo
))
Los mapas de destino muestran hacia dónde llegan los viajes. Esta lectura complementa los mapas de origen porque una comuna puede producir muchos viajes, recibir muchos viajes o cumplir ambas funciones.
crear_mapa_destino()
cat(sprintf(
'<div class="conclusion"><strong>Lectura del mapa.</strong> La comuna con mayor número de llegadas es la comuna %s (%s), con %s viajes. Comparar esta comuna con el principal origen ayuda a distinguir zonas generadoras y zonas atractoras de viajes.</div>',
destino_principal,
destino_principal_nombre,
formato_num(top_destinos$total_viajes[1])
))
top_destinos %>%
transmute(
comuna = comuna_destino_num,
nombre_comuna,
viajes_llegada = total_viajes
) %>%
slice_head(n = 10) %>%
kable(caption = "Diez comunas con mayor cantidad de viajes de destino")
| comuna | nombre_comuna | viajes_llegada |
|---|---|---|
| 2 | Comuna 2 | 4810 |
| 3 | Comuna 3 | 3859 |
| 19 | Comuna 19 | 3158 |
| 17 | Comuna 17 | 2164 |
| 22 | Comuna 22 | 1860 |
| 4 | Comuna 4 | 1856 |
| 9 | Comuna 9 | 1279 |
| 8 | Comuna 8 | 1025 |
| 10 | Comuna 10 | 927 |
| 7 | Comuna 7 | 748 |
crear_mapa_destino("Bicicleta")
crear_mapa_destino("Moto")
crear_mapa_destino("Automóvil")
tabla_destinos_vehiculo <- encuesta_limpia %>%
filter(
vehiculo_nombre %in% vehiculos_interes,
!is.na(comuna_destino_num)
) %>%
count(vehiculo_nombre, comuna_destino_num, name = "total_viajes") %>%
left_join(st_drop_geometry(comunas_limpias) %>% select(comuna_num, nombre_comuna),
by = c("comuna_destino_num" = "comuna_num")) %>%
arrange(vehiculo_nombre, desc(total_viajes))
tabla_destinos_vehiculo %>%
group_by(vehiculo_nombre) %>%
slice_max(order_by = total_viajes, n = 5, with_ties = FALSE) %>%
ungroup() %>%
transmute(
vehiculo = vehiculo_nombre,
comuna = comuna_destino_num,
nombre_comuna,
viajes_llegada = total_viajes
) %>%
kable(caption = "Cinco principales comunas de destino por tipo de vehículo")
| vehiculo | comuna | nombre_comuna | viajes_llegada |
|---|---|---|---|
| Automóvil | 2 | Comuna 2 | 2006 |
| Automóvil | 3 | Comuna 3 | 1557 |
| Automóvil | 19 | Comuna 19 | 1302 |
| Automóvil | 17 | Comuna 17 | 959 |
| Automóvil | 4 | Comuna 4 | 757 |
| Bicicleta | 2 | Comuna 2 | 218 |
| Bicicleta | 3 | Comuna 3 | 189 |
| Bicicleta | 19 | Comuna 19 | 147 |
| Bicicleta | 17 | Comuna 17 | 115 |
| Bicicleta | 22 | Comuna 22 | 100 |
| Moto | 2 | Comuna 2 | 2177 |
| Moto | 3 | Comuna 3 | 1734 |
| Moto | 19 | Comuna 19 | 1421 |
| Moto | 17 | Comuna 17 | 927 |
| Moto | 22 | Comuna 22 | 911 |
top_destino_vehiculo <- tabla_destinos_vehiculo %>%
group_by(vehiculo_nombre) %>%
slice_max(order_by = total_viajes, n = 1, with_ties = FALSE) %>%
ungroup()
texto_destino_vehiculo <- top_destino_vehiculo %>%
mutate(
frase = sprintf(
"para %s, el principal destino es la comuna %s (%s), con %s viajes",
vehiculo_nombre,
comuna_destino_num,
nombre_comuna,
formato_num(total_viajes)
)
) %>%
pull(frase) %>%
paste(collapse = "; ")
cat(sprintf(
'<div class="conclusion"><strong>Interpretación de destinos por vehículo.</strong> %s. La comparación entre estos destinos permite identificar comunas atractoras de viajes según el modo de transporte y complementa la lectura de los orígenes.</div>',
texto_destino_vehiculo
))
Para responder la pregunta de destino desde origen, se seleccionan las tres comunas con mayor cantidad de viajes de salida. En cada mapa se resalta con borde negro la comuna de origen y se colorean las comunas de destino según el número de viajes recibidos desde ese origen.
tabla_od <- encuesta_limpia %>%
filter(
!is.na(comuna_origen_num),
!is.na(comuna_destino_num)
) %>%
count(comuna_origen_num, comuna_destino_num, name = "total_viajes") %>%
arrange(desc(total_viajes))
tabla_od %>%
slice_head(n = 20) %>%
kable(caption = "Principales relaciones origen-destino entre comunas")
| comuna_origen_num | comuna_destino_num | total_viajes |
|---|---|---|
| 2 | 2 | 496 |
| 19 | 2 | 492 |
| 17 | 2 | 340 |
| 2 | 19 | 306 |
| 17 | 22 | 294 |
| 18 | 2 | 256 |
| 2 | 3 | 253 |
| 19 | 19 | 248 |
| 3 | 2 | 244 |
| 17 | 3 | 236 |
| 3 | 19 | 216 |
| 18 | 3 | 210 |
| 19 | 3 | 205 |
| 22 | 17 | 186 |
| 10 | 2 | 181 |
| 1 | 2 | 179 |
| 19 | 22 | 179 |
| 2 | 17 | 178 |
| 18 | 19 | 168 |
| 22 | 2 | 164 |
top_3_origenes <- top_origenes$comuna_origen_num[1:3]
for (origen in top_3_origenes) {
print(crear_mapa_destino_desde_origen(origen))
}
for (vehiculo in vehiculos_interes) {
print(crear_mapa_destino_desde_origen(origen_principal, vehiculo))
}
vehiculo_principal <- encuesta_limpia %>%
filter(vehiculo_nombre %in% vehiculos_interes) %>%
count(vehiculo_nombre, sort = TRUE) %>%
slice(1)
cat(sprintf(
'<div class="conclusion"><strong>1.</strong> La comuna %s (%s) concentra el mayor número de viajes de origen dentro de las comunas cartografiables, con %s viajes. Este resultado la identifica como una zona generadora relevante dentro de la muestra.</div>',
origen_principal,
origen_principal_nombre,
formato_num(top_origenes$total_viajes[1])
))
cat(sprintf(
'<div class="conclusion"><strong>2.</strong> La comuna %s (%s) es el principal destino, con %s viajes. Esta diferencia entre origen y destino permite interpretar la movilidad como una relación espacial entre áreas productoras y atractoras de desplazamientos.</div>',
destino_principal,
destino_principal_nombre,
formato_num(top_destinos$total_viajes[1])
))
cat(sprintf(
'<div class="conclusion"><strong>3.</strong> Entre bicicleta, moto y automóvil, el modo con más registros es %s. La comparación cartográfica por vehículo muestra que los patrones espaciales cambian según el medio de transporte, por lo que no conviene interpretar la movilidad únicamente con el mapa general.</div>',
vehiculo_principal$vehiculo_nombre
))
cat(
'<div class="conclusion"><strong>4.</strong> Los mapas de destino desde los principales orígenes permiten pasar de una lectura descriptiva por comuna a una lectura relacional origen-destino. Esta mirada es útil para reconocer flujos, centralidades y posibles diferencias territoriales en la movilidad urbana.</div>'
)
Fuera de Cali, ya que son importantes para la movilidad
metropolitana pero no se representan con la cartografía de comunas usada
en este ejercicio.