La Encuesta de Movilidad 2015 de Cali (Metro Cali S.A.) incluyó una encuesta de interceptación a vehículos privados en 35 estaciones de la ciudad. A cada conductor o pasajero se le preguntó de dónde venía (origen), hacia dónde iba (destino), qué iba a hacer allá (motivo), su estrato, edad, sexo y el tipo de vehículo. Cada registro tiene asignada la comuna de origen y la comuna de destino, lo que permite llevar la encuesta al espacio usando el shapefile de las 22 comunas de Cali.
Objetivo: explorar espacialmente los viajes en vehículo privado de Cali para responder:
Se siguen los mismos pasos del ejemplo de la unidad (importar el
shapefile, hacer consultas espaciales, crear geometrías a partir de una
tabla, transformar el sistema de coordenadas, integrar la información a
nivel de polígono y visualizar con coropletas). Como las librerías
rgdal y raster del ejemplo fueron retiradas de
CRAN en 2023, se usa su reemplazo moderno
sf, que hace las mismas operaciones
(st_read ≈ shapefile,
st_transform ≈ spTransform,
st_join/left_join ≈ over).
Los archivos del caso (Casos.zip) deben estar
descomprimidos en una carpeta Casos/ junto a este archivo
.Rmd.
library(sf) # lectura, transformación y operaciones con datos espaciales
library(readxl) # lectura de archivos Excel
library(dplyr) # manipulación de datos
library(tidyr) # reestructuración de tablas
library(ggplot2) # gráficos y mapas estáticos
library(scales) # formatos de ejes y leyendas
library(knitr) # tablas
ruta_shape <- "Casos/cali/Comunas.shp"
ruta_encuesta <- "Casos/EncuestaOrigenDestino.xlsx"
comunas <- st_read(ruta_shape, quiet = TRUE)
comunas
## Simple feature collection with 22 features and 4 fields
## Geometry type: POLYGON
## Dimension: XY
## Bounding box: xmin: 1053868 ymin: 860190.2 xmax: 1068492 ymax: 879441.5
## Projected CRS: MAGNA_Colombia_Cali
## First 10 features:
## OBJECTID gid comuna nombre geometry
## 1 1 107 2 Comuna 2 POLYGON ((1059648 874236.3,...
## 2 2 108 1 Comuna 1 POLYGON ((1054094 875172.8,...
## 3 3 109 3 Comuna 3 POLYGON ((1061757 874615.5,...
## 4 4 110 19 Comuna 19 POLYGON ((1057817 873269.4,...
## 5 5 103 15 Comuna 15 POLYGON ((1065485 869864.1,...
## 6 6 104 17 Comuna 17 POLYGON ((1061675 868320.9,...
## 7 7 105 18 Comuna 18 POLYGON ((1059061 867223.6,...
## 8 8 106 22 Comuna 22 POLYGON ((1059486 864976.4,...
## 9 9 89 6 Comuna 6 POLYGON ((1065143 879409.5,...
## 10 10 90 4 Comuna 4 POLYGON ((1063833 877507.1,...
st_crs(comunas)$proj4string
## [1] "+proj=tmerc +lat_0=3.441883333 +lon_0=-76.5205625 +k=1 +x_0=1061900.18 +y_0=872364.63 +a=6379137 +rf=298.257222101 +units=m +no_defs"
El shapefile tiene 22 polígonos (una por comuna) y 4 atributos. Está en el sistema de coordenadas planas MAGNA-SIRGAS Cali (Transversa de Mercator, en metros), por lo que sirve para medir áreas y distancias. Para mapas web (leaflet) hay que transformarlo a coordenadas geográficas WGS84 (EPSG:4326).
comunas <- comunas %>%
mutate(comuna = as.integer(comuna),
area_km2 = as.numeric(st_area(geometry)) / 1e6) # área a partir de la geometría
ggplot(comunas) +
geom_sf(fill = "grey92", colour = "grey40") +
geom_sf_text(aes(label = comuna), size = 3.2) +
labs(title = "Comunas de Cali", x = NULL, y = NULL) +
theme_void()
Consulta espacial (como en el ejemplo, filtrando por un campo de la tabla de atributos): se resaltan las comunas 2, 3 y 19, que más adelante aparecen como los principales destinos de viaje.
comunas_interes <- comunas[comunas$comuna %in% c(2, 3, 19), ]
ggplot() +
geom_sf(data = comunas, fill = "grey92", colour = "grey50") +
geom_sf(data = comunas_interes, fill = "firebrick", alpha = 0.7) +
geom_sf_text(data = comunas_interes, aes(label = comuna), colour = "white") +
labs(title = "Consulta espacial: comunas 2, 3 y 19") +
theme_void()
# Exportar el nuevo shapefile (equivale a shapefile(..., overwrite = TRUE) del ejemplo)
st_write(comunas_interes, file.path(tempdir(), "Comunas_interes.shp"),
delete_layer = TRUE, quiet = TRUE)
eod_raw <- read_excel(ruta_encuesta, sheet = "Hoja1", .name_repair = "unique_quiet")
dim(eod_raw)
## [1] 35054 28
La hoja tiene 35054 encuestas y 28 variables. Los nombres originales son largos y tienen tildes y signos, así que se renombran a nombres cortos.
eod <- eod_raw
names(eod) <- c("fecha", "id_estacion", "estacion", "acceso", "movimiento", "hora",
"mun_origen", "lugar_origen", "cod_origen", "actividad_origen",
"mun_destino", "lugar_destino", "cod_destino", "motivo",
"estrato", "disponia_vehiculo", "otro_vehiculo", "antes", "despues",
"edad", "sexo", "personas", "tipo_vehiculo", "otro_tipo",
"tipo_viajero", "comuna_origen", "comuna_destino", "intracomuna")
Se decodifican las variables con las convenciones de la hoja Convenciones (2). La comuna “0” corresponde a viajes con origen o destino en la zona rural de Cali (corregimientos como Pance, La Buitrera o Montebello) y “Fuera de Cali” a otros municipios (Yumbo, Jamundí, Palmira, etc.).
etq_motivo <- c("1" = "Trabajar", "2" = "Asuntos de trabajo", "3" = "Estudiar",
"4" = "Salud", "5" = "Ver a alguien", "6" = "Volver a casa",
"7" = "Buscar/dejar a alguien", "8" = "Buscar/dejar algo",
"9" = "Comer", "10" = "Compras", "11" = "Trámites",
"12" = "Recreación", "13" = "Buscar trabajo", "77" = "Otra cosa")
etq_vehiculo <- c("1" = "Bicicleta", "2" = "Moto", "3" = "Automóvil",
"4" = "Campero", "5" = "Taxi", "6" = "Bus intermunicipal",
"7" = "Buseta intermunicipal", "8" = "Van intermunicipal",
"9" = "Taxi blanco", "77" = "Otro")
eod <- eod %>%
mutate(
motivo_txt = factor(etq_motivo[as.character(motivo)], levels = etq_motivo),
vehiculo_txt = factor(etq_vehiculo[as.character(tipo_vehiculo)], levels = etq_vehiculo),
sexo_txt = factor(sexo, levels = 1:2, labels = c("Femenino", "Masculino")),
hora_num = as.integer(format(hora, "%H")),
# Tipo de lugar de origen y destino
zona_origen = case_when(comuna_origen == "Fuera de Cali" ~ "Fuera de Cali",
comuna_origen == "0" ~ "Rural Cali",
TRUE ~ "Urbano Cali"),
zona_destino = case_when(comuna_destino == "Fuera de Cali" ~ "Fuera de Cali",
comuna_destino == "0" ~ "Rural Cali",
TRUE ~ "Urbano Cali"),
# Comunas como número entero (NA si es rural o fuera de Cali) para unir con el shape
com_o = suppressWarnings(as.integer(comuna_origen)),
com_d = suppressWarnings(as.integer(comuna_destino)),
com_o = ifelse(com_o == 0, NA, com_o),
com_d = ifelse(com_d == 0, NA, com_d)
)
# Validaciones de calidad: valores faltantes en las variables clave
eod %>%
summarise(across(c(comuna_origen, comuna_destino, motivo, estrato, edad,
tipo_vehiculo), ~ sum(is.na(.)))) %>%
kable(caption = "Valores faltantes por variable clave")
| comuna_origen | comuna_destino | motivo | estrato | edad | tipo_vehiculo |
|---|---|---|---|---|---|
| 0 | 0 | 154 | 1231 | 479 | 115 |
Las comunas de origen y destino están completas en todos los registros; los faltantes en motivo, estrato o edad son pocos (menos del 4 %) y solo se excluyen en los análisis que usan esa variable.
Para el análisis espacial interesa el tipo de viaje según dónde empieza y dónde termina:
tabla_zonas <- eod %>% count(zona_origen, zona_destino) %>%
mutate(porcentaje = percent(n / sum(n), accuracy = 0.1)) %>%
arrange(desc(n))
kable(tabla_zonas, caption = "Viajes según zona de origen y destino")
| zona_origen | zona_destino | n | porcentaje |
|---|---|---|---|
| Urbano Cali | Urbano Cali | 22151 | 63.2% |
| Urbano Cali | Fuera de Cali | 5261 | 15.0% |
| Fuera de Cali | Urbano Cali | 5180 | 14.8% |
| Fuera de Cali | Fuera de Cali | 1024 | 2.9% |
| Urbano Cali | Rural Cali | 664 | 1.9% |
| Rural Cali | Urbano Cali | 535 | 1.5% |
| Rural Cali | Fuera de Cali | 115 | 0.3% |
| Fuera de Cali | Rural Cali | 102 | 0.3% |
| Rural Cali | Rural Cali | 22 | 0.1% |
Se separan los viajes urbanos (origen y destino en una comuna de Cali), que son la base de la matriz origen-destino entre comunas.
urbanos <- eod %>% filter(!is.na(com_o), !is.na(com_d))
nrow(urbanos)
## [1] 22151
Población por comuna (2015). La misma hoja de cálculo trae las proyecciones de población por comuna (hoja Sheet3), que se usan para calcular tasas por habitante.
poblacion <- read_excel(ruta_encuesta, sheet = "Sheet3", range = "U3:AB25",
.name_repair = "unique_quiet")
poblacion <- poblacion %>%
select(nombre = 1, pob_2015 = `2015`) %>%
mutate(comuna = as.integer(gsub("Comuna ", "", nombre))) %>%
select(comuna, pob_2015)
head(poblacion)
## # A tibble: 6 × 2
## comuna pob_2015
## <int> <dbl>
## 1 1 88432
## 2 2 114651
## 3 3 46400
## 4 4 53369
## 5 5 112089
## 6 6 189837
eod %>% filter(!is.na(vehiculo_txt)) %>%
count(vehiculo_txt) %>%
ggplot(aes(x = reorder(vehiculo_txt, n), y = n)) +
geom_col(fill = "steelblue") +
geom_text(aes(label = percent(n / sum(n), accuracy = 0.1)), hjust = -0.1, size = 3) +
coord_flip() + scale_y_continuous(expand = expansion(mult = c(0, 0.12))) +
labs(title = "Encuestados según tipo de vehículo", x = NULL, y = "Encuestas") +
theme_minimal()
eod %>% filter(!is.na(motivo_txt)) %>%
count(motivo_txt) %>%
ggplot(aes(x = reorder(motivo_txt, n), y = n)) +
geom_col(fill = "darkorange") +
geom_text(aes(label = percent(n / sum(n), accuracy = 0.1)), hjust = -0.1, size = 3) +
coord_flip() + scale_y_continuous(expand = expansion(mult = c(0, 0.12))) +
labs(title = "Motivo del viaje (¿qué va a hacer en el destino?)", x = NULL, y = "Encuestas") +
theme_minimal()
# Se excluyen 11 registros con hora 00:xx, que son errores de digitación
eod %>% filter(!is.na(hora_num), hora_num >= 5) %>%
ggplot(aes(x = hora_num)) +
geom_bar(fill = "grey40") +
scale_x_continuous(breaks = 6:12) +
labs(title = "Encuestas por hora del día", x = "Hora", y = "Encuestas") +
theme_minimal()
eod %>% filter(!is.na(estrato)) %>%
count(estrato) %>% mutate(p = n / sum(n)) %>%
ggplot(aes(x = factor(estrato), y = p)) +
geom_col(fill = "seagreen") +
geom_text(aes(label = percent(p, accuracy = 0.1)), vjust = -0.3, size = 3) +
scale_y_continuous(labels = percent) +
labs(title = "Estrato de la vivienda del viajero", x = "Estrato", y = "% de encuestas") +
theme_minimal()
eod %>% summarise(
`Edad media` = round(mean(edad, na.rm = TRUE), 1),
`% hombres` = percent(mean(sexo == 2, na.rm = TRUE), 0.1),
`% conductores` = percent(mean(tipo_viajero == 1, na.rm = TRUE), 0.1),
`% viaja solo` = percent(mean(personas == 1, na.rm = TRUE), 0.1),
`% motivo laboral` = percent(mean(motivo %in% c(1, 2), na.rm = TRUE), 0.1),
`% volver a casa` = percent(mean(motivo == 6, na.rm = TRUE), 0.1)
) %>% kable(caption = "Perfil general del viajero en vehículo privado")
| Edad media | % hombres | % conductores | % viaja solo | % motivo laboral | % volver a casa |
|---|---|---|---|---|---|
| 38 | 79.5% | 82.9% | 65.6% | 60.6% | 13.7% |
Se cuentan los viajes generados (comuna de origen) y
atraídos (comuna de destino) por cada comuna. Se
incluyen todos los viajes que empiezan o terminan en una comuna urbana,
también los que vienen de o van hacia fuera de Cali. Luego se unen al
shapefile por el código de comuna; es el mismo resultado que da
over() en el ejemplo, pero aquí la unión es por atributo
porque la encuesta ya trae la comuna asignada.
generados <- eod %>% filter(!is.na(com_o)) %>% count(comuna = com_o, name = "generados")
atraidos <- eod %>% filter(!is.na(com_d)) %>% count(comuna = com_d, name = "atraidos")
intra <- urbanos %>% filter(com_o == com_d) %>% count(comuna = com_o, name = "intra")
comunas_viajes <- comunas %>%
left_join(generados, by = "comuna") %>%
left_join(atraidos, by = "comuna") %>%
left_join(intra, by = "comuna") %>%
left_join(poblacion, by = "comuna") %>%
mutate(across(c(generados, atraidos, intra), ~ coalesce(., 0L)),
balance = atraidos - generados, # >0 polo atractor
indice_atrac = atraidos / generados, # >1 atrae más de lo que genera
gen_x1000hab = generados / pob_2015 * 1000,
atr_x1000hab = atraidos / pob_2015 * 1000,
pct_intra = intra / generados)
comunas_viajes %>% st_drop_geometry() %>%
select(comuna, pob_2015, area_km2, generados, atraidos, balance, indice_atrac,
gen_x1000hab, pct_intra) %>%
arrange(desc(atraidos)) %>%
mutate(across(c(area_km2, indice_atrac, gen_x1000hab), ~ round(., 2)),
pct_intra = percent(pct_intra, 0.1)) %>%
kable(caption = "Viajes en vehículo privado por comuna (ordenado por viajes atraídos)")
| comuna | pob_2015 | area_km2 | generados | atraidos | balance | indice_atrac | gen_x1000hab | pct_intra |
|---|---|---|---|---|---|---|---|---|
| 2 | 114651 | 11.41 | 2968 | 4810 | 1842 | 1.62 | 25.89 | 16.7% |
| 3 | 46400 | 3.70 | 2121 | 3859 | 1738 | 1.82 | 45.71 | 5.4% |
| 19 | 112947 | 11.32 | 2850 | 3158 | 308 | 1.11 | 25.23 | 8.7% |
| 17 | 139665 | 12.57 | 2377 | 2164 | -213 | 0.91 | 17.02 | 4.5% |
| 22 | 11160 | 10.59 | 1153 | 1860 | 707 | 1.61 | 103.32 | 2.9% |
| 4 | 53369 | 4.53 | 1493 | 1856 | 363 | 1.24 | 27.98 | 4.8% |
| 9 | 44994 | 2.90 | 952 | 1279 | 327 | 1.34 | 21.16 | 5.5% |
| 8 | 102388 | 5.27 | 1124 | 1025 | -99 | 0.91 | 10.98 | 2.5% |
| 10 | 110854 | 4.30 | 1401 | 927 | -474 | 0.66 | 12.64 | 1.7% |
| 7 | 71334 | 5.11 | 734 | 748 | 14 | 1.02 | 10.29 | 4.9% |
| 16 | 107170 | 4.26 | 993 | 743 | -250 | 0.75 | 9.27 | 3.8% |
| 6 | 189837 | 5.04 | 998 | 716 | -282 | 0.72 | 5.26 | 2.2% |
| 13 | 177641 | 4.74 | 1238 | 673 | -565 | 0.54 | 6.97 | 2.8% |
| 18 | 131453 | 5.43 | 1536 | 662 | -874 | 0.43 | 11.68 | 3.5% |
| 15 | 159369 | 4.06 | 1171 | 579 | -592 | 0.49 | 7.35 | 2.0% |
| 11 | 107339 | 3.70 | 925 | 564 | -361 | 0.61 | 8.62 | 3.4% |
| 5 | 112089 | 4.20 | 664 | 522 | -142 | 0.79 | 5.92 | 1.2% |
| 21 | 112336 | 4.83 | 818 | 515 | -303 | 0.63 | 7.28 | 1.1% |
| 14 | 172696 | 4.54 | 685 | 379 | -306 | 0.55 | 3.97 | 2.2% |
| 20 | 69331 | 2.44 | 733 | 351 | -382 | 0.48 | 10.57 | 3.3% |
| 12 | 66881 | 2.33 | 348 | 246 | -102 | 0.71 | 5.20 | 2.0% |
| 1 | 88432 | 3.84 | 794 | 230 | -564 | 0.29 | 8.98 | 0.8% |
mapa_long <- comunas_viajes %>%
select(comuna, generados, atraidos) %>%
pivot_longer(c(generados, atraidos), names_to = "tipo", values_to = "viajes") %>%
mutate(tipo = factor(tipo, levels = c("generados", "atraidos"),
labels = c("Viajes generados (origen)", "Viajes atraídos (destino)")))
ggplot(mapa_long) +
geom_sf(aes(fill = viajes), colour = "white", linewidth = 0.3) +
geom_sf_text(aes(label = comuna), size = 2.6) +
facet_wrap(~ tipo) +
scale_fill_distiller(palette = "YlOrRd", direction = 1, labels = comma) +
labs(title = "Viajes en vehículo privado por comuna", fill = "Viajes") +
theme_void() + theme(legend.position = "bottom", legend.key.width = unit(1.5, "cm"))
El balance (atraídos − generados) muestra qué comunas reciben más viajes de los que producen (polos de actividad, en rojo) y cuáles producen más de los que reciben (comunas principalmente residenciales, en azul).
lim <- max(abs(comunas_viajes$balance))
ggplot(comunas_viajes) +
geom_sf(aes(fill = balance), colour = "grey30", linewidth = 0.2) +
geom_sf_text(aes(label = comuna), size = 2.8) +
scale_fill_distiller(palette = "RdBu", direction = -1, limits = c(-lim, lim),
labels = comma) +
labs(title = "Balance de viajes: atraídos − generados",
subtitle = "Rojo: polo atractor · Azul: comuna generadora (residencial)",
fill = "Balance") +
theme_void()
Los conteos absolutos dependen del tamaño de la comuna. Dividir por la población permite comparar mejor la intensidad de uso del vehículo privado.
ggplot(comunas_viajes) +
geom_sf(aes(fill = gen_x1000hab), colour = "white", linewidth = 0.3) +
geom_sf_text(aes(label = comuna), size = 2.8) +
scale_fill_viridis_c(option = "magma", direction = -1) +
labs(title = "Viajes generados por cada 1.000 habitantes (2015)",
fill = "Viajes /\n1.000 hab") +
theme_void()
Se calcula, para cada comuna de destino, la proporción de viajes por motivo laboral (trabajar o asuntos de trabajo), y para cada comuna de origen el estrato promedio de los viajeros.
pct_trabajo <- eod %>% filter(!is.na(com_d), !is.na(motivo)) %>%
group_by(comuna = com_d) %>%
summarise(pct_trabajo = mean(motivo %in% c(1, 2)))
estrato_origen <- eod %>% filter(!is.na(com_o), !is.na(estrato)) %>%
group_by(comuna = com_o) %>%
summarise(estrato_medio = mean(estrato))
comunas_viajes <- comunas_viajes %>%
left_join(pct_trabajo, by = "comuna") %>%
left_join(estrato_origen, by = "comuna")
g1 <- ggplot(comunas_viajes) +
geom_sf(aes(fill = pct_trabajo), colour = "white", linewidth = 0.3) +
geom_sf_text(aes(label = comuna), size = 2.5) +
scale_fill_distiller(palette = "Purples", direction = 1, labels = percent) +
labs(title = "% de viajes atraídos por motivo laboral", fill = NULL) +
theme_void() + theme(legend.position = "bottom")
g2 <- ggplot(comunas_viajes) +
geom_sf(aes(fill = estrato_medio), colour = "white", linewidth = 0.3) +
geom_sf_text(aes(label = comuna), size = 2.5) +
scale_fill_distiller(palette = "Greens", direction = 1) +
labs(title = "Estrato medio de quien viaja desde la comuna", fill = NULL) +
theme_void() + theme(legend.position = "bottom")
g1
g2
cor_tasa_estrato <- with(st_drop_geometry(comunas_viajes),
cor.test(gen_x1000hab, estrato_medio, method = "spearman"))
cor_tasa_estrato
##
## Spearman's rank correlation rho
##
## data: gen_x1000hab and estrato_medio
## S = 1278, p-value = 0.2089
## alternative hypothesis: true rho is not equal to 0
## sample estimates:
## rho
## 0.2783738
od <- urbanos %>% count(com_o, com_d, name = "viajes")
ggplot(od, aes(x = factor(com_d), y = factor(com_o), fill = viajes)) +
geom_tile(colour = "white") +
scale_fill_distiller(palette = "Blues", direction = 1, trans = "log10", labels = comma) +
labs(title = "Matriz origen-destino entre comunas (escala logarítmica)",
x = "Comuna de destino", y = "Comuna de origen", fill = "Viajes") +
coord_equal() + theme_minimal(base_size = 9)
od %>% filter(com_o != com_d) %>% arrange(desc(viajes)) %>% head(10) %>%
mutate(`% de viajes urbanos` = percent(viajes / nrow(urbanos), 0.1)) %>%
kable(col.names = c("Comuna origen", "Comuna destino", "Viajes", "% de viajes urbanos"),
caption = "Los 10 pares origen-destino entre comunas con más viajes")
| Comuna origen | Comuna destino | Viajes | % de viajes urbanos |
|---|---|---|---|
| 19 | 2 | 492 | 2.2% |
| 17 | 2 | 340 | 1.5% |
| 2 | 19 | 306 | 1.4% |
| 17 | 22 | 294 | 1.3% |
| 18 | 2 | 256 | 1.2% |
| 2 | 3 | 253 | 1.1% |
| 3 | 2 | 244 | 1.1% |
| 17 | 3 | 236 | 1.1% |
| 3 | 19 | 216 | 1.0% |
| 18 | 3 | 210 | 0.9% |
Las líneas de deseo unen los centroides de la comuna de origen y la de destino; su grosor es proporcional al número de viajes. Aquí se construye una geometría de líneas a partir de una tabla, igual que en el ejemplo se construyeron puntos a partir de coordenadas de Excel. Se muestran los 40 pares con más viajes, sin contar los viajes dentro de la misma comuna.
centroides <- st_centroid(st_geometry(comunas))
coords_cent <- data.frame(comuna = comunas$comuna, st_coordinates(centroides))
top_od <- od %>% filter(com_o != com_d) %>% slice_max(viajes, n = 40) %>%
left_join(coords_cent, by = c("com_o" = "comuna")) %>%
left_join(coords_cent, by = c("com_d" = "comuna"), suffix = c("_o", "_d"))
lineas <- lapply(seq_len(nrow(top_od)), function(i) {
st_linestring(matrix(c(top_od$X_o[i], top_od$Y_o[i],
top_od$X_d[i], top_od$Y_d[i]), ncol = 2, byrow = TRUE))
})
lineas_od <- st_sf(top_od[, c("com_o", "com_d", "viajes")],
geometry = st_sfc(lineas, crs = st_crs(comunas)))
ggplot() +
geom_sf(data = comunas_viajes, aes(fill = atraidos), colour = "white", linewidth = 0.3) +
geom_sf(data = lineas_od, aes(linewidth = viajes), colour = "black", alpha = 0.55) +
geom_sf_text(data = comunas, aes(label = comuna), size = 2.6, colour = "grey15") +
scale_fill_distiller(palette = "YlOrRd", direction = 1, labels = comma) +
scale_linewidth(range = c(0.3, 3)) +
labs(title = "Líneas de deseo: 40 principales pares origen-destino",
fill = "Viajes atraídos", linewidth = "Viajes del par") +
theme_void()
externos <- eod %>% filter(zona_origen == "Fuera de Cali", !is.na(com_d))
# 737 registros marcados "Fuera de Cali" tienen como municipio CALI (p. ej. Dapa o
# vías de salida); se dejan en el conteo por comuna pero no en la tabla de municipios
externos %>% filter(mun_origen != "CALI") %>%
count(mun_origen, sort = TRUE) %>% head(8) %>%
kable(col.names = c("Municipio de origen", "Viajes hacia comunas de Cali"),
caption = "Principales municipios de origen de viajes que entran a Cali")
| Municipio de origen | Viajes hacia comunas de Cali |
|---|---|
| YUMBO | 1331 |
| JAMUNDI | 1055 |
| PALMIRA | 813 |
| CANDELARIA | 254 |
| LA UNION | 111 |
| SANTANDER DE QUILICHAO | 79 |
| GUADALAJARA DE BUGA | 72 |
| PUERTO TEJADA | 52 |
comunas_ext <- comunas %>%
left_join(externos %>% count(comuna = com_d, name = "desde_fuera"), by = "comuna")
ggplot(comunas_ext) +
geom_sf(aes(fill = desde_fuera), colour = "white", linewidth = 0.3) +
geom_sf_text(aes(label = comuna), size = 2.6) +
scale_fill_distiller(palette = "OrRd", direction = 1, na.value = "grey90") +
labs(title = "Comuna de destino de los viajes que llegan desde otros municipios",
fill = "Viajes") +
theme_void()
Como en el ejemplo, para usar leaflet el shape debe
estar en coordenadas geográficas, por eso primero se transforma a WGS84
con st_transform (equivalente a
spTransform).
library(leaflet)
comunas_wgs84 <- st_transform(comunas_viajes, crs = 4326)
pal <- colorNumeric("YlOrRd", domain = comunas_wgs84$atraidos)
leaflet(comunas_wgs84) %>%
addTiles() %>%
addPolygons(fillColor = ~pal(atraidos), fillOpacity = 0.7,
color = "white", weight = 1,
label = ~paste0("Comuna ", comuna, ": ", atraidos, " atraídos / ",
generados, " generados"),
highlightOptions = highlightOptions(weight = 3, color = "black")) %>%
addLegend(pal = pal, values = ~atraidos, title = "Viajes atraídos")
Antes de interpretar, hay que tener en cuenta cómo se tomaron los datos. Es una encuesta de interceptación: solo incluye vehículos privados (motos, carros, taxis, bicicletas) que pasaron por alguna de las 35 estaciones, entre las 6:00 y las 12:59 de días hábiles de mayo y junio de 2015. Los conteos no se expandieron con los factores de expansión. Por eso los resultados muestran la movilidad privada de la mañana y dependen en parte de dónde se ubicaron las estaciones. No representan todos los viajes de la ciudad.
Perfil del viaje. El viajero típico va en moto (46 %) o automóvil (40 %). Es hombre (80 %), conduce el vehículo (83 %), va solo (66 %), tiene unos 38 años y es de estrato 2 o 3 (65 %). El 61 % de los viajes tiene motivo laboral (trabajar o asuntos de trabajo) y apenas un 14 % es para volver a casa. Esto es lo que se espera en la mañana: la gente sale de su casa hacia el trabajo. Por eso la comuna de origen se puede leer en general como comuna de residencia y la de destino como comuna de actividad.
Generación y atracción (pregunta 1). Los destinos están muy concentrados. Las comunas 2, 3 y 19 atraen el 42 % de los viajes con destino urbano (4.810, 3.859 y 3.158 viajes). La comuna 2 incluye el norte empresarial y comercial (Granada, Chipichape, Centenario), la 3 es el centro tradicional y administrativo, y la 19 concentra clínicas, comercio y servicios (San Fernando, Tequendama). Las mismas comunas 2, 19 y 3 también son las que más viajes generan, junto con la 17, porque tienen mucha actividad y mucho tráfico privado en las dos direcciones.
Polos de actividad frente a comunas residenciales (pregunta 2). El mapa de balance divide la ciudad en dos:
Según el mapa de tasas por habitante, en la comuna 22 (103 viajes por cada 1.000 habitantes) y la 3 (46) el uso del vehículo privado es mucho más intenso que en el oriente (14: 4; 6: 5; 13: 7). Esto coincide con las diferencias de ingreso y de tenencia de carro conocidas en Cali, aunque también influye que hay menos estaciones en esa zona. La relación entre la tasa de generación y el estrato medio del viajero por comuna es positiva pero no significativa (Spearman ρ = 0,28; p = 0,21). Además, el estrato medio varía muy poco entre comunas (2,7 a 3,1), así que el estrato reportado en esta encuesta no basta para explicar las diferencias espaciales. Lo mismo pasa con el porcentaje de viajes por trabajo según la comuna de destino, que va de 57 % a 64 %: el motivo laboral domina en toda la ciudad.
Corredores origen-destino (pregunta 3). Solo el 6,7 % de los viajes urbanos se queda en la misma comuna. Casi todos los viajes en vehículo privado cruzan límites de comuna, y la comuna 2 es la que más viajes internos tiene (17 %). La matriz OD y las líneas de deseo muestran un corredor sur-norte muy marcado. Los pares más fuertes son 19→2 (492 viajes), 17→2 (340), 2→19 (306), 17→22 (294), 18→2 (256) y 17→3 (236). Es decir, el sur residencial (17, 18, 19, 10) se dirige al norte (2) y al centro (3), y la comuna 17 también alimenta el polo universitario de la 22. Las comunas del oriente aparecen poco en las líneas de deseo.
Relación con la región. El 15 % de los viajes llega desde otro municipio, sobre todo Yumbo, Jamundí y Palmira, y se dirige otra vez a las comunas 2, 3 y 19. Esto confirma que estas comunas son centros de empleo para toda el área metropolitana y no solo para Cali.
Calidad de los datos. Las comunas de origen y destino están completas. Sin embargo, 737 registros marcados como “Fuera de Cali” tienen como municipio CALI, y hay 11 horas mal registradas (00:xx). Son pocos casos y no cambian los patrones, pero se deben revisar si se quiere expandir la muestra.