¿Dónde empiezan y terminan los viajes registrados dentro de Cali y cómo cambia su distribución según el medio de transporte?
Este trabajo analiza la distribución de los orígenes y destinos de los desplazamientos registrados dentro de Cali, tanto de forma general como por medio de transporte. El análisis integra la preparación de la encuesta, la verificación de calidad de los datos, la construcción de mapas interactivos, una matriz origen–destino y un balance territorial por comuna.
Los datos de los desplazamientos provienen del archivo
EncuestaOrigenDestino.xlsx, leído en la
hoja Hoja1. Para ubicar los resultados se
utiliza la cartografía Comunas.shp, que
contiene los límites de las comunas de Cali. Son dos insumos diferentes:
el Excel describe los registros de movilidad y la capa geográfica
permite representarlos en el territorio.
Los registros con fecha válida abarcan del
04/05/2015 al 09/06/2015, según la
columna fechas del Excel. Este intervalo corresponde a las
fechas observadas en la base.
El objetivo es reconocer dónde se concentran las salidas y llegadas, comparar los modos de transporte y cuantificar las diferencias entre las comunas, sus conexiones y su balance de salidas y llegadas. Antes de interpretar los patrones, revisamos qué registros cuentan con información suficiente para ubicarlos.
La unidad de análisis es un registro de desplazamiento. Los resultados no representan personas únicas ni todos los viajes de Cali: para hacer esa estimación se necesitan factores de expansión y conocer el diseño muestral.
Usaremos R para organizar los datos y hacer los cálculos, y RStudio como entorno de trabajo para abrir y ejecutar el archivo. El documento está escrito en R Markdown: en él conviven el texto, el código y los resultados, de modo que las tablas, los gráficos y los mapas se actualizan al generar el informe en HTML.
Cada paquete tiene una tarea concreta. readxl abre el
Excel; dplyr y tidyr ayudan a ordenar y
resumir la información; sf lee y prepara las comunas;
ggplot2 dibuja los gráficos, y leaflet
construye los mapas que podemos explorar con el cursor.
# Instalo los paquetes una sola vez, desde la consola de RStudio:
# install.packages(c("readxl", "dplyr", "tidyr", "sf", "leaflet", "ggplot2"))
library(readxl) # Lee archivos de Excel.
library(dplyr) # Filtra, transforma, agrupa y une tablas.
library(tidyr) # Completa combinaciones y reemplaza valores ausentes.
library(sf) # Lee y trabaja con datos geográficos vectoriales.
library(leaflet) # Construye mapas interactivos con polígonos y ventanas de consulta.
library(ggplot2) # Construye los gráficos estadísticos.
# echo = TRUE incluye el código; code_folding permite desplegarlo en el HTML.
# Las advertencias no se imprimen en el informe; los controles de datos sí se muestran.
# Para depuración se puede cambiar warning = TRUE y volver a ejecutar.
knitr::opts_chunk$set(echo = TRUE, message = FALSE, warning = FALSE,
fig.width = 10, fig.height = 6, fig.align = "center")
theme_set(theme_minimal(base_size = 12) +
theme(plot.title = element_text(face = "bold", colour = "#174b60"),
plot.subtitle = element_text(colour = "#526875"),
panel.grid.minor = element_blank(),
panel.grid.major.y = element_blank(),
plot.caption = element_text(hjust = 0, colour = "#526875")))
fmt_n <- function(x) format(x, big.mark = ".", scientific = FALSE, trim = TRUE)
fmt_p <- function(x) format(round(x, 1), decimal.mark = ",", nsmall = 1, trim = TRUE)
# library() carga un paquete instalado; no lo instala. %>% pasa el resultado de una
# operación a la siguiente y se puede leer como «después». paquete::funcion() permite usar
# una función indicando su paquete, como knitr::kable().Primero indicamos la carpeta donde están los insumos. Con
file.path() armamos la ruta del Excel y la del mapa. Luego
consultamos las hojas disponibles con excel_sheets() y
leemos Hoja1 con read_excel(): cada fila queda
como un registro y cada encabezado como una columna.
La cartografía se importa con st_read(). Un
shapefile no es un único archivo: .shp
guarda las formas, .dbf sus atributos, .shx su
índice y .prj el sistema de coordenadas. Deben
estar juntos y compartir el nombre Comunas. No son columnas
.sh: son archivos complementarios de una misma capa
geográfica. Sus columnas de atributos se pueden consultar con
names(comunas); la columna de geometría contiene los
polígonos.
# Cambio únicamente estas rutas si traslado los archivos a otro equipo.
carpeta <- "C:/Users/nicolas.cobo/OneDrive - PUJ Cali/Escritorio/Casos"
hoja <- "Hoja1" # Verifico este nombre con excel_sheets(archivo_excel).
archivo_excel <- file.path(carpeta, "EncuestaOrigenDestino.xlsx")
archivo_mapa <- file.path(carpeta, "cali", "Comunas.shp")
# file.path() construye una ruta sin concatenar manualmente las barras.
excel_sheets(archivo_excel) # Muestra los nombres de las hojas disponibles.
encuesta <- read_excel(archivo_excel, sheet = hoja)
comunas <- st_read(archivo_mapa, quiet = TRUE)
names(comunas) # Reviso los atributos de la cartografía.
names(encuesta) # Identifico cómo se llaman realmente las columnas.
knitr::kable(head(encuesta, 5), caption = "Primeros cinco registros: revisión local")
# <- asigna un resultado a un objeto. head(x, 5) permite mirar cinco filas y names(x)
# muestra los nombres de las columnas. Las comillas invertidas permiten referirse a nombres
# que contienen espacios o tildes, como TIPO DE VEHÍCULO .## [1] "OBJECTID" "gid" "comuna" "nombre" "geometry"
## [1] "FECHA"
## [2] "ID ESTACIÓN"
## [3] "ESTACIÓN"
## [4] "ACCESO"
## [5] "MOVIMIENTO"
## [6] "Hora de Encuesta"
## [7] "MUNICIPIO...7"
## [8] "DEPARTAMENTO / LOCALIDAD / COMUNA / DISTRITO / BARRIO / VEREDA / HITO / DIRECCIÓN...8"
## [9] "Codigo Origen_SDG"
## [10] "¿QUE ESTABA HACIENDO EN ESE LUGAR?"
## [11] "MUNICIPIO...11"
## [12] "DEPARTAMENTO / LOCALIDAD / COMUNA / DISTRITO / BARRIO / VEREDA / HITO / DIRECCIÓN...12"
## [13] "Codigo Destino_SDG"
## [14] "¿QUE VA HACER A ESE LUGAR?"
## [15] "ESTRATO EN SU VIVIENDA"
## [16] "¿DISPONIA DE UN VEHÍCULO PARA REALIZAR ESTE DESPLAZAMIENTO?"
## [17] "OTRO ¿CUÁL?...17"
## [18] "ANTES"
## [19] "DESPUES"
## [20] "EDAD"
## [21] "SEXO"
## [22] "PERSONAS EN EL VEHÍCULO"
## [23] "TIPO DE VEHÍCULO"
## [24] "OTRO ¿CUÁL?...24"
## [25] "TIPO DE VIAJERO"
## [26] "comuna origen"
## [27] "comuna destino"
## [28] "Intracomuna"
| FECHA | ID ESTACIÓN | ESTACIÓN | ACCESO | MOVIMIENTO | Hora de Encuesta | MUNICIPIO…7 | DEPARTAMENTO / LOCALIDAD / COMUNA / DISTRITO / BARRIO / VEREDA / HITO / DIRECCIÓN…8 | Codigo Origen_SDG | ¿QUE ESTABA HACIENDO EN ESE LUGAR? | MUNICIPIO…11 | DEPARTAMENTO / LOCALIDAD / COMUNA / DISTRITO / BARRIO / VEREDA / HITO / DIRECCIÓN…12 | Codigo Destino_SDG | ¿QUE VA HACER A ESE LUGAR? | ESTRATO EN SU VIVIENDA | ¿DISPONIA DE UN VEHÍCULO PARA REALIZAR ESTE DESPLAZAMIENTO? | OTRO ¿CUÁL?…17 | ANTES | DESPUES | EDAD | SEXO | PERSONAS EN EL VEHÍCULO | TIPO DE VEHÍCULO | OTRO ¿CUÁL?…24 | TIPO DE VIAJERO | comuna origen | comuna destino | Intracomuna |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2015-06-01 | 1 | Avenida 6N X Calle 70N | NORTE | MOV 1 | 1899-12-31 06:10:00 | CALI | B CHIPICHAPE | 218 | 5 | CALI | B PANCE | 2216 | 1 | 3 | 0 | NA | NA | NA | 35 | 2 | 1 | 2 | NA | 1 | 02 | 22 | 0 |
| 2015-06-01 | 1 | Avenida 6N X Calle 70N | NORTE | MOV 1 | 1899-12-31 06:11:00 | CALIMA | DARIEN | 4102 | 6 | CALI | TEQUENDAMA | 1904 | 4 | 2 | NA | NA | NA | NA | 40 | 2 | 4 | 3 | NA | 1 | Fuera de Cali | 19 | 0 |
| 2015-06-01 | 1 | Avenida 6N X Calle 70N | NORTE | MOV 1 | 1899-12-31 06:12:00 | YUMBO | VALLE DEL CAUCA | 3102 | 6 | CALI | B EL PEÑON | 302 | 1 | 3 | NA | NA | NA | NA | 33 | 2 | 1 | 2 | NA | 1 | Fuera de Cali | 03 | 0 |
| 2015-06-01 | 1 | Avenida 6N X Calle 70N | NORTE | MOV 1 | 1899-12-31 06:13:00 | YUMBO | VALLE DEL CAUCA | 3102 | 6 | CALI | B OBRERO | 910 | 1 | 2 | NA | NA | NA | NA | 28 | 2 | 1 | 2 | NA | 1 | Fuera de Cali | 09 | 0 |
| 2015-06-01 | 1 | Avenida 6N X Calle 70N | NORTE | MOV 1 | 1899-12-31 06:14:00 | PALMIRA | VALLE DEL CAUCA | 3202 | 8 | CALI | B CENTENARIO | 223 | 1 | 3 | NA | NA | NA | NA | 25 | 2 | 2 | 2 | NA | 1 | Fuera de Cali | 02 | 0 |
En los mapas se usa el mismo conjunto de viajes para origen y destino: ambos municipios deben ser Cali y ambas comunas deben ser enteros entre 1 y 22. Los viajes intermunicipales se excluyen de este análisis, aunque no necesariamente son errores.
# Reviso los encabezados reales antes de transformar la encuesta.
# Creo claves de comparación sin cambiar los nombres originales del Excel.
# iconv() quita tildes; tolower() pasa a minúsculas.
# gsub() elimina espacios, saltos de línea, guiones y otros signos.
claves <- iconv(names(encuesta), from = "", to = "ASCII//TRANSLIT")
claves <- gsub("[^a-z0-9]", "", tolower(claves))
knitr::kable(data.frame(columna_original = names(encuesta), clave = claves),
caption = "Encabezados disponibles en la hoja seleccionada")| columna_original | clave |
|---|---|
| FECHA | fecha |
| ID ESTACIÓN | idestacion |
| ESTACIÓN | estacion |
| ACCESO | acceso |
| MOVIMIENTO | movimiento |
| Hora de Encuesta | horadeencuesta |
| MUNICIPIO…7 | municipio7 |
| DEPARTAMENTO / LOCALIDAD / COMUNA / DISTRITO / BARRIO / VEREDA / HITO / DIRECCIÓN…8 | departamentolocalidadcomunadistritobarrioveredahitodireccion8 |
| Codigo Origen_SDG | codigoorigensdg |
| ¿QUE ESTABA HACIENDO EN ESE LUGAR? | queestabahaciendoeneselugar |
| MUNICIPIO…11 | municipio11 |
| DEPARTAMENTO / LOCALIDAD / COMUNA / DISTRITO / BARRIO / VEREDA / HITO / DIRECCIÓN…12 | departamentolocalidadcomunadistritobarrioveredahitodireccion12 |
| Codigo Destino_SDG | codigodestinosdg |
| ¿QUE VA HACER A ESE LUGAR? | quevahaceraeselugar |
| ESTRATO EN SU VIVIENDA | estratoensuvivienda |
| ¿DISPONIA DE UN VEHÍCULO PARA REALIZAR ESTE DESPLAZAMIENTO? | disponiadeunvehiculopararealizarestedesplazamiento |
| OTRO ¿CUÁL?…17 | otrocual17 |
| ANTES | antes |
| DESPUES | despues |
| EDAD | edad |
| SEXO | sexo |
| PERSONAS EN EL VEHÍCULO | personasenelvehiculo |
| TIPO DE VEHÍCULO | tipodevehiculo |
| OTRO ¿CUÁL?…24 | otrocual24 |
| TIPO DE VIAJERO | tipodeviajero |
| comuna origen | comunaorigen |
| comuna destino | comunadestino |
| Intracomuna | intracomuna |
# Busco coincidencias exactas entre claves y recupero el nombre original.
# No elijo una columna por suposición si falta o hay varias coincidencias.
buscar_columna <- function(candidatos) {
coincidencias <- names(encuesta)[which(claves %in% candidatos)]
if (length(coincidencias) != 1) {
stop("Columna ausente o ambigua: ", paste(candidatos, collapse = " / "),
". Revise la hoja seleccionada y estos encabezados: ",
paste(names(encuesta), collapse = " | "))
}
coincidencias
}
columna_origen <- buscar_columna(c("comunaorigen", "comunadeorigen"))
columna_destino <- buscar_columna(c("comunadestino", "comunadedestino"))
columna_vehiculo <- buscar_columna(c("tipodevehiculo", "tipovehiculo"))
# La clave comunaorigen reconoce encabezados como COMUNA ORIGEN, Comuna_Origen o comuna
# origen ; también se admite comuna de origen. Esta revisión no cambia el contenido de las
# columnas.# Identifico los municipios por sus nombres descriptivos normalizados.
# Si ambos se llaman MUNICIPIO, read_excel() añade sufijos como ...5 y ...9.
if (sum(claves %in% c("municipioorigen", "municipiodeorigen")) == 1 &&
sum(claves %in% c("municipiodestino", "municipiodedestino")) == 1) {
columnas_municipio <- c(
buscar_columna(c("municipioorigen", "municipiodeorigen")),
buscar_columna(c("municipiodestino", "municipiodedestino"))
)
} else {
columnas_municipio <- names(encuesta)[which(grepl("^municipio[0-9]*$", claves))]
}
if (length(columnas_municipio) != 2) {
stop("Revise los encabezados: se necesitan dos columnas de municipio (origen y destino).")
}
columnas_municipio # En encabezados repetidos, confirmo el orden origen–destino.## [1] "MUNICIPIO...7" "MUNICIPIO...11"
# Unifico tildes, mayúsculas y espacios; conservo la respuesta original en encuesta.
normalizar_texto <- function(x) {
x <- toupper(trimws(as.character(x)))
x <- iconv(x, from = "", to = "ASCII//TRANSLIT")
gsub("[[:space:]]+", " ", x)
}
marcadores_ausencia <- c("", "NA", "N/A", "NULL", "SIN DATO", "SIN INFORMACION")
# Conservo encuesta intacta y creo una tabla para el análisis.
# sub() quita el prefijo COMUNA; toupper() y trimws() unifican el texto.
# as.numeric() convierte "Comuna 02", "COMUNA 2" y "02" al número 2.
# suppressWarnings() se limita a esta conversión: reviso los NA más abajo.
datos <- encuesta %>%
mutate(
municipio_origen = normalizar_texto(.data[[columnas_municipio[1]]]),
municipio_destino = normalizar_texto(.data[[columnas_municipio[2]]]),
origen = suppressWarnings(as.numeric(sub("^COMUNA[[:space:]]*", "",
toupper(trimws(as.character(.data[[columna_origen]])))))),
destino = suppressWarnings(as.numeric(sub("^COMUNA[[:space:]]*", "",
toupper(trimws(as.character(.data[[columna_destino]])))))),
vehiculo = normalizar_texto(.data[[columna_vehiculo]]),
modo = case_when(
vehiculo %in% c("1", "1.0", "BICICLETA", "BICI") ~ "Bicicleta",
vehiculo %in% c("2", "2.0", "MOTO", "MOTOCICLETA") ~ "Motocicleta",
vehiculo %in% c("3", "3.0", "AUTOMOVIL", "AUTOMÓVIL", "AUTO", "CARRO") ~ "Automóvil",
is.na(vehiculo) | vehiculo %in% marcadores_ausencia ~ "Sin información",
TRUE ~ "Otro modo o código"
),
duplicado = duplicated(encuesta),
control = case_when(
is.na(municipio_origen) | is.na(municipio_destino) |
municipio_origen %in% marcadores_ausencia |
municipio_destino %in% marcadores_ausencia ~ "Municipio sin información",
!municipio_origen %in% c("CALI", "SANTIAGO DE CALI") |
!municipio_destino %in% c("CALI", "SANTIAGO DE CALI") ~ "Viaje externo o municipio por revisar",
is.na(origen) | is.na(destino) |
!origen %in% 1:22 | !destino %in% 1:22 ~ "Comuna ausente o inválida",
TRUE ~ "Incluido"
)
)
viajes <- datos %>% filter(control == "Incluido")
if (nrow(viajes) == 0) stop("No hay viajes incluidos: revise municipios y comunas.")
# mutate() crea columnas y filter() selecciona filas. toupper() unifica mayúsculas y
# trimws() quita espacios al comienzo y al final. case_when() evalúa condiciones en orden y
# asigna la primera categoría que corresponda. %in% verifica pertenencia; ! significa
# negación y | significa «o». is.na() identifica valores ausentes. .data[[nombre]]
# selecciona una columna cuyo nombre está guardado en una variable.Los códigos de comuna se normalizan antes del análisis:
"Comuna 02", "COMUNA 2" y "02" se
convierten en 2. Se conservan únicamente códigos enteros
entre 1 y 22; los valores ausentes o no interpretables se identifican en
el control de calidad. En este análisis se utiliza la equivalencia
1 = bicicleta, 2 = motocicleta y 3 = automóvil.
Esta tabla conecta las columnas de entrada con los nombres que
usaremos en el análisis. Conservamos el Excel original y creamos una
tabla de trabajo llamada datos; después,
viajes contiene únicamente los registros incluidos.
biblioteca <- data.frame(
Entrada = c(columnas_municipio, columna_origen, columna_destino,
columna_vehiculo, columna_fecha, "Fila completa del Excel", "Variables territoriales"),
Variable = c("municipio_origen", "municipio_destino", "origen", "destino",
"vehiculo / modo", "fechas_registro", "duplicado", "control"),
Tipo = c("Texto", "Texto", "Numérico", "Numérico", "Texto / categoría",
"Fecha", "Lógico", "Categoría"),
Uso = c("Verificar que el viaje comienza en Cali", "Verificar que termina en Cali",
"Comuna de salida: entero entre 1 y 22", "Comuna de llegada: entero entre 1 y 22",
"Conservar el código y agrupar los modos principales", "Establecer el periodo observado",
"Marcar filas idénticas adicionales, sin eliminarlas", "Explicar inclusiones y exclusiones"))
knitr::kable(biblioteca, caption = "Variables de entrada y variables de trabajo")| Entrada | Variable | Tipo | Uso |
|---|---|---|---|
| MUNICIPIO…7 | municipio_origen | Texto | Verificar que el viaje comienza en Cali |
| MUNICIPIO…11 | municipio_destino | Texto | Verificar que termina en Cali |
| comuna origen | origen | Numérico | Comuna de salida: entero entre 1 y 22 |
| comuna destino | destino | Numérico | Comuna de llegada: entero entre 1 y 22 |
| TIPO DE VEHÍCULO | vehiculo / modo | Texto / categoría | Conservar el código y agrupar los modos principales |
| FECHA | fechas_registro | Fecha | Establecer el periodo observado |
| Fila completa del Excel | duplicado | Lógico | Marcar filas idénticas adicionales, sin eliminarlas |
| Variables territoriales | control | Categoría | Explicar inclusiones y exclusiones |
La columna FECHA contiene las fechas de los registros y se usa para calcular el intervalo presentado en la introducción.
control_fechas <- data.frame(
Estado = c("Fecha válida", "Fecha ausente o no interpretable"),
Registros = c(sum(!is.na(fechas_registro)), sum(is.na(fechas_registro))))
knitr::kable(control_fechas, caption = "Fechas utilizadas para establecer el periodo observado")| Estado | Registros |
|---|---|
| Fecha válida | 35054 |
| Fecha ausente o no interpretable | 0 |
El periodo se obtiene del archivo completo antes del filtro territorial. Los registros sin fecha válida no intervienen en ese intervalo, pero no se excluyen del análisis espacial por ese motivo.
En la cartografía, comuna será la llave territorial y la
geometría conservará los límites. Más adelante agregaremos
total, bicicletas, motos y
automoviles: son conteos calculados, no columnas originales
de la encuesta.
Antes de mirar dónde se concentran los viajes, conviene responder tres preguntas: ¿cuántos registros podemos ubicar?, ¿qué información falta? y ¿cómo se distribuyen los modos? Los gráficos siguientes responden una pregunta a la vez.
La revisión diferencia dos bases de cálculo: el archivo completo, utilizado para evaluar la calidad y la selección territorial, y los viajes incluidos, utilizados para describir los modos de transporte. En las tablas y el gráfico, N identifica el total de registros de referencia y n el número de registros de cada categoría.
N_inicial <- nrow(datos)
N_incluido <- nrow(viajes)
# Cada registro se cuenta una sola vez, según la primera condición que cumple.
calidad <- datos %>%
count(control, name = "registros") %>%
mutate(porcentaje = round(100 * registros / N_inicial, 1))
knitr::kable(calidad,
col.names = c("Categoría de control", "Registros (n)", "% del archivo completo"),
caption = paste0("Trazabilidad territorial. N = ",
format(N_inicial, big.mark = ".", trim = TRUE), " registros"),
format.args = list(big.mark = ".", decimal.mark = ","))| Categoría de control | Registros (n) | % del archivo completo |
|---|---|---|
| Comuna ausente o inválida | 2.389 | 6,8 |
| Incluido | 22.139 | 63,2 |
| Municipio sin información | 79 | 0,2 |
| Viaje externo o municipio por revisar | 10.447 | 29,8 |
ggplot(calidad, aes(reorder(control, registros), registros, fill = control == "Incluido")) +
geom_col(width = 0.65, show.legend = FALSE) +
geom_text(aes(label = paste0(fmt_n(registros), " (", fmt_p(porcentaje), " %)")),
hjust = -0.08, size = 3.6) + coord_flip() +
scale_fill_manual(values = c("FALSE" = "#95a9b5", "TRUE" = "#16847b")) +
scale_y_continuous(expand = expansion(mult = c(0, 0.4))) +
labs(title = "Del archivo original al conjunto de análisis",
subtitle = paste("Base de cálculo:", fmt_n(N_inicial), "registros iniciales"),
x = NULL, y = "Registros", caption = "Cada registro aparece en una sola categoría.")La barra verde muestra la información que podremos ubicar en ambos extremos del viaje. Las demás barras explican qué queda fuera y por qué: no deben leerse automáticamente como errores de captura.
De los 35.054 registros iniciales, se incluyen 22.139 (63,2 %). Los porcentajes de esta tabla se calculan como n de cada categoría / N del archivo completo × 100.
NA, N/A o «sin información».Las categorías son excluyentes. El orden de evaluación es el siguiente: primero se revisa la ausencia municipal, después la condición Cali–Cali y finalmente las comunas. Por eso los problemas de comuna de viajes ya clasificados como externos no vuelven a contarse en «Comuna ausente o inválida».
# Un ausente original no es lo mismo que un texto que falló al convertirse.
# Se consultan las columnas originales para distinguir ambas situaciones.
marcadores_ausencia <- c("", "NA", "N/A", "NULL", "SIN DATO",
"SIN INFORMACION", "SIN INFORMACIÓN")
texto_origen <- toupper(trimws(as.character(encuesta[[columna_origen]])))
texto_destino <- toupper(trimws(as.character(encuesta[[columna_destino]])))
ausente_origen <- is.na(texto_origen) | texto_origen %in% marcadores_ausencia
ausente_destino <- is.na(texto_destino) | texto_destino %in% marcadores_ausencia
# Estos controles se calculan sobre TODO el archivo y pueden superponerse.
revision <- data.frame(
Indicador = c("Registros iniciales", "Registros incluidos",
"Comuna de origen: ausente en el archivo",
"Comuna de destino: ausente en el archivo",
"Comuna de origen: texto no convertible",
"Comuna de destino: texto no convertible",
"Comuna de origen: número fuera de 1–22 o no entero",
"Comuna de destino: número fuera de 1–22 o no entero",
"Filas idénticas adicionales"),
Registros = c(N_inicial, N_incluido,
sum(ausente_origen), sum(ausente_destino),
sum(!ausente_origen & is.na(datos$origen)),
sum(!ausente_destino & is.na(datos$destino)),
sum(!is.na(datos$origen) & !datos$origen %in% 1:22),
sum(!is.na(datos$destino) & !datos$destino %in% 1:22),
sum(datos$duplicado))
) %>% mutate(Porcentaje = round(100 * Registros / N_inicial, 1))
knitr::kable(revision,
col.names = c("Indicador", "Registros (n)", "% del archivo completo"),
caption = paste0("Controles básicos y valores ausentes. N = ",
format(N_inicial, big.mark = ".", trim = TRUE), " registros"),
format.args = list(big.mark = ".", decimal.mark = ","))| Indicador | Registros (n) | % del archivo completo |
|---|---|---|
| Registros iniciales | 35.054 | 100,0 |
| Registros incluidos | 22.139 | 63,2 |
| Comuna de origen: ausente en el archivo | 0 | 0,0 |
| Comuna de destino: ausente en el archivo | 0 | 0,0 |
| Comuna de origen: texto no convertible | 6.306 | 18,0 |
| Comuna de destino: texto no convertible | 6.400 | 18,3 |
| Comuna de origen: número fuera de 1–22 o no entero | 672 | 1,9 |
| Comuna de destino: número fuera de 1–22 o no entero | 788 | 2,2 |
| Filas idénticas adicionales | 1 | 0,0 |
Ausente significa que la comuna no está informada en el archivo original o contiene uno de los marcadores de ausencia definidos en el código. Texto no convertible significa que sí existe una respuesta, pero no se obtiene un número después de normalizarla. Por ejemplo, «Comuna 02» se convierte correctamente a 2, mientras que «desconocida» no es convertible. Un valor como 25 o 2,5 puede ser numérico, pero es inválido para este análisis.
La tabla separa la ausencia original de las dificultades de conversión para que podamos distinguir qué información falta y cuál necesita revisión. Todos estos controles usan como denominador N = 35.054, incluso si el registro quedó fuera del análisis territorial. Sus filas no se suman: un mismo registro puede tener problemas en origen y destino, o ser una fila idéntica adicional. Por esta razón, tampoco deben sumarse los problemas de origen y destino para compararlos con la categoría territorial de comunas inválidas.
revision_grafico <- revision %>% filter(grepl("^Comuna", Indicador)) %>%
mutate(Extremo = if_else(grepl("origen", Indicador), "Origen", "Destino"),
Problema = sub("^Comuna de (origen|destino): ", "", Indicador))
ggplot(revision_grafico, aes(Problema, Registros, fill = Extremo)) +
geom_col(position = position_dodge(width = 0.75), width = 0.65) +
geom_text(aes(label = fmt_n(Registros)), position = position_dodge(width = 0.75),
hjust = -0.15, size = 3.5) + coord_flip() +
scale_fill_manual(values = c("Origen" = "#16847b", "Destino" = "#375b99")) +
scale_y_continuous(expand = expansion(mult = c(0, 0.2))) +
labs(title = "¿Qué necesitamos revisar en las comunas?",
subtitle = paste("Revisión del archivo completo: N =", fmt_n(N_inicial)),
x = NULL, y = "Registros", fill = NULL,
caption = "Los controles pueden coincidir en un mismo registro; las barras no se suman.")Comparamos origen y destino para reconocer dónde se pierde más información. Una barra alta de ausencias sugiere revisar el diligenciamiento; una de textos no convertibles sugiere revisar cómo se escribieron las comunas. Los códigos fuera de rango requieren consultar el dato original antes de corregirlos.
# El denominador cambia: ahora son únicamente los viajes incluidos.
frecuencia_modo <- viajes %>%
count(modo, name = "registros", sort = TRUE) %>%
mutate(porcentaje = round(100 * registros / N_incluido, 1),
etiqueta = paste0(format(registros, big.mark = ".", trim = TRUE),
" (", format(porcentaje, decimal.mark = ",",
nsmall = 1, trim = TRUE), " %)"))
knitr::kable(frecuencia_modo %>% select(-etiqueta),
col.names = c("Modo de transporte", "Registros (n)", "% de los incluidos"),
caption = paste0("Modos de transporte. N = ",
format(N_incluido, big.mark = ".", trim = TRUE), " viajes incluidos"),
format.args = list(big.mark = ".", decimal.mark = ","))| Modo de transporte | Registros (n) | % de los incluidos |
|---|---|---|
| Motocicleta | 10.302 | 46,5 |
| Automóvil | 8.598 | 38,8 |
| Otro modo o código | 1.966 | 8,9 |
| Bicicleta | 1.223 | 5,5 |
| Sin información | 50 | 0,2 |
En esta tabla, cada porcentaje corresponde a n del modo / N
de viajes incluidos × 100; no se calcula sobre el archivo
completo. «Sin información» agrupa respuestas de vehículo vacías,
NA, N/A o «sin información», mientras que
«Otro modo o código» contiene respuestas no reconocidas como bicicleta,
motocicleta o automóvil. Esta última categoría no se interpreta como un
modo específico sin consultar las convenciones de la encuesta. Las
diferencias de una décima en la suma de porcentajes se deben al
redondeo.
Las filas idénticas se conservan porque, sin un identificador de entrevista, no se puede asegurar que sean errores de captura. También se conservan los viajes dentro de una misma comuna. Los mapas generales incluyen otros modos y respuestas sin modo; los específicos muestran únicamente bicicleta, motocicleta o automóvil.
# Las etiquetas muestran n y porcentaje; el subtítulo identifica el denominador.
# Todos los valores se recalculan al ejecutar el documento con la base de datos.
ggplot(frecuencia_modo, aes(x = reorder(modo, registros), y = registros)) +
geom_col(fill = "#16847b", width = 0.65) +
geom_text(aes(label = etiqueta), hjust = -0.08, size = 3.8) +
scale_y_continuous(expand = expansion(mult = c(0, 0.30))) +
coord_flip() +
labs(title = "Composición modal de los registros incluidos",
subtitle = paste0("N = ", format(N_incluido, big.mark = ".", trim = TRUE),
" viajes incluidos de ",
format(N_inicial, big.mark = ".", trim = TRUE),
" registros iniciales (",
format(round(100 * N_incluido / N_inicial, 1),
decimal.mark = ",", nsmall = 1, trim = TRUE), " %)"),
x = NULL, y = "Registros de desplazamiento (n)",
caption = "Cada etiqueta muestra n (% de los viajes incluidos). Porcentajes sin expansión muestral.")El modo con más registros predomina en esta muestra. Esto no demuestra que sea el más utilizado por toda la población.
Ahora pasamos de los registros individuales a una lectura territorial. Contamos cuántos viajes comienzan y terminan en cada comuna y desagregamos esos conteos por modo. Después unimos los resultados con los polígonos para dibujar los mapas. Esta unión usa el código de comuna, no el orden de las filas.
Una misma comuna puede ser un origen frecuente y también un destino frecuente. Por eso analizamos ambos extremos antes de hablar de predominio de llegadas o salidas.
# group_by() organiza los cálculos por comuna.
# sum(condición) cuenta los TRUE; na.rm = TRUE omite valores NA.
origen_resumen <- viajes %>%
group_by(comuna = origen) %>%
summarise(total = n(),
bicicletas = sum(modo == "Bicicleta", na.rm = TRUE),
motos = sum(modo == "Motocicleta", na.rm = TRUE),
automoviles = sum(modo == "Automóvil", na.rm = TRUE),
.groups = "drop")
destino_resumen <- viajes %>%
group_by(comuna = destino) %>%
summarise(total = n(),
bicicletas = sum(modo == "Bicicleta", na.rm = TRUE),
motos = sum(modo == "Motocicleta", na.rm = TRUE),
automoviles = sum(modo == "Automóvil", na.rm = TRUE),
.groups = "drop")
knitr::kable(origen_resumen, caption = "Registros por comuna de origen")| comuna | total | bicicletas | motos | automoviles |
|---|---|---|---|---|
| 1 | 626 | 32 | 296 | 234 |
| 2 | 2249 | 122 | 1062 | 860 |
| 3 | 1608 | 84 | 701 | 678 |
| 4 | 1193 | 58 | 571 | 443 |
| 5 | 521 | 28 | 260 | 185 |
| 6 | 796 | 29 | 377 | 313 |
| 7 | 583 | 30 | 272 | 229 |
| 8 | 862 | 46 | 421 | 319 |
| 9 | 737 | 36 | 317 | 295 |
| 10 | 1094 | 71 | 535 | 404 |
| 11 | 717 | 42 | 339 | 268 |
| 12 | 257 | 10 | 133 | 88 |
| 13 | 1031 | 62 | 453 | 412 |
| 14 | 574 | 32 | 258 | 235 |
| 15 | 974 | 55 | 455 | 377 |
| 16 | 836 | 65 | 383 | 320 |
| 17 | 1762 | 103 | 853 | 662 |
| 18 | 1208 | 85 | 593 | 433 |
| 19 | 2240 | 113 | 995 | 922 |
| 20 | 599 | 33 | 278 | 228 |
| 21 | 693 | 35 | 323 | 272 |
| 22 | 979 | 52 | 427 | 421 |
| comuna | total | bicicletas | motos | automoviles |
|---|---|---|---|---|
| 1 | 187 | 9 | 90 | 73 |
| 2 | 3772 | 194 | 1734 | 1517 |
| 3 | 2881 | 169 | 1330 | 1095 |
| 4 | 1496 | 80 | 678 | 595 |
| 5 | 440 | 38 | 183 | 181 |
| 6 | 570 | 31 | 255 | 208 |
| 7 | 598 | 36 | 279 | 224 |
| 8 | 830 | 46 | 382 | 325 |
| 9 | 1055 | 56 | 512 | 382 |
| 10 | 741 | 34 | 353 | 291 |
| 11 | 483 | 26 | 217 | 204 |
| 12 | 207 | 13 | 106 | 71 |
| 13 | 560 | 28 | 285 | 189 |
| 14 | 316 | 20 | 150 | 120 |
| 15 | 481 | 17 | 232 | 199 |
| 16 | 645 | 31 | 290 | 275 |
| 17 | 1615 | 95 | 743 | 650 |
| 18 | 526 | 28 | 240 | 216 |
| 19 | 2438 | 131 | 1151 | 924 |
| 20 | 303 | 22 | 124 | 131 |
| 21 | 434 | 24 | 202 | 164 |
| 22 | 1561 | 95 | 766 | 564 |
# .groups = "drop" deja el resumen sin agrupación. El total puede superar la suma de los
# tres modos, porque incluye otras categorías y respuestas sin información de modo.Los totales de origen y destino deben coincidir: cada viaje aporta una salida y una llegada. Las columnas de los tres modos principales pueden sumar menos que el total, porque todavía puede haber códigos sin identificar o respuestas ausentes. Una comuna que no aparezca en estos resúmenes recibirá cero al unirla con la cartografía.
Un mapa permite reconocer concentraciones; los porcentajes nos dicen cuánto pesan. Aquí comparamos las cinco comunas con más registros en cada extremo y medimos cuántas comunas acumulan al menos la mitad de las salidas o llegadas. Los empates se ordenan por código de comuna para que las selecciones sean reproducibles.
territorio <- data.frame(comuna = 1:22) %>%
left_join(origen_resumen %>% select(comuna, salidas = total), by = "comuna") %>%
left_join(destino_resumen %>% select(comuna, llegadas = total), by = "comuna") %>%
mutate(salidas = replace_na(salidas, 0), llegadas = replace_na(llegadas, 0),
pct_salidas = 100 * salidas / N_incluido,
pct_llegadas = 100 * llegadas / N_incluido)
ranking_o <- territorio %>% arrange(desc(salidas), comuna) %>%
mutate(acumulado = 100 * cumsum(salidas) / N_incluido)
ranking_d <- territorio %>% arrange(desc(llegadas), comuna) %>%
mutate(acumulado = 100 * cumsum(llegadas) / N_incluido)
c3_o <- sum(head(ranking_o$salidas, 3)) / N_incluido * 100
c3_d <- sum(head(ranking_d$llegadas, 3)) / N_incluido * 100
mitad_o <- which(ranking_o$acumulado >= 50)[1]
mitad_d <- which(ranking_d$acumulado >= 50)[1]
resumen_concentracion <- data.frame(
Indicador = c("Total de registros", "Media por comuna", "Mediana por comuna",
"Mínimo", "Máximo", "Desviación estándar",
"Participación de las 3 primeras (%)", "Participación de las 5 primeras (%)",
"Comunas necesarias para reunir al menos el 50 %"),
Salidas = c(N_incluido, mean(territorio$salidas), median(territorio$salidas),
min(territorio$salidas), max(territorio$salidas), sd(territorio$salidas),
c3_o, sum(head(ranking_o$salidas, 5)) / N_incluido * 100, mitad_o),
Llegadas = c(N_incluido, mean(territorio$llegadas), median(territorio$llegadas),
min(territorio$llegadas), max(territorio$llegadas), sd(territorio$llegadas),
c3_d, sum(head(ranking_d$llegadas, 5)) / N_incluido * 100, mitad_d))
knitr::kable(resumen_concentracion, digits = 1,
caption = "Distribución de los conteos entre las 22 comunas")| Indicador | Salidas | Llegadas |
|---|---|---|
| Total de registros | 22139.0 | 22139.0 |
| Media por comuna | 1006.3 | 1006.3 |
| Mediana por comuna | 849.0 | 584.0 |
| Mínimo | 257.0 | 187.0 |
| Máximo | 2249.0 | 3772.0 |
| Desviación estándar | 530.4 | 941.9 |
| Participación de las 3 primeras (%) | 28.2 | 41.1 |
| Participación de las 5 primeras (%) | 41.0 | 55.4 |
| Comunas necesarias para reunir al menos el 50 % | 7.0 | 5.0 |
rankings <- bind_rows(
ranking_o %>% slice_head(n = 5) %>% transmute(Extremo = "Origen", comuna, registros = salidas, porcentaje = pct_salidas),
ranking_d %>% slice_head(n = 5) %>% transmute(Extremo = "Destino", comuna, registros = llegadas, porcentaje = pct_llegadas))
knitr::kable(rankings, digits = 1, caption = "Cinco comunas con más registros en cada extremo")| Extremo | comuna | registros | porcentaje |
|---|---|---|---|
| Origen | 2 | 2249 | 10.2 |
| Origen | 19 | 2240 | 10.1 |
| Origen | 17 | 1762 | 8.0 |
| Origen | 3 | 1608 | 7.3 |
| Origen | 18 | 1208 | 5.5 |
| Destino | 2 | 3772 | 17.0 |
| Destino | 3 | 2881 | 13.0 |
| Destino | 19 | 2438 | 11.0 |
| Destino | 17 | 1615 | 7.3 |
| Destino | 22 | 1561 | 7.1 |
ggplot(rankings, aes(reorder(factor(comuna), registros), registros, fill = Extremo)) +
geom_col(width = 0.65, show.legend = FALSE) +
geom_text(aes(label = paste0(fmt_n(registros), " (", fmt_p(porcentaje), " %)")),
hjust = -0.08, size = 3.2) +
facet_wrap(~Extremo, ncol = 1, scales = "free_x") + coord_flip() +
scale_fill_manual(values = c("Origen" = "#16847b", "Destino" = "#375b99")) +
scale_y_continuous(expand = expansion(mult = c(0, 0.35))) +
labs(title = "Las comunas que más pesan en los registros",
subtitle = paste("Cada porcentaje usa N =", fmt_n(N_incluido), "viajes incluidos"),
x = "Comuna", y = "Registros")cat("Las comunas **", paste(head(ranking_o$comuna, 3), collapse = ", "),
"** reúnen el **", fmt_p(c3_o), " %** de las salidas; las comunas **",
paste(head(ranking_d$comuna, 3), collapse = ", "), "** reúnen el **", fmt_p(c3_d),
" %** de las llegadas. La diferencia de concentración entre destinos y orígenes es de **",
fmt_p(c3_d - c3_o), " puntos porcentuales**.\n\n", sep = "")Las comunas 2, 19, 17 reúnen el 28,2 % de las salidas; las comunas 2, 3, 19 reúnen el 41,1 % de las llegadas. La diferencia de concentración entre destinos y orígenes es de 12,8 puntos porcentuales.
cat("Para reunir al menos la mitad de los registros se necesitan **", mitad_o,
" comunas de origen** y **", mitad_d, " de destino**.\n\n", sep = "")Para reunir al menos la mitad de los registros se necesitan 7 comunas de origen y 5 de destino.
cat("La media de salidas y llegadas es idéntica porque sus totales son iguales. Las medianas son **",
fmt_p(median(territorio$salidas)), "** y **", fmt_p(median(territorio$llegadas)),
"**, respectivamente. Compararlas con la media permite apreciar cuánto influyen las comunas de mayor conteo.\n", sep = "")La media de salidas y llegadas es idéntica porque sus totales son iguales. Las medianas son 849,0 y 584,0, respectivamente. Compararlas con la media permite apreciar cuánto influyen las comunas de mayor conteo.
Las llegadas están más concentradas en las tres primeras comunas que las salidas.
La concentración describe la distribución de los registros. Para comparar intensidad de movilidad por habitante se necesitarían denominadores de población; para medir densidad espacial, el área de cada comuna.
# Identifico la llave del mapa sin depender de mayúsculas o tildes.
claves_mapa <- gsub("[^a-z0-9]", "", tolower(iconv(names(comunas), to = "ASCII//TRANSLIT")))
llave_mapa <- names(comunas)[claves_mapa %in% c("comuna", "codcomuna", "codigocomuna")]
if (length(llave_mapa) != 1) stop("Identifique la columna de comuna en la cartografía: ", paste(names(comunas), collapse = ", "))
# No asigno un CRS por suposición: debe venir definido en el archivo .prj.
if (is.na(st_crs(comunas))) stop("Falta el sistema de referencia: revise el archivo .prj.")
comunas <- comunas %>%
mutate(comuna = suppressWarnings(as.numeric(sub("^COMUNA[[:space:]]*", "", normalizar_texto(.data[[llave_mapa]]))))) %>%
select(comuna) %>%
st_make_valid() %>%
group_by(comuna) %>%
summarise(.groups = "drop") %>%
st_transform(4326)
# setequal() comprueba que estén las 22 comunas, sin códigos adicionales.
stopifnot(setequal(comunas$comuna, 1:22), !anyNA(comunas$comuna),
!any(st_is_empty(comunas)), all(st_is_valid(comunas)))
# left_join() conserva todas las comunas y agrega sus conteos por la clave comuna.
# Uso objetos sf para mantener geometría y atributos unidos en cada operación.
mapa_origen <- comunas %>% left_join(origen_resumen, by = "comuna")
mapa_destino <- comunas %>% left_join(destino_resumen, by = "comuna")
conteos <- c("total", "bicicletas", "motos", "automoviles")
mapa_origen <- mapa_origen %>% mutate(across(all_of(conteos), ~replace_na(.x, 0)))
mapa_destino <- mapa_destino %>% mutate(across(all_of(conteos), ~replace_na(.x, 0)))
# Cada viaje debe sumar una vez en origen y una vez en destino.
stopifnot(sum(mapa_origen$total) == nrow(viajes),
sum(mapa_destino$total) == nrow(viajes))
knitr::kable(st_drop_geometry(mapa_origen), caption = "Verificación de la unión: orígenes")| comuna | total | bicicletas | motos | automoviles |
|---|---|---|---|---|
| 1 | 626 | 32 | 296 | 234 |
| 2 | 2249 | 122 | 1062 | 860 |
| 3 | 1608 | 84 | 701 | 678 |
| 4 | 1193 | 58 | 571 | 443 |
| 5 | 521 | 28 | 260 | 185 |
| 6 | 796 | 29 | 377 | 313 |
| 7 | 583 | 30 | 272 | 229 |
| 8 | 862 | 46 | 421 | 319 |
| 9 | 737 | 36 | 317 | 295 |
| 10 | 1094 | 71 | 535 | 404 |
| 11 | 717 | 42 | 339 | 268 |
| 12 | 257 | 10 | 133 | 88 |
| 13 | 1031 | 62 | 453 | 412 |
| 14 | 574 | 32 | 258 | 235 |
| 15 | 974 | 55 | 455 | 377 |
| 16 | 836 | 65 | 383 | 320 |
| 17 | 1762 | 103 | 853 | 662 |
| 18 | 1208 | 85 | 593 | 433 |
| 19 | 2240 | 113 | 995 | 922 |
| 20 | 599 | 33 | 278 | 228 |
| 21 | 693 | 35 | 323 | 272 |
| 22 | 979 | 52 | 427 | 421 |
# st_make_valid() repara geometrías y st_transform(4326) transforma al sistema WGS 84 para
# visualizar. Agrupar y resumir une las partes de una misma comuna. st_drop_geometry() deja
# únicamente sus atributos para mostrar una tabla. across() aplica una operación a varias
# columnas; .x representa cada columna y replace_na() reemplaza sus ausentes. Aquí el cero
# significa que una comuna válida no tiene registros en el resumen; no se aplica a la
# información faltante de la encuesta. stopifnot() detiene la ejecución cuando una
# comprobación no se cumple.Para leerlos, empezamos por el mapa general y luego miramos cada modo. Un tono más oscuro indica un conteo mayor de registros, no necesariamente mayor uso por habitante. Al comparar una misma comuna en origen y destino podemos reconocer si concentra más salidas o más llegadas.
Cada mapa muestra conteos de registros. Al pulsar una comuna se consulta su valor exacto. Para comparar origen y destino de una misma categoría se usan los mismos intervalos de color. Los intervalos pueden cambiar entre modos; por tanto, la comparación entre medios de transporte debe considerar los valores y no únicamente los colores.
# Se dibujan las comunas directamente y se ajusta la vista a su extensión.
# El fondo OpenStreetMap no requiere una clave API para esta visualización.
mapa <- function(capa, variable, titulo) {
valores <- capa[[variable]]
total <- sum(valores)
maximo <- max(1, mapa_origen[[variable]], mapa_destino[[variable]])
paleta <- colorBin("Blues", domain = c(0, maximo),
bins = seq(0, maximo, length.out = 6))
limites <- st_bbox(capa)
porcentaje <- round(100 * valores / max(1, total), 1)
detalle <- paste0("<b>", titulo, "</b><br>Comuna ", capa$comuna,
"<br>Registros: ", format(valores, big.mark = ".", trim = TRUE),
"<br>Participación: ", porcentaje, " %",
"<br>Total de esta categoría (N): ", total)
leaflet(capa, width = "100%", height = 480) %>%
addProviderTiles(providers$Esri.WorldTopoMap) %>%
addPolygons(
fillColor = paleta(valores),
fillOpacity = 0.75,
color = "#FFFFFF",
weight = 1.2,
label = paste("Comuna", capa$comuna, "—", valores, "registros"),
popup = detalle,
highlightOptions = highlightOptions(
weight = 3,
color = "#163247"
)
) %>%
addLegend(pal = paleta, values = valores,
title = paste0(titulo, "<br>N = ", total), position = "bottomright") %>%
fitBounds(as.numeric(limites["xmin"]), as.numeric(limites["ymin"]),
as.numeric(limites["xmax"]), as.numeric(limites["ymax"]))
}
# colorBin() asigna colores por intervalos comunes de origen y destino.
# addTiles() agrega el fondo; addPolygons() representa las comunas con sus conteos.
# popup muestra los datos al pulsar; label los muestra al pasar el cursor.
# fitBounds() centra el mapa en la extensión de las comunas.Lectura del mapa. En todos los modos se registran 22.139 desplazamientos. El mayor conteo corresponde a la(s) comuna(s) 2, con 2.249 registros cada una (10,2 % de la categoría). En las comunas 2, 19, 17 salen 6.251 registros (28,2 %).
Hay 22 comunas con registros y 0 sin registros de esta categoría en la base incluida. El tono del mapa representa volumen registrado; no mide densidad de población ni congestión.
Lectura del mapa. En bicicleta se registran 1.223 desplazamientos. El mayor conteo corresponde a la(s) comuna(s) 2, con 122 registros cada una (10,0 % de la categoría). En las comunas 2, 19, 17 salen 338 registros (27,6 %).
Hay 22 comunas con registros y 0 sin registros de esta categoría en la base incluida. En la primera comuna del ranking, este modo representa el 5,4 % del total de origen de esa comuna. Esta proporción local responde una pregunta distinta al porcentaje de la categoría en toda la ciudad analizada.
Lectura del mapa. En motocicleta se registran 10.302 desplazamientos. El mayor conteo corresponde a la(s) comuna(s) 2, con 1.062 registros cada una (10,3 % de la categoría). En las comunas 2, 19, 17 salen 2.910 registros (28,2 %).
Hay 22 comunas con registros y 0 sin registros de esta categoría en la base incluida. En la primera comuna del ranking, este modo representa el 47,2 % del total de origen de esa comuna. Esta proporción local responde una pregunta distinta al porcentaje de la categoría en toda la ciudad analizada.
Lectura del mapa. En automóvil se registran 8.598 desplazamientos. El mayor conteo corresponde a la(s) comuna(s) 19, con 922 registros cada una (10,7 % de la categoría). En las comunas 19, 2, 3 salen 2.460 registros (28,6 %).
Hay 22 comunas con registros y 0 sin registros de esta categoría en la base incluida. En la primera comuna del ranking, este modo representa el 41,2 % del total de origen de esa comuna. Esta proporción local responde una pregunta distinta al porcentaje de la categoría en toda la ciudad analizada.
Lectura del mapa. En todos los modos se registran 22.139 desplazamientos. El mayor conteo corresponde a la(s) comuna(s) 2, con 3.772 registros cada una (17,0 % de la categoría). En las comunas 2, 3, 19 llegan 9.091 registros (41,1 %).
Hay 22 comunas con registros y 0 sin registros de esta categoría en la base incluida. El tono del mapa representa volumen registrado; no mide densidad de población ni congestión.
Lectura del mapa. En bicicleta se registran 1.223 desplazamientos. El mayor conteo corresponde a la(s) comuna(s) 2, con 194 registros cada una (15,9 % de la categoría). En las comunas 2, 3, 19 llegan 494 registros (40,4 %).
Hay 22 comunas con registros y 0 sin registros de esta categoría en la base incluida. En la primera comuna del ranking, este modo representa el 5,1 % del total de destino de esa comuna. Esta proporción local responde una pregunta distinta al porcentaje de la categoría en toda la ciudad analizada.
Lectura del mapa. En motocicleta se registran 10.302 desplazamientos. El mayor conteo corresponde a la(s) comuna(s) 2, con 1.734 registros cada una (16,8 % de la categoría). En las comunas 2, 3, 19 llegan 4.215 registros (40,9 %).
Hay 22 comunas con registros y 0 sin registros de esta categoría en la base incluida. En la primera comuna del ranking, este modo representa el 46,0 % del total de destino de esa comuna. Esta proporción local responde una pregunta distinta al porcentaje de la categoría en toda la ciudad analizada.
Lectura del mapa. En automóvil se registran 8.598 desplazamientos. El mayor conteo corresponde a la(s) comuna(s) 2, con 1.517 registros cada una (17,6 % de la categoría). En las comunas 2, 3, 19 llegan 3.536 registros (41,1 %).
Hay 22 comunas con registros y 0 sin registros de esta categoría en la base incluida. En la primera comuna del ranking, este modo representa el 40,2 % del total de destino de esa comuna. Esta proporción local responde una pregunta distinta al porcentaje de la categoría en toda la ciudad analizada.
lectura <- lapply(conteos, function(v) {
o <- mapa_origen %>% st_drop_geometry() %>% filter(.data[[v]] == max(.data[[v]]))
d <- mapa_destino %>% st_drop_geometry() %>% filter(.data[[v]] == max(.data[[v]]))
N <- sum(mapa_origen[[v]])
data.frame(Categoria = v, Viajes = N,
Origenes_principales = if (N > 0) paste(o$comuna, collapse = ", ") else "Sin registros",
Maximo_origen = max(mapa_origen[[v]]),
Destinos_principales = if (N > 0) paste(d$comuna, collapse = ", ") else "Sin registros",
Maximo_destino = max(mapa_destino[[v]]))
})
print(knitr::kable(bind_rows(lectura), col.names = c("Categoría", "N", "Comunas con más salidas", "n", "Comunas con más llegadas", "n"),
caption = "Lectura conjunta de los mapas; se conservan todos los empates"))| Categoría | N | Comunas con más salidas | n | Comunas con más llegadas | n |
|---|---|---|---|---|---|
| total | 22139 | 2 | 2249 | 2 | 3772 |
| bicicletas | 1223 | 2 | 122 | 2 | 194 |
| motos | 10302 | 2 | 1062 | 2 | 1734 |
| automoviles | 8598 | 19 | 922 | 2 | 1517 |
| Modo | Participación general (%) | Participación en llegadas a comuna 2 (%) | Diferencia (pp) |
|---|---|---|---|
| Bicicleta | 5,52 | 5,14 | -0,38 |
| Motocicleta | 46,53 | 45,97 | -0,56 |
| Automóvil | 38,84 | 40,22 | 1,38 |
Una diferencia positiva indica que el modo tiene mayor participación entre las llegadas a la comuna 2 que en el conjunto analizado; una negativa indica menor participación. Los denominadores incluyen todos los modos, aunque la tabla presenta únicamente bicicleta, motocicleta y automóvil.
La comuna 2 concentra la mayor atracción de viajes: recibe 3.772 llegadas (17,04 % del total) y genera 2.249 salidas (10,16 %). Su saldo de +1.523 registros equivale a 168 llegadas por cada 100 salidas. Esta comuna incluye barrios como Granada, Centenario, Versalles, San Vicente, Santa Mónica, Chipichape, La Flora, Prados del Norte, Vipasa, Juanambú y Normandía, entre otros.
Su configuración territorial permite plantear una explicación del patrón: la presencia de oferta gastronómica en Granada, comercial en Chipichape y Centenario, y de alojamiento en el entorno de la avenida Sexta–Chipichape hace plausible que parte de las llegadas esté relacionada con el acceso a bienes y servicios. Esta interpretación es consistente con su papel receptor, pero no demuestra el motivo de los viajes ni permite atribuir las llegadas a un barrio o establecimiento particular.
La atracción se mantiene entre medios de transporte: la comuna 2 recibe el 15,86 % de los destinos en bicicleta, el 16,83 % en motocicleta y el 17,64 % en automóvil. La coincidencia sugiere que su importancia como destino trasciende un vehículo específico. Además, las motocicletas representan el 45,97 % de sus llegadas, proporción cercana al 46,53 % del conjunto analizado: su elevado conteo refleja principalmente el volumen general de viajes y no una especialización marcada en este modo.
La comuna 19 presenta un patrón distinto en los orígenes: lidera las salidas en automóvil, con 922 registros (10,72 % de ese modo). Incluye barrios como San Fernando Viejo, San Fernando Nuevo, Urbanización Tequendama, Nueva Tequendama, El Lido, El Refugio, Miraflores, Santa Isabel, Champagnat, Los Cámbulos, Pampalinda y Cuarto de Legua–Guadalupe, entre otros. La presencia de vivienda y equipamientos de salud, educación y deporte permite plantear que las salidas pueden combinar desplazamientos desde hogares y desde lugares de actividad. Por ello, el origen de un viaje no debe interpretarse automáticamente como la residencia del viajero.
Los resultados sugieren funciones territoriales diferenciadas, pero no una conexión directa demostrada: la comuna 2 destaca como receptora de varios modos y la comuna 19 como principal origen de automóviles. Esto justifica examinar el intercambio entre ambas en la matriz origen–destino. Sin embargo, sus posiciones en los rankings no prueban que los automóviles que salen de la comuna 19 lleguen principalmente a la comuna 2.
La escala del análisis limita la interpretación por barrio: una comuna reúne sectores con características y actividades distintas. Los resultados permiten identificar territorios prioritarios para profundizar el estudio, pero no establecer cuáles de sus barrios explican la concentración. Para ello se necesitan destinos y orígenes más detallados, motivos de viaje y horarios.
El mapa de conteos identifica dónde hay más registros de un vehículo. La composición local identifica dónde ese vehículo representa una mayor parte de los viajes registrados. Una comuna puede destacar en una medida y no en la otra.
modos_principales <- c("Bicicleta", "Motocicleta", "Automóvil")
modal_o <- viajes %>% count(comuna = origen, modo, name = "registros") %>%
complete(comuna = 1:22, modo = modos_principales, fill = list(registros = 0)) %>%
left_join(territorio %>% select(comuna, total_local = salidas), by = "comuna") %>%
mutate(Extremo = "Origen")
modal_d <- viajes %>% count(comuna = destino, modo, name = "registros") %>%
complete(comuna = 1:22, modo = modos_principales, fill = list(registros = 0)) %>%
left_join(territorio %>% select(comuna, total_local = llegadas), by = "comuna") %>%
mutate(Extremo = "Destino")
perfiles <- bind_rows(modal_o, modal_d) %>%
filter(modo %in% modos_principales) %>%
left_join(frecuencia_modo %>% transmute(modo, cuota_global = 100 * registros / N_incluido), by = "modo") %>%
mutate(cuota_global = replace_na(cuota_global, 0),
cuota_local = if_else(total_local > 0, 100 * registros / total_local, NA_real_),
diferencia_pp = cuota_local - cuota_global)
ggplot(perfiles, aes(modo, factor(comuna, levels = 22:1), fill = cuota_local)) +
geom_tile(colour = "white") + facet_wrap(~Extremo) +
scale_fill_gradient(low = "#edf5f6", high = "#174b60", limits = c(0, 100),
na.value = "grey85", name = "% local") +
labs(title = "Composición modal dentro de cada comuna", x = NULL, y = "Comuna",
caption = "Denominador: todos los registros de la comuna en ese extremo. Gris: sin registros totales.")# Selecciono por volumen para que el mayor porcentaje no oculte un conteo pequeño.
perfil_destacado <- perfiles %>% group_by(Extremo, modo) %>%
arrange(desc(registros), comuna, .by_group = TRUE) %>% slice_head(n = 1) %>% ungroup() %>%
select(Extremo, modo, comuna, registros, total_local, cuota_local, cuota_global, diferencia_pp)
knitr::kable(perfil_destacado, digits = 1,
caption = "Comuna con más registros por modo: peso local y diferencia frente al conjunto incluido (pp)")| Extremo | modo | comuna | registros | total_local | cuota_local | cuota_global | diferencia_pp |
|---|---|---|---|---|---|---|---|
| Destino | Automóvil | 2 | 1517 | 3772 | 40.2 | 38.8 | 1.4 |
| Destino | Bicicleta | 2 | 194 | 3772 | 5.1 | 5.5 | -0.4 |
| Destino | Motocicleta | 2 | 1734 | 3772 | 46.0 | 46.5 | -0.6 |
| Origen | Automóvil | 19 | 922 | 2240 | 41.2 | 38.8 | 2.3 |
| Origen | Bicicleta | 2 | 122 | 2249 | 5.4 | 5.5 | -0.1 |
| Origen | Motocicleta | 2 | 1062 | 2249 | 47.2 | 46.5 | 0.7 |
Bicicleta: liderazgo en volumen, sin mayor participación local. La comuna 2 encabeza tanto las salidas como las llegadas en bicicleta, pero este modo representa el 5,4 % de sus orígenes y el 5,1 % de sus destinos, proporciones cercanas al 5,5 % del conjunto analizado. Su liderazgo responde principalmente al volumen de desplazamientos de la comuna; no evidencia una participación ciclista especialmente elevada.
Motocicleta: peso similar al patrón general, con predominio de llegadas. En la comuna 2, las motocicletas representan el 47,2 % de las salidas y el 46,0 % de las llegadas, frente al 46,5 % general. Aunque su participación local cambia poco, recibe 1.734 viajes y genera 1.062, con un saldo de +672 registros. La diferencia territorial se manifiesta principalmente en el volumen recibido, más que en una especialización en motocicleta.
Automóvil: mayor participación relativa en las dos comunas destacadas. En la comuna 19, el automóvil representa el 41,2 % de las salidas, 2,3 puntos porcentuales por encima del conjunto analizado. En la comuna 2 representa el 40,2 % de las llegadas, 1,4 puntos por encima. Estos resultados muestran una participación relativamente mayor del automóvil en ambos extremos, aunque las diferencias son moderadas. No permiten afirmar que los viajes que salen de la comuna 19 se dirijan a la comuna 2.
Lectura conjunta: concentración territorial con composiciones modales cercanas al promedio. Las diferencias observadas oscilan entre −0,6 y +2,3 puntos porcentuales. Por tanto, los mayores conteos de estas comunas no se acompañan de desviaciones marcadas en la composición modal de las filas presentadas. La comuna 2 destaca principalmente por concentrar llegadas de distintos vehículos, mientras la comuna 19 combina el mayor conteo de salidas en automóvil con una participación de este modo algo superior a la general.
Nota: las diferencias se calculan antes de redondear; por ello, restar los porcentajes mostrados puede producir una discrepancia de una décima. La tabla selecciona las comunas líderes por conteo en cada modo, no necesariamente aquellas con la mayor participación porcentual local.
# count(origen, destino) cuenta cada par dirigido.
# complete() incluye las 484 combinaciones y asigna cero a las no observadas.
od <- viajes %>%
count(origen, destino, name = "registros") %>%
complete(origen = 1:22, destino = 1:22, fill = list(registros = 0))
stopifnot(sum(od$registros) == nrow(viajes))
ggplot(od, aes(x = factor(destino), y = factor(origen, levels = 22:1), fill = registros)) +
geom_tile(color = "white") +
scale_fill_viridis_c(trans = "sqrt", name = "Registros") +
coord_equal() +
labs(title = "Conexiones entre comunas en la muestra",
x = "Comuna de destino", y = "Comuna de origen")Cada celda corresponde a un origen y un destino; la diagonal conserva los viajes intracomuna.
pares_principales <- od %>%
filter(origen != destino, registros > 0) %>%
arrange(desc(registros), origen, destino) %>% slice_head(n = 10)
pares_principales <- pares_principales %>%
mutate(pct_total = 100 * registros / N_incluido,
pct_intercomuna = 100 * registros / max(1, sum(viajes$origen != viajes$destino)))
knitr::kable(pares_principales, digits = 2,
caption = "Diez conexiones intercomuna: peso sobre todos los viajes y sobre los intercomuna")| origen | destino | registros | pct_total | pct_intercomuna |
|---|---|---|---|---|
| 19 | 2 | 492 | 2.22 | 2.38 |
| 17 | 2 | 340 | 1.54 | 1.65 |
| 2 | 19 | 306 | 1.38 | 1.48 |
| 17 | 22 | 294 | 1.33 | 1.42 |
| 18 | 2 | 256 | 1.16 | 1.24 |
| 2 | 3 | 252 | 1.14 | 1.22 |
| 3 | 2 | 244 | 1.10 | 1.18 |
| 17 | 3 | 236 | 1.07 | 1.14 |
| 3 | 19 | 216 | 0.98 | 1.05 |
| 18 | 3 | 210 | 0.95 | 1.02 |
# factor() trata los códigos como categorías; levels controla su orden. geom_tile() dibuja
# las celdas y coord_equal() las hace cuadradas. La transformación raíz cuadrada mejora la
# visibilidad de frecuencias pequeñas; la leyenda conserva los conteos originales.
# arrange(desc()) ordena de mayor a menor y slice_head() toma las primeras filas. El par 2 →
# 19 es distinto de 19 → 2; ninguno indica por qué calles se viajó.La matriz y la tabla muestran una concentración de llegadas hacia la comuna 2 desde distintos orígenes, especialmente desde las comunas 19, 17 y 18. La conexión 19 → 2 presenta el mayor conteo, con 492 viajes (2,38 % de los intercomuna), frente a 306 en sentido contrario. Sin embargo, las diez conexiones principales reúnen apenas el 13,8 % de los viajes intercomuna: existen destinos destacados, pero los desplazamientos se distribuyen entre múltiples conexiones.
resumen_intra <- viajes %>% group_by(modo) %>%
summarise(total = n(), intracomuna = sum(origen == destino),
intercomuna = sum(origen != destino),
pct_intra = 100 * intracomuna / total, .groups = "drop")
knitr::kable(resumen_intra, digits = 1, caption = "Viajes intracomuna por modo (% de cada modo)")| modo | total | intracomuna | intercomuna | pct_intra |
|---|---|---|---|---|
| Automóvil | 8598 | 588 | 8010 | 6.8 |
| Bicicleta | 1223 | 72 | 1151 | 5.9 |
| Motocicleta | 10302 | 676 | 9626 | 6.6 |
| Otro modo o código | 1966 | 141 | 1825 | 7.2 |
| Sin información | 50 | 4 | 46 | 8.0 |
intra_grafico <- resumen_intra %>%
pivot_longer(c(intracomuna, intercomuna), names_to = "Tipo", values_to = "registros") %>%
mutate(porcentaje = 100 * registros / total)
ggplot(intra_grafico, aes(modo, porcentaje, fill = Tipo)) +
geom_col(width = 0.65) + coord_flip() +
scale_fill_manual(values = c("intracomuna" = "#16847b", "intercomuna" = "#95a9b5")) +
labs(title = "¿Qué parte de cada modo permanece en la misma comuna?",
x = NULL, y = "% de registros de cada modo", fill = NULL)| Indicador | Registros (n) | Base de cálculo (N) | % |
|---|---|---|---|
| Viajes dentro de la misma comuna | 1.481 | 22.139 | 6,7 |
| Viajes entre comunas diferentes | 20.658 | 22.139 | 93,3 |
| Conexión 19 → 2 | 492 | 20.658 | 2,4 |
| Conexión 2 → 19 | 306 | 20.658 | 1,5 |
| Diez conexiones intercomuna principales | 2.846 | 20.658 | 13,8 |
| Demás conexiones intercomuna | 17.812 | 20.658 | 86,2 |
Distribución territorial de los viajes: de los 22.139 registros incluidos, 20.658 (93,3 %) conectan comunas diferentes y 1.481 (6,7 %) permanecen en la misma comuna. Esta comparación permite dimensionar el papel de los desplazamientos entre territorios. Cruzar una frontera administrativa no implica necesariamente recorrer una distancia mayor.
Peso de las conexiones principales: las diez conexiones intercomuna seleccionadas reúnen 2.846 viajes (13,8 %). Las demás acumulan 17.812 (86,2 %). Esta distribución permite evaluar cuánto del movimiento registrado queda explicado por un grupo reducido de pares origen–destino. Los empates se resuelven por código de origen y destino.
| Indicador | Resultado |
|---|---|
| Viajes 19 → 2 | 492 |
| Viajes 2 → 19 | 306 |
| Diferencia: 19 → 2 menos 2 → 19 | 186 |
| Variación relativa respecto al sentido 2 → 19 | 60,8 % |
Asimetría direccional: se registran 492 viajes de la comuna 19 a la 2, frente a 306 en sentido contrario. El primer sentido supera al segundo en 186 registros (60,8 %). Este resultado es consistente con el papel receptor de la comuna 2 observado en el balance general.
Predominan los desplazamientos entre comunas: 20.658 viajes (93,3 %) conectan comunas diferentes, mientras que 1.481 (6,7 %) permanecen dentro de la misma. Esto muestra que la movilidad registrada rebasa ampliamente los límites de cada comuna y respalda una lectura de conectividad entre territorios. Sin embargo, cruzar un límite administrativo no implica necesariamente recorrer una distancia mayor.
La conexión 19 → 2 presenta una asimetría direccional: registra 492 viajes, frente a 306 en sentido contrario. El flujo hacia la comuna 2 es 60,8 % mayor, lo que resulta consistente con su papel receptor en el análisis general.
La conexión principal no domina el conjunto: aunque 19 → 2 ocupa el primer lugar, representa aproximadamente el 2,4 % de los viajes intercomuna. Su liderazgo debe entenderse como el mayor conteo de una conexión particular, no como un eje que concentre la mayoría de los desplazamientos.
Los viajes se distribuyen entre numerosas conexiones: las diez principales reúnen el 13,8 % de los registros intercomuna; el 86,2 % restante corresponde a otros pares. Esto indica que la concentración de llegadas en ciertas comunas puede coexistir con una distribución amplia de conexiones: un destino importante puede recibir viajes desde múltiples orígenes.
La planificación requiere combinar destinos y conexiones: los resultados justifican profundizar en el intercambio entre las comunas 19 y 2, junto con las demás conexiones que alimentan los principales destinos. Para traducir estos enlaces en corredores de intervención se necesitan recorridos, modos de transporte y horarios, porque la matriz identifica relaciones entre comunas, pero no las vías utilizadas.
# Parto de las 22 comunas para incluir también aquellas sin registros.
balance <- data.frame(comuna = 1:22) %>%
left_join(origen_resumen %>% select(comuna, origenes = total), by = "comuna") %>%
left_join(destino_resumen %>% select(comuna, destinos = total), by = "comuna") %>%
mutate(origenes = replace_na(origenes, 0), destinos = replace_na(destinos, 0),
balance = destinos - origenes,
indice = if_else(destinos + origenes > 0,
balance / (destinos + origenes), NA_real_))
stopifnot(sum(balance$balance) == 0)
knitr::kable(balance, digits = 3, caption = "Conteos e indicadores por comuna")| comuna | origenes | destinos | balance | indice |
|---|---|---|---|---|
| 1 | 626 | 187 | -439 | -0.540 |
| 2 | 2249 | 3772 | 1523 | 0.253 |
| 3 | 1608 | 2881 | 1273 | 0.284 |
| 4 | 1193 | 1496 | 303 | 0.113 |
| 5 | 521 | 440 | -81 | -0.084 |
| 6 | 796 | 570 | -226 | -0.165 |
| 7 | 583 | 598 | 15 | 0.013 |
| 8 | 862 | 830 | -32 | -0.019 |
| 9 | 737 | 1055 | 318 | 0.177 |
| 10 | 1094 | 741 | -353 | -0.192 |
| 11 | 717 | 483 | -234 | -0.195 |
| 12 | 257 | 207 | -50 | -0.108 |
| 13 | 1031 | 560 | -471 | -0.296 |
| 14 | 574 | 316 | -258 | -0.290 |
| 15 | 974 | 481 | -493 | -0.339 |
| 16 | 836 | 645 | -191 | -0.129 |
| 17 | 1762 | 1615 | -147 | -0.044 |
| 18 | 1208 | 526 | -682 | -0.393 |
| 19 | 2240 | 2438 | 198 | 0.042 |
| 20 | 599 | 303 | -296 | -0.328 |
| 21 | 693 | 434 | -259 | -0.230 |
| 22 | 979 | 1561 | 582 | 0.229 |
ggplot(balance, aes(x = reorder(factor(comuna), balance), y = balance)) +
geom_col(fill = "#375b99") + geom_hline(yintercept = 0) + coord_flip() +
labs(title = "Balance de llegadas y salidas observadas",
x = "Comuna", y = "Destinos menos orígenes")El índice de balance territorial permite identificar si en una comuna predominan las llegadas o las salidas y dimensionar ese desequilibrio respecto al movimiento registrado. Se calcula así:
\[ I = \frac{\text{Llegadas} - \text{Salidas}} {\text{Llegadas} + \text{Salidas}} \]
Su valor varía entre −1 y 1:
A diferencia del balance absoluto, que expresa cuántas llegadas adicionales o salidas adicionales existen, el índice mide el desequilibrio relativo. Por eso una comuna con pocos registros puede presentar un índice elevado sin tener un gran volumen de desplazamientos. Ambas medidas deben interpretarse juntas.
La comuna 2 combina volumen y predominio de llegadas: registra 3.772 llegadas y 2.249 salidas, con un balance de +1.523 y un índice aproximado de 0,253. Las llegadas representan el 62,6 % de la suma de ambos extremos y las salidas el 37,4 %. Esto confirma su papel receptor dentro del conjunto analizado.
Las comunas 2, 3 y 22 presentan los mayores excedentes absolutos de llegadas: destacan como territorios receptores de desplazamientos. Sin embargo, el orden del gráfico corresponde al balance absoluto; no permite establecer cuál presenta el mayor desequilibrio relativo sin comparar sus índices.
La comuna 18 presenta el mayor excedente absoluto de salidas: en este territorio se originan más desplazamientos de los que terminan. Para determinar qué tan marcado es ese predominio respecto a su movimiento total se debe consultar su índice, además del saldo negativo.
Un balance cercano a cero no implica poca actividad: las comunas con salidas y llegadas similares pueden mantener un intercambio elevado con otros territorios. El índice describe el equilibrio entre ambos conteos, pero no la intensidad total de movilidad.
detalle_balance <- balance %>%
mutate(pct_salida = 100 * origenes / N_incluido,
pct_llegada = 100 * destinos / N_incluido,
diferencia_pp = pct_llegada - pct_salida,
llegadas_por_100_salidas = if_else(origenes > 0, 100 * destinos / origenes, NA_real_))
receptoras <- detalle_balance %>% filter(balance > 0) %>% arrange(desc(balance), comuna)
generadoras <- detalle_balance %>% filter(balance < 0) %>% arrange(balance, comuna)
knitr::kable(bind_rows(
receptoras %>% slice_head(n = 3) %>% mutate(Perfil = "Más llegadas"),
generadoras %>% slice_head(n = 3) %>% mutate(Perfil = "Más salidas")) %>%
select(Perfil, comuna, origenes, destinos, balance, diferencia_pp, llegadas_por_100_salidas),
digits = 1, caption = "Mayores diferencias absolutas y llegadas por cada 100 salidas")| Perfil | comuna | origenes | destinos | balance | diferencia_pp | llegadas_por_100_salidas |
|---|---|---|---|---|---|---|
| Más llegadas | 2 | 2249 | 3772 | 1523 | 6.9 | 167.7 |
| Más llegadas | 3 | 1608 | 2881 | 1273 | 5.8 | 179.2 |
| Más llegadas | 22 | 979 | 1561 | 582 | 2.6 | 159.4 |
| Más salidas | 18 | 1208 | 526 | -682 | -3.1 | 43.5 |
| Más salidas | 15 | 974 | 481 | -493 | -2.2 | 49.4 |
| Más salidas | 13 | 1031 | 560 | -471 | -2.1 | 54.3 |
Se observan 7 comunas con más llegadas, 15 con más salidas y 0 con conteos iguales.
Comuna 2: 2.249 salidas y 3.772 llegadas; el saldo es +1.523. Por cada 100 salidas se registran 167,7 llegadas.
Comuna 3: 1.608 salidas y 2.881 llegadas; el saldo es +1.273. Por cada 100 salidas se registran 179,2 llegadas.
Comuna 22: 979 salidas y 1.561 llegadas; el saldo es +582. Por cada 100 salidas se registran 159,4 llegadas.
Comuna 18: 1.208 salidas y 526 llegadas; el saldo es -682. Por cada 100 salidas se registran 43,5 llegadas.
Comuna 15: 974 salidas y 481 llegadas; el saldo es -493. Por cada 100 salidas se registran 49,4 llegadas.
Comuna 13: 1.031 salidas y 560 llegadas; el saldo es -471. Por cada 100 salidas se registran 54,3 llegadas.
El saldo identifica dónde predominan llegadas o salidas dentro del conjunto observado. La razón por cada 100 salidas ayuda a dimensionar la diferencia, pero puede ser muy alta cuando el número de salidas es pequeño. Ninguna de estas medidas convierte automáticamente una comuna en zona residencial, comercial o laboral; esa explicación requeriría datos del propósito del viaje o del uso del suelo.
Además del balance, podemos preguntarnos si las comunas con más salidas registradas también tienen más llegadas. Para ello comparamos los dos conteos de las 22 comunas en un diagrama de dispersión y calculamos la correlación de Pearson. No se correlacionan los códigos numéricos de las comunas: esos números son identificadores, no cantidades.
r_od <- if (sd(balance$origenes) > 0 && sd(balance$destinos) > 0) {
cor(balance$origenes, balance$destinos, method = "pearson")
} else NA_real_
ggplot(balance, aes(origenes, destinos)) +
geom_abline(slope = 1, intercept = 0, linetype = "dashed", colour = "#98a9b3") +
geom_point(size = 3.5, colour = "#16847b", alpha = 0.85) +
geom_text(aes(label = comuna), vjust = -0.7, size = 3.2, check_overlap = TRUE) +
scale_x_continuous(expand = expansion(mult = c(0.08, 0.12))) +
scale_y_continuous(expand = expansion(mult = c(0.08, 0.12))) +
coord_equal() +
labs(title = "¿Las comunas con más salidas también reciben más llegadas?",
subtitle = paste0("22 comunas · Pearson r = ", ifelse(is.na(r_od), "no definido", format(round(r_od, 3), decimal.mark = ","))),
x = "Salidas registradas", y = "Llegadas registradas",
caption = "Cada punto es una comuna. La diagonal indica igual número de salidas y llegadas.")La correlación de Pearson (r = 0,876) muestra una asociación lineal positiva fuerte: las comunas con más salidas suelen registrar también más llegadas. Esto indica que los territorios con mayor volumen de movilidad participan de manera importante en ambos extremos del viaje.
Esta asociación no implica equilibrio territorial. Las comunas 2 y 3 combinan conteos elevados con un claro predominio de llegadas; la 22 también destaca por encima de la diagonal. En contraste, comunas como la 18 y la 13 presentan más salidas que llegadas. Así, una relación general fuerte convive con funciones receptoras y generadoras diferenciadas.
El coeficiente resume los conteos de las 22 comunas y puede ser sensible a aquellas con mayores volúmenes, especialmente la 2 y la 3. Su interpretación es descriptiva: no demuestra causalidad, conexiones directas entre comunas ni dependencia espacial.
El análisis incluye 22.139 de 35.054 registros (63,2 %). Las tres comunas con más salidas —2, 19, 17— concentran el 28,2 % de los orígenes; las tres con más llegadas —2, 3, 19— reúnen el 41,1 % de los destinos. Esta concentración territorial coexiste con una distribución amplia de conexiones: las diez principales representan el 13,8 % de los viajes intercomuna.
La lectura conjunta de mapas, composición modal y matriz origen–destino permite distinguir dónde se concentra el movimiento, qué vehículos participan y cómo se relacionan los territorios. Estas tres dimensiones orientan las siguientes líneas de análisis:
| Hallazgo | Aplicación que merece estudiarse | Información necesaria para sustentar decisiones |
|---|---|---|
| La comuna 2 concentra llegadas de distintos modos | Evaluar conjuntamente las condiciones de acceso y llegada a sus actividades | Propósitos, horarios, destinos específicos y oferta de transporte |
| Las comunas presentan balances de distinto signo y magnitud | Comparar territorios receptores y generadores mediante saldo, índice y volumen total | Cobertura del levantamiento, usos del suelo y distribución horaria |
| La comuna 19 destaca como origen de automóviles | Examinar sus principales destinos y las alternativas disponibles al automóvil | Matriz por modo, tiempos, ocupación vehicular y transporte público |
| El intercambio 19 ↔︎ 2 presenta diferencias entre sentidos | Profundizar en la composición y temporalidad de esta conexión | Modo, motivo, horario y recorridos efectivos |
| Los viajes intercomuna predominan y se distribuyen entre múltiples pares | Estudiar la conectividad de la red, además de sus enlaces de mayor conteo | Transbordos, continuidad de rutas, frecuencias y accesibilidad |
| Existen comunas con pocos registros | Verificar su representación antes de asignarles menor prioridad | Diseño muestral, puntos de encuesta, horarios y factores de expansión |
Estas aplicaciones constituyen líneas de diagnóstico, no intervenciones ya justificadas. Los conteos no miden directamente congestión, riesgo vial ni déficit de infraestructura; tampoco un cero implica ausencia real de desplazamientos.
La comuna 2 destaca como un territorio de atracción con actividades diversas. Incluye barrios como Granada, Centenario, Chipichape, Versalles, San Vicente y La Flora, entre otros. La oferta gastronómica de Granada, comercial de Chipichape y Centenario, y de alojamiento del entorno avenida Sexta–Chipichape aporta contexto a sus 3.772 llegadas, frente a 2.249 salidas, y a su índice de balance de 0,253. El resultado es compatible con desplazamientos hacia actividades y servicios, aunque identificar sus motivos exige consultar esa variable en la encuesta.
La atracción de la comuna 2 se mantiene entre vehículos, sin una especialización modal marcada. Su liderazgo como destino en bicicleta, motocicleta y automóvil sugiere que distintos modos participan en el acceso a este territorio. Las motocicletas representan el 46,0 % de sus llegadas, proporción cercana al 46,5 % general. Por tanto, su principal rasgo es la concentración de destinos de varios modos, lo que justifica estudiar conjuntamente las condiciones de acceso, circulación y llegada.
La comuna 19 combina funciones residenciales y de servicios que requieren una lectura diferenciada de sus orígenes. Comprende sectores como San Fernando Viejo y Nuevo, Tequendama, Nueva Tequendama, El Lido, El Refugio y Pampalinda, y reúne equipamientos de salud, educación y deporte. El automóvil representa el 41,2 % de sus salidas, frente al 38,8 % general. Su liderazgo en salidas de este modo puede involucrar viajes desde viviendas y desde lugares de actividad; no permite atribuir todos los desplazamientos a residentes ni inferir sus ingresos o tenencia de vehículo.
El intercambio entre las comunas 19 y 2 vincula territorios con actividades diversas, pero su explicación funcional sigue abierta. Se observan 492 viajes en sentido 19 → 2 y 306 en sentido contrario. Esta asimetría es consistente con el papel receptor de la comuna 2, pero no demuestra una relación exclusiva entre vivienda y empleo ni un patrón de ida y regreso. Para establecerla se necesitan motivos, horarios y modos de los viajes de esta conexión.
La estructura observada exige combinar una mirada de red con análisis dentro de cada comuna. El 93,3 % de los viajes conecta comunas diferentes, mientras las diez conexiones principales reúnen el 13,8 % de esos registros. Existen destinos destacados alimentados por múltiples relaciones territoriales. Sin embargo, cada comuna reúne barrios y actividades heterogéneos: sus conteos agregados no permiten identificar qué sectores explican la concentración ni localizar directamente corredores de intervención.
En conjunto, los resultados orientan un diagnóstico de accesibilidad entre territorios y hacia sus actividades. Su aplicación requiere contrastar los patrones con usos del suelo, oferta de transporte, recorridos y cobertura de la encuesta. Las conclusiones describen los registros incluidos, sin expansión muestral, y no estiman por sí solas la demanda total de movilidad de Cali.
Fuentes del contexto territorial: barrios de la comuna 2, barrios de la comuna 19, oferta turística de Cali y cartografía de la comuna 19 — IDESC.
Fuentes de datos. Archivo
EncuestaOrigenDestino.xlsx y cartografía
Comunas.shp. El periodo observado se obtiene de la columna
fechas del Excel. Los empates en rankings y conexiones se
resuelven por código ascendente.
## R version 4.5.1 (2025-06-13 ucrt)
## Platform: x86_64-w64-mingw32/x64
## Running under: Windows 11 x64 (build 26100)
##
## Matrix products: default
## LAPACK version 3.12.1
##
## locale:
## [1] LC_COLLATE=Spanish_Colombia.utf8 LC_CTYPE=Spanish_Colombia.utf8
## [3] LC_MONETARY=Spanish_Colombia.utf8 LC_NUMERIC=C
## [5] LC_TIME=Spanish_Colombia.utf8
##
## time zone: America/Bogota
## tzcode source: internal
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] ggplot2_4.0.1 leaflet_2.2.3 sf_1.1-2 tidyr_1.3.2 dplyr_1.1.4
## [6] readxl_1.4.5
##
## loaded via a namespace (and not attached):
## [1] s2_1.1.11 sass_0.4.10 generics_0.1.4
## [4] class_7.3-23 KernSmooth_2.23-26 digest_0.6.39
## [7] magrittr_2.0.4 evaluate_1.0.5 grid_4.5.1
## [10] RColorBrewer_1.1-3 fastmap_1.2.0 cellranger_1.1.0
## [13] jsonlite_2.0.0 e1071_1.7-17 DBI_1.2.3
## [16] purrr_1.2.1 viridisLite_0.4.3 crosstalk_1.2.2
## [19] scales_1.4.0 jquerylib_0.1.4 cli_3.6.5
## [22] rlang_1.1.7 units_1.0-1 withr_3.0.3
## [25] cachem_1.1.0 yaml_2.3.12 otel_0.2.0
## [28] tools_4.5.1 vctrs_0.6.5 R6_2.6.1
## [31] proxy_0.4-29 lifecycle_1.0.5 classInt_0.4-11
## [34] leaflet.providers_3.0.0 htmlwidgets_1.6.4 pkgconfig_2.0.3
## [37] pillar_1.11.1 bslib_0.12.0 gtable_0.3.6
## [40] glue_1.8.0 Rcpp_1.1.1 xfun_0.55
## [43] tibble_3.3.1 tidyselect_1.2.1 rstudioapi_0.18.0
## [46] knitr_1.51 farver_2.1.2 htmltools_0.5.9
## [49] labeling_0.4.3 rmarkdown_2.31 wk_0.9.5
## [52] compiler_4.5.1 S7_0.2.1