En esta actividad se realizará un análisis espacial de los trayectos reportados en la Encuesta Origen–Destino para la ciudad de Cali y su área metropolitana. El objetivo es identificar patrones territoriales de movilidad, tanto desde el punto de partida de los viajes como en su destino final.
Primero, se construirá un mapa temático para visualizar de qué comunas salen con mayor frecuencia los viajes en términos generales. Posteriormente, este análisis se desagregará por tipo de vehículo utilizado, específicamente:
Bicicleta
Moto
Automóvil
Luego, se realizará el mismo procedimiento para los destinos, con el fin de identificar hacia qué comunas se dirigen principalmente los trayectos. De igual manera, se aplicará la misma desagregación por categoría de vehículo (bicicleta, moto y automóvil) para comparar patrones diferenciales de movilidad según el medio de transporte utilizado.
Este proceso permitirá comprender la distribución espacial de la movilidad urbana y evidenciar zonas de mayor flujo según el tipo de transporte, lo cual resulta útil para fines de planificación urbana y transporte.
datos <- read_excel("Casos/EncuestaOrigenDestino.xlsx", sheet = "Hoja1") %>%
clean_names() %>%
rename(tipo_vehiculo = tipo_de_vehiculo) %>%
mutate(
comuna_origen_nom = str_to_upper(paste("COMUNA", as.numeric(comuna_origen))),
comuna_destino_nom = str_to_upper(paste("COMUNA", as.numeric(comuna_destino))),
tipo_vehiculo = case_when(
tipo_vehiculo == 1 ~ "Bicicleta",
tipo_vehiculo == 2 ~ "Moto",
tipo_vehiculo == 3 ~ "Automovil",
TRUE ~ NA_character_
)
)
comunas <- st_read("Casos/cali/Comunas.shp", quiet = TRUE) %>%
st_transform(4326) %>% # CRS WGS84
mutate(nombre = str_to_upper(str_trim(nombre)))
agg_origen <- datos %>%
count(comuna_origen_nom, name = "viajes_origen")
agg_destino <- datos %>%
count(comuna_destino_nom, name = "viajes_destino")
# ---- Unir al shapefile (conserva clase sf) ----
comunas_origen <- comunas %>%
left_join(agg_origen, by = c("nombre" = "comuna_origen_nom")) %>%
mutate(
viajes_origen = if_else(is.na(viajes_origen), 0L, viajes_origen),
viajes_origen_fmt = comma(viajes_origen)
)
comunas_destino <- comunas %>%
left_join(agg_destino, by = c("nombre" = "comuna_destino_nom")) %>%
mutate(
viajes_destino = if_else(is.na(viajes_destino), 0L, viajes_destino),
viajes_destino_fmt = comma(viajes_destino)
)En la etapa de preparación se cargaron los datos de la encuesta, se estandarizaron los nombres de variables y se recodificaron los campos necesarios para el análisis. En particular, se construyeron etiquetas textuales para las comunas de origen y destino, se transformó a mayúsculas la nomenclatura para facilitar empates posteriores y se reemplazaron los códigos numéricos del tipo de vehículo por sus categorías equivalentes en texto. Adicionalmente, se cargó el shapefile de comunas de Cali, se transformó su sistema de coordenadas a WGS84 y se homogenizó el formato del nombre de la comuna para asegurar consistencia con la base de viajes.
tmap_mode("view")
tm_shape(comunas_origen) +
tm_polygons(
col = "viajes_origen",
style = "quantile",
n = 6,
palette = "-viridis",
alpha = 0.85,
id = "nombre",
popup.vars = c("Comuna" = "nombre", "Viajes" = "viajes_origen_fmt"),
title = "Cantidad de viajes"
) +
tm_layout(
title = "Origen de los viajes",
title.size = 1.25,
frame = FALSE,
legend.outside = TRUE,
legend.bg.color = "white",
legend.bg.alpha = 0.9,
inner.margins = c(0.02, 0.08, 0.02, 0.02)
) +
tm_compass(position = c("left", "bottom"), type = "8star", size = 2) +
tm_scale_bar(position = c("left", "bottom")) +
tm_credits(
"Fuente: Encuesta Origen–Destino (Cali). Elaboración propia.",
position = c("RIGHT", "BOTTOM"),
size = 0.8
)comunas_origen_bici <- comunas %>%
left_join(
datos %>%
filter(tipo_vehiculo == "Bicicleta") %>%
count(comuna_origen_nom, name = "viajes_bici"),
by = c("nombre" = "comuna_origen_nom")
) %>%
mutate(
viajes_bici = if_else(is.na(viajes_bici), 0L, viajes_bici),
viajes_bici_fmt = comma(viajes_bici)
)
tmap_mode("view")
tm_shape(comunas_origen_bici) +
tm_polygons(
col = "viajes_bici",
style = "quantile",
n = 6,
palette = "-viridis",
alpha = 0.85,
id = "nombre",
popup.vars = c(
"Comuna" = "nombre",
"Viajes en bicicleta" = "viajes_bici_fmt"
),
title = "Cantidad de viajes (bici)"
) +
tm_layout(
title = "Origen de los viajes — solo bicicletas",
title.size = 1.25,
frame = FALSE,
legend.outside = TRUE,
legend.bg.color = "white",
legend.bg.alpha = 0.9,
inner.margins = c(0.02, 0.08, 0.02, 0.02)
) +
tm_compass(position = c("left", "bottom"), type = "8star", size = 2) +
tm_scale_bar(position = c("left", "bottom")) +
tm_credits(
"Fuente: Encuesta Origen–Destino (Cali). Elaboración propia.",
position = c("RIGHT", "BOTTOM"),
size = 0.8
)comunas_origen_moto <- comunas %>%
left_join(
datos %>%
filter(tipo_vehiculo == "Moto") %>%
count(comuna_origen_nom, name = "viajes_moto"),
by = c("nombre" = "comuna_origen_nom")
) %>%
mutate(
viajes_moto = if_else(is.na(viajes_moto), 0L, viajes_moto),
viajes_moto_fmt = comma(viajes_moto)
)
tmap_mode("view")
tm_shape(comunas_origen_moto) +
tm_polygons(
col = "viajes_moto",
style = "quantile",
n = 6,
palette = "-viridis",
alpha = 0.85,
id = "nombre",
popup.vars = c(
"Comuna" = "nombre",
"Viajes en moto" = "viajes_moto_fmt"
),
title = "Cantidad de viajes (moto)"
) +
tm_layout(
title = "Origen de los viajes — solo moto",
title.size = 1.25,
frame = FALSE,
legend.outside = TRUE,
legend.bg.color = "white",
legend.bg.alpha = 0.9,
inner.margins = c(0.02, 0.08, 0.02, 0.02)
) +
tm_compass(position = c("left", "bottom"), type = "8star", size = 2) +
tm_scale_bar(position = c("left", "bottom")) +
tm_credits(
"Fuente: Encuesta Origen–Destino (Cali). Elaboración propia.",
position = c("RIGHT", "BOTTOM"),
size = 0.8
)library(dplyr)
library(scales)
library(tmap)
to_utf8 <- function(x) enc2utf8(iconv(x, from = "", to = "UTF-8"))
datos <- datos %>% mutate(across(where(is.character), to_utf8))
comunas <- comunas %>% mutate(across(where(is.character), to_utf8))
comunas_origen_auto <- comunas %>%
left_join(
datos %>%
filter(tipo_vehiculo == "Automovil") %>%
count(comuna_origen_nom, name = "viajes_auto"),
by = c("nombre" = "comuna_origen_nom")
) %>%
mutate(
viajes_auto = if_else(is.na(viajes_auto), 0L, viajes_auto),
viajes_auto_fmt = comma(viajes_auto)
) %>%
mutate(across(where(is.character), to_utf8))
tmap_mode("view")
tm_shape(comunas_origen_auto) +
tm_polygons(
col = "viajes_auto",
style = "quantile",
n = 6,
palette = "-viridis",
alpha = 0.85,
id = "nombre",
popup.vars = c(
"Comuna" = "nombre",
"Viajes en automovil" = "viajes_auto_fmt"
),
title = "Cantidad de viajes (automovil)"
) +
tm_layout(
title = "Origen de los viajes - solo automovil",
title.size = 1.25,
frame = FALSE,
legend.outside = TRUE,
legend.bg.color = "white",
legend.bg.alpha = 0.9,
inner.margins = c(0.02, 0.08, 0.02, 0.02)
) +
tm_compass(position = c("left","bottom"), type = "8star", size = 2) +
tm_scale_bar(position = c("left","bottom")) +
tm_credits(
"Fuente: Encuesta Origen Destino (Cali). Elaboracion propia.",
position = c("RIGHT","BOTTOM"),
size = 0.8
)# Calcular top 5 filtrando tanto NA como "COMUNA NA"
top5_general <- datos %>%
filter(!is.na(comuna_origen_nom) & comuna_origen_nom != "COMUNA NA") %>%
count(comuna_origen_nom, name = "cantidad") %>%
arrange(desc(cantidad)) %>%
slice_head(n = 5) %>%
mutate(ranking = 1:5)
top5_bicicleta <- datos %>%
filter(tipo_vehiculo == "Bicicleta" &
!is.na(comuna_origen_nom) & comuna_origen_nom != "COMUNA NA") %>%
count(comuna_origen_nom, name = "cantidad") %>%
arrange(desc(cantidad)) %>%
slice_head(n = 5) %>%
mutate(ranking = 1:5)
top5_moto <- datos %>%
filter(tipo_vehiculo == "Moto" &
!is.na(comuna_origen_nom) & comuna_origen_nom != "COMUNA NA") %>%
count(comuna_origen_nom, name = "cantidad") %>%
arrange(desc(cantidad)) %>%
slice_head(n = 5) %>%
mutate(ranking = 1:5)
top5_automovil <- datos %>%
filter(tipo_vehiculo == "Automovil" &
!is.na(comuna_origen_nom) & comuna_origen_nom != "COMUNA NA") %>%
count(comuna_origen_nom, name = "cantidad") %>%
arrange(desc(cantidad)) %>%
slice_head(n = 5) %>%
mutate(ranking = 1:5)
# Crear tabla consolidada
tabla_final <- data.frame(
TOP = 1:5,
Comunas_Origen_Viajes = top5_general$comuna_origen_nom,
Cantidad_General = top5_general$cantidad,
Comunas_Origen_Bicicleta = top5_bicicleta$comuna_origen_nom,
Cantidad_Bicicleta = top5_bicicleta$cantidad,
Comunas_Origen_Moto = top5_moto$comuna_origen_nom,
Cantidad_Moto = top5_moto$cantidad,
Comunas_Origen_Automovil = top5_automovil$comuna_origen_nom,
Cantidad_Automovil = top5_automovil$cantidad
)
# Mostrar tabla
knitr::kable(tabla_final,
caption = "Top 5 Comunas con Mayor Número de Viajes de Origen por Tipo de Vehículo",
col.names = c("TOP", "Comunas Origen viajes", "Cantidad",
"Comunas Origen - Bicicleta", "Cantidad",
"Comunas Origen - Moto", "Cantidad",
"Comunas Origen - Automóvil", "Cantidad"))| TOP | Comunas Origen viajes | Cantidad | Comunas Origen - Bicicleta | Cantidad | Comunas Origen - Moto | Cantidad | Comunas Origen - Automóvil | Cantidad |
|---|---|---|---|---|---|---|---|---|
| 1 | COMUNA 2 | 2968 | COMUNA 2 | 159 | COMUNA 2 | 1414 | COMUNA 19 | 1165 |
| 2 | COMUNA 19 | 2850 | COMUNA 17 | 141 | COMUNA 19 | 1280 | COMUNA 2 | 1121 |
| 3 | COMUNA 17 | 2377 | COMUNA 19 | 141 | COMUNA 17 | 1136 | COMUNA 17 | 899 |
| 4 | COMUNA 3 | 2121 | COMUNA 18 | 111 | COMUNA 3 | 970 | COMUNA 3 | 854 |
| 5 | COMUNA 18 | 1536 | COMUNA 3 | 99 | COMUNA 18 | 746 | COMUNA 18 | 559 |
Concentración primaria del origen de viajes. Las salidas se concentran en Comuna 2 (2.968), seguida por Comuna 19 (2.850) y Comuna 17 (2.377). Comuna 3 (2.121) y Comuna 18 (1.536) completan el top 5, evidenciando una estructura espacial altamente concentrada en cinco nodos.
Liderazgos diferenciados por modo.
La primacía de Comuna 2 como mayor generadora total y nodo dominante en moto sugiere que allí se originan flujos intensivos de partida. El desplazamiento del liderazgo automotor hacia Comuna 19 indica una centralidad alterna para viajes en carro. La mayor paridad en bicicleta entre Comunas 2, 17 y 19, con presencia secundaria de Comunas 18 y 3, sugiere que este modo depende de desplazamientos cortos/medios en varios nodos, no de una sola centralidad.
tm_shape(comunas_destino) +
tm_polygons(
col = "viajes_destino",
style = "quantile",
n = 6,
palette = "PuBuGn",
alpha = 0.85,
id = "nombre",
popup.vars = c("Comuna" = "nombre", "Viajes destino" = "viajes_destino_fmt"),
title = "Cantidad de viajes"
) +
tm_layout(
title = "Destino de los viajes",
title.size = 1.25,
frame = FALSE,
legend.outside = TRUE,
legend.bg.color = "white",
legend.bg.alpha = 0.9,
inner.margins = c(0.02, 0.08, 0.02, 0.02)
) +
tm_compass(position = c("left", "bottom"), type = "8star", size = 2) +
tm_scale_bar(position = c("left", "bottom")) +
tm_credits(
"Fuente: Encuesta Origen–Destino (Cali). Elaboración propia.",
position = c("RIGHT", "BOTTOM"),
size = 0.8
)comunas_destino_bici <- comunas %>%
left_join(
datos %>%
filter(tipo_vehiculo == "Bicicleta") %>%
count(comuna_destino_nom, name = "viajes_bici_dest"),
by = c("nombre" = "comuna_destino_nom")
) %>%
mutate(
viajes_bici_dest = if_else(is.na(viajes_bici_dest), 0L, viajes_bici_dest),
viajes_bici_dest_fmt = comma(viajes_bici_dest)
)
tmap_mode("view")
tm_shape(comunas_destino_bici) +
tm_polygons(
col = "viajes_bici_dest",
style = "quantile",
n = 6,
palette = "PuBuGn",
alpha = 0.85,
id = "nombre",
popup.vars = c(
"Comuna" = "nombre",
"Viajes en bicicleta (destino)" = "viajes_bici_dest_fmt"
),
title = "Cantidad de viajes (bici)"
) +
tm_layout(
title = "Destino de los viajes — solo bicicletas",
title.size = 1.25,
frame = FALSE,
legend.outside = TRUE,
legend.bg.color = "white",
legend.bg.alpha = 0.9,
inner.margins = c(0.02, 0.08, 0.02, 0.02)
) +
tm_compass(position = c("left", "bottom"), type = "8star", size = 2) +
tm_scale_bar(position = c("left", "bottom")) +
tm_credits(
"Fuente: Encuesta Origen–Destino (Cali). Elaboración propia.",
position = c("RIGHT", "BOTTOM"),
size = 0.8
)comunas_destino_moto <- comunas %>%
left_join(
datos %>%
filter(tipo_vehiculo == "Moto") %>%
count(comuna_destino_nom, name = "viajes_moto_dest"),
by = c("nombre" = "comuna_destino_nom")
) %>%
mutate(
viajes_moto_dest = if_else(is.na(viajes_moto_dest), 0L, viajes_moto_dest),
viajes_moto_dest_fmt = scales::comma(viajes_moto_dest)
)
tmap_mode("view")
tm_shape(comunas_destino_moto) +
tm_polygons(
col = "viajes_moto_dest",
style = "quantile",
n = 6,
palette = "PuBuGn",
alpha = 0.85,
id = "nombre",
popup.vars = c(
"Comuna" = "nombre",
"Viajes en moto (destino)" = "viajes_moto_dest_fmt"
),
title = "Cantidad de viajes (moto)"
) +
tm_layout(
title = "Destino de los viajes — solo moto",
title.size = 1.25,
frame = FALSE,
legend.outside = TRUE,
legend.bg.color = "white",
legend.bg.alpha = 0.9,
inner.margins = c(0.02, 0.08, 0.02, 0.02)
) +
tm_compass(position = c("left", "bottom"), type = "8star", size = 2) +
tm_scale_bar(position = c("left", "bottom")) +
tm_credits(
"Fuente: Encuesta Origen–Destino (Cali). Elaboración propia.",
position = c("RIGHT", "BOTTOM"),
size = 0.8
)to_utf8 <- function(x) enc2utf8(iconv(x, from = "", to = "UTF-8"))
datos <- datos %>% mutate(across(where(is.character), to_utf8))
comunas <- comunas %>% mutate(across(where(is.character), to_utf8))
comunas_destino_auto <- comunas %>%
left_join(
datos %>%
filter(tipo_vehiculo == "Automovil") %>%
count(comuna_destino_nom, name = "viajes_auto_dest"),
by = c("nombre" = "comuna_destino_nom")
) %>%
mutate(
viajes_auto_dest = if_else(is.na(viajes_auto_dest), 0L, viajes_auto_dest),
viajes_auto_dest_fmt = comma(viajes_auto_dest)
) %>%
mutate(across(where(is.character), to_utf8))
tmap_mode("view")
tm_shape(comunas_destino_auto) +
tm_polygons(
col = "viajes_auto_dest",
style = "quantile",
n = 6,
palette = "PuBuGn",
alpha = 0.85,
id = "nombre",
popup.vars = c(
"Comuna" = "nombre",
"Viajes en automovil (destino)" = "viajes_auto_dest_fmt"
),
title = "Cantidad de viajes (automovil)"
) +
tm_layout(
title = "Destino de los viajes - solo automovil",
title.size = 1.25,
frame = FALSE,
legend.outside = TRUE,
legend.bg.color = "white",
legend.bg.alpha = 0.9,
inner.margins = c(0.02, 0.08, 0.02, 0.02)
) +
tm_compass(position = c("left","bottom"), type = "8star", size = 2) +
tm_scale_bar(position = c("left","bottom")) +
tm_credits(
enc2utf8("Fuente: Encuesta Origen Destino (Cali). Elaboracion propia."),
position = c("RIGHT","BOTTOM"),
size = 0.8
)# Función para calcular top 5 de DESTINO limpio
calcular_top5_destino <- function(datos, vehiculo = NULL) {
if (!is.null(vehiculo)) {
datos_filtrados <- datos %>%
filter(tipo_vehiculo == vehiculo)
} else {
datos_filtrados <- datos
}
datos_filtrados %>%
filter(!is.na(comuna_destino_nom) & comuna_destino_nom != "COMUNA NA") %>%
count(comuna_destino_nom, name = "cantidad") %>%
arrange(desc(cantidad)) %>%
slice_head(n = 5) %>%
mutate(ranking = 1:5)
}
# Calcular todos los top 5 de DESTINO
top5_destino_general <- calcular_top5_destino(datos)
top5_destino_bicicleta <- calcular_top5_destino(datos, "Bicicleta")
top5_destino_moto <- calcular_top5_destino(datos, "Moto")
top5_destino_automovil <- calcular_top5_destino(datos, "Automovil")
# Crear tabla final de DESTINO
tabla_destino_final <- data.frame(
TOP = 1:5,
Comunas_Destino_Viajes = top5_destino_general$comuna_destino_nom,
Cantidad_General = top5_destino_general$cantidad,
Comunas_Destino_Bicicleta = top5_destino_bicicleta$comuna_destino_nom,
Cantidad_Bicicleta = top5_destino_bicicleta$cantidad,
Comunas_Destino_Moto = top5_destino_moto$comuna_destino_nom,
Cantidad_Moto = top5_destino_moto$cantidad,
Comunas_Destino_Automovil = top5_destino_automovil$comuna_destino_nom,
Cantidad_Automovil = top5_destino_automovil$cantidad
)
# Mostrar tabla de DESTINO
knitr::kable(tabla_destino_final,
caption = "Top 5 Comunas con Mayor Número de Viajes de Destino por Tipo de Vehículo",
col.names = c("TOP", "Comunas Destino viajes", "Cantidad",
"Comunas Destino - Bicicleta", "Cantidad",
"Comunas Destino - Moto", "Cantidad",
"Comunas Destino - Automóvil", "Cantidad"))| TOP | Comunas Destino viajes | Cantidad | Comunas Destino - Bicicleta | Cantidad | Comunas Destino - Moto | Cantidad | Comunas Destino - Automóvil | Cantidad |
|---|---|---|---|---|---|---|---|---|
| 1 | COMUNA 2 | 4810 | COMUNA 2 | 218 | COMUNA 2 | 2177 | COMUNA 2 | 2006 |
| 2 | COMUNA 3 | 3859 | COMUNA 3 | 189 | COMUNA 3 | 1734 | COMUNA 3 | 1557 |
| 3 | COMUNA 19 | 3158 | COMUNA 19 | 147 | COMUNA 19 | 1421 | COMUNA 19 | 1302 |
| 4 | COMUNA 17 | 2164 | COMUNA 17 | 115 | COMUNA 17 | 927 | COMUNA 17 | 959 |
| 5 | COMUNA 22 | 1860 | COMUNA 22 | 100 | COMUNA 22 | 911 | COMUNA 4 | 757 |
Alta concentración en un único nodo. El destino dominante es Comuna 2 (4.810 viajes), seguida de Comuna 3 (3.859) y Comuna 19 (3.158). Más atrás se ubican Comuna 17 (2.164) y Comuna 22 (1.860), lo que confirma una estructura fuertemente centralizada en torno a tres polos (2–3–19).
Coincidencia modal en el liderazgo del destino. A diferencia del origen, el liderazgo por modo es unificado en favor de Comuna 2:
Estabilidad del orden jerárquico. El orden 2 > 3 > 19 > 17 se mantiene en los tres modos motorizados, variando solo el quinto puesto (Comuna 22 en bici y moto, Comuna 4 en automóvil).
La posición de Comuna 2 como principal imán de viajes en todos los modos indica una concentración funcional del destino urbano que no se fragmenta por modo. Comuna 3 y Comuna 19 conforman un segundo cinturón de atracción, mientras que Comunas 17 y 22 ingresan como destinos secundarios con menor peso relativo.
El análisis se basa exclusivamente en viajes de destino. No se infieren tasas relativas ni patrones de causalidad, pues no se incluyen atributos socioeconómicos, usos del suelo ni tiempos de viaje. Las cifras se reportan exactamente según la tabla original y se interpretan solo en términos de estructura jerárquica del destino.
El análisis conjunto de los viajes de origen y destino revela una estructura de movilidad urbana fuertemente concentrada en pocos nodos territoriales, con patrones modales que no se distribuyen de manera aleatoria sino siguiendo centralidades funcionales diferenciadas. En el origen, Comuna 2, Comuna 19 y Comuna 17 conforman el núcleo generador, mientras que en el destino la supremacía de Comuna 2 es consistente en los tres modos analizados, acompañada de Comuna 3 y Comuna 19 como corredores de absorción de demanda.
La moto actúa como modo dominante en la generación desde Comuna 2, mientras que el automóvil presenta liderazgos compartidos entre Comunas 19 y 2, y la bicicleta mantiene volúmenes inferiores pero con una distribución más equilibrada entre las comunas líderes. Estos resultados evidencian que la movilidad urbana no responde a una sola lógica modal, sino a un ensamblaje jerárquico de nodos y modos que requiere intervenciones diferenciadas.
En términos de planificación, se concluye que no es suficiente intervenir la red de manera uniforme: la gestión debe priorizar Comuna 2 como nodo crítico tanto de partida como de llegada, reforzar estrategias de manejo de demanda en Comunas 19 y 3, y consolidar infraestructura ciclista en los corredores que conectan las comunas líderes con los polos secundarios. La comprensión de estas jerarquías de movilidad constituye un insumo clave para orientar políticas basadas en evidencia y diseñar intervenciones focalizadas que optimicen el desempeño del sistema urbano de transporte.