1 Objetivo

Este documento analiza la Encuesta Origen-Destino utilizando exclusivamente los desplazamientos realizados dentro de Santiago de Cali. El criterio de inclusión exige:

  1. Municipio de origen = CALI.
  2. Municipio de destino = CALI.
  3. Comuna de origen entre 01 y 22.
  4. Comuna de destino entre 01 y 22.

En el análisis se construyeron 8 mapas:

  • 1 mapa general de orígenes.
  • 3 mapas de orígenes: bicicleta, motocicleta y automóvil.
  • 1 mapa general de destinos.
  • 3 mapas de destinos: bicicleta, motocicleta y automóvil.

Los mapas utilizan la cartografía de comunas suministrada para el desarrollo de este ejercicio.

Interpretación importante: la concentración espacial observada es descriptiva y no demuestra por sí sola causalidad. Las explicaciones sobre rutas, ciclovías, trabajo, estudio, compras, etc. se plantean como hipótesis sustentadas en las variables disponibles y deben validarse con información adicional de infraestructura, empleo, educación, usos del suelo y red vial.

2 Preparación del entorno

knitr::opts_chunk$set(
  echo = TRUE,
  message = FALSE,
  warning = FALSE,
  fig.align = "center",
  out.width = "100%"
)

paquetes <- c(
  "readxl", "dplyr", "tidyr", "stringr", "ggplot2",
  "sf", "scales", "knitr", "purrr"
)

faltantes <- paquetes[!vapply(paquetes, requireNamespace, logical(1), quietly = TRUE)]

if (length(faltantes) > 0) {
  install.packages(faltantes, repos = "https://cloud.r-project.org")
}

invisible(lapply(paquetes, library, character.only = TRUE))

options(scipen = 999)

3 Rutas de entrada

La ruta del Excel puede modificarse mediante ARCHIVO_EXCEL. La ruta de cartografía debe apuntar a la carpeta que contiene los archivos geográficos de las comunas de Cali.

ARCHIVO_EXCEL <- "C:/Users/dcruz/OneDrive - Consorcio Programa Servicios de Tránsito/Documentos/ElectivaUn1/EncuestaOrigenDestino.xlsx"

CARTO_PATH <- "C:/Users/dcruz/OneDrive - Consorcio Programa Servicios de Tránsito/Documentos/ElectivaUn1/cali"

if (!file.exists(ARCHIVO_EXCEL)) {
  stop(
    "No se encontró el archivo Excel. Revise ARCHIVO_EXCEL. ",
    "Ruta actual: ", ARCHIVO_EXCEL
  )
}

if (!dir.exists(CARTO_PATH)) {
  stop(
    "No se encontró la carpeta de cartografía. Revise CARTO_PATH. ",
    "Ruta actual: ", CARTO_PATH
  )
}

4 Carga de la base y revisión inicial

datos_raw <- readxl::read_excel(
  ARCHIVO_EXCEL,
  sheet = "Hoja1",
  .name_repair = "unique"
)

convenciones <- readxl::read_excel(
  ARCHIVO_EXCEL,
  sheet = "Convenciones (2)",
  .name_repair = "unique"
)

cat("Registros iniciales:", nrow(datos_raw), "\n")
## Registros iniciales: 35054
cat("Variables iniciales:", ncol(datos_raw), "\n")
## Variables iniciales: 28

4.1 Estandarización de texto

limpiar_texto <- function(x) {
  x |>
    as.character() |>
    stringr::str_squish() |>
    stringr::str_to_upper()
}

limpiar_comuna <- function(x) {
  z <- limpiar_texto(x)
  z <- stringr::str_extract(z, "\\d+")
  z <- stringr::str_pad(z, width = 2, side = "left", pad = "0")
  z
}

# La hoja Hoja1 contiene DOS columnas con el mismo encabezado "MUNICIPIO":
# la primera corresponde al municipio de origen y la segunda al municipio de destino.
# readxl las renombra automáticamente (por ejemplo, MUNICIPIO...7 y MUNICIPIO...11).
# Por eso NO se debe referenciar MUNICIPIO directamente.
cols_municipio <- grep("^MUNICIPIO", names(datos_raw), ignore.case = TRUE)

if (length(cols_municipio) != 2) {
  stop(
    "Se esperaban exactamente dos columnas de municipio en Hoja1, " ,
    "pero se encontraron ", length(cols_municipio), ". Revise los encabezados."
  )
}

# Se valida que las columnas de comuna y vehículo existan antes de continuar.
columnas_requeridas <- c(
  "comuna origen",
  "comuna destino",
  "TIPO DE VEHÍCULO",
  "EDAD",
  "SEXO",
  "¿QUE VA HACER A ESE LUGAR?"
)

faltantes <- setdiff(columnas_requeridas, names(datos_raw))

if (length(faltantes) > 0) {
  stop(
    "Faltan columnas requeridas en Hoja1: ",
    paste(faltantes, collapse = ", ")
  )
}

# Uso de .data[[...]] para que el código funcione tanto con MUNICIPIO...7/
# MUNICIPIO...11 como con cualquier otro nombre que genere readxl.
datos <- datos_raw |>
  mutate(
    municipio_origen = limpiar_texto(.data[[names(datos_raw)[cols_municipio[1]]]]),
    municipio_destino = limpiar_texto(.data[[names(datos_raw)[cols_municipio[2]]]]),
    comuna_origen = limpiar_comuna(.data[["comuna origen"]]),
    comuna_destino = limpiar_comuna(.data[["comuna destino"]]),
    vehiculo_codigo = suppressWarnings(as.numeric(.data[["TIPO DE VEHÍCULO"]])),
    edad_num = suppressWarnings(as.numeric(.data[["EDAD"]])),
    sexo_codigo = suppressWarnings(as.numeric(.data[["SEXO"]])),
    proposito_destino = suppressWarnings(
      as.numeric(.data[["¿QUE VA HACER A ESE LUGAR?"]])
    )
  )

comunas_validas <- sprintf("%02d", 1:22)

datos_cali <- datos |>
  filter(
    municipio_origen == "CALI",
    municipio_destino == "CALI",
    comuna_origen %in% comunas_validas,
    comuna_destino %in% comunas_validas
  ) |>
  mutate(
    vehiculo = case_when(
      vehiculo_codigo == 1 ~ "Bicicleta",
      vehiculo_codigo == 2 ~ "Motocicleta",
      vehiculo_codigo == 3 ~ "Automóvil",
      TRUE ~ NA_character_
    ),
    proposito_destino_label = case_when(
      proposito_destino == 1 ~ "A trabajar",
      proposito_destino == 2 ~ "Asuntos de trabajo",
      proposito_destino == 3 ~ "Estudiar",
      proposito_destino == 4 ~ "Atención de salud",
      proposito_destino == 5 ~ "Ver a alguien",
      proposito_destino == 6 ~ "Volver a casa",
      proposito_destino == 7 ~ "Buscar/dejar a alguien",
      proposito_destino == 8 ~ "Buscar/dejar algo",
      proposito_destino == 9 ~ "Comer/tomar algo",
      proposito_destino == 10 ~ "Compras",
      proposito_destino == 11 ~ "Trámites",
      proposito_destino == 12 ~ "Recreación",
      proposito_destino == 13 ~ "Buscar trabajo",
      proposito_destino == 77 ~ "Otra cosa",
      TRUE ~ "Sin información"
    )
  )

cat("Registros después del filtro Cali-Cali y comunas 01-22:", nrow(datos_cali), "\n")
## Registros después del filtro Cali-Cali y comunas 01-22: 22139

5 Control de calidad de los datos

El control de calidad se realiza en cuatro dimensiones:

  • completitud;
  • duplicidad;
  • consistencia de los códigos;
  • validez de las variables utilizadas para el análisis.

5.1 Completitud

completitud <- datos |>
  summarise(across(everything(), ~ mean(!is.na(.)))) |>
  pivot_longer(
    cols = everything(),
    names_to = "variable",
    values_to = "completitud"
  ) |>
  mutate(
    porcentaje_completitud = completitud * 100
  ) |>
  arrange(completitud)

ggplot(
  completitud |> slice_head(n = 15),
  aes(
    x = reorder(variable, porcentaje_completitud),
    y = porcentaje_completitud
  )
) +
  geom_col(fill = "#F6B6C8", color = "#8E6C78") +
  coord_flip() +
  scale_y_continuous(
    limits = c(0, 100),
    labels = scales::label_percent(scale = 1)
  ) +
  labs(
    title = "Control de completitud: variables con menor \n porcentaje de información",
    x = NULL,
    y = "Completitud (%)"
  ) +
  theme_minimal(base_size = 12)+
theme(
plot.title = element_text(hjust = 0.5)
)

Interpretación. Las variables abiertas y de observaciones pueden presentar una alta proporción de valores vacíos sin que esto represente necesariamente un error: en varios casos corresponden a preguntas condicionales. Para los mapas se priorizan municipio, comuna y tipo de vehículo.

5.2 Duplicados

duplicados <- sum(duplicated(datos))

tabla_duplicados <- tibble(
  Estado = c("Registros únicos", "Registros duplicados"),
  Registros = c(nrow(datos) - duplicados, duplicados)
)

ggplot(
  tabla_duplicados,
  aes(x = Estado, y = Registros, fill = Estado)
) +
  geom_col(width = 0.65, color = "#7D6570") +
  scale_fill_manual(values = c(
    "Registros únicos" = "#B8E0D2",
    "Registros duplicados" = "#F6B6C8"
  )) +
  labs(
    title = "Control de duplicidad",
    x = NULL,
    y = "Número de registros"
  ) +
  theme_minimal(base_size = 12) +
  theme(legend.position = "none")

cat("Registros iniciales:", nrow(datos), "\n")
## Registros iniciales: 35054
cat("Duplicados exactos:", duplicados, "\n")
## Duplicados exactos: 1
cat("Registros Cali-Cali con comunas 01-22:", nrow(datos_cali), "\n")
## Registros Cali-Cali con comunas 01-22: 22139
cat(
  "Registros Cali-Cali antes de validar comunas:",
  sum(datos$municipio_origen == "CALI" & datos$municipio_destino == "CALI", na.rm = TRUE),
  "\n"
)
## Registros Cali-Cali antes de validar comunas: 24528

5.3 Validez de las comunas

calidad_comunas <- tibble(
  Indicador = c(
    "Origen válido 01-22",
    "Destino válido 01-22",
    "Origen fuera de rango/no informado",
    "Destino fuera de rango/no informado"
  ),
  Registros = c(
    sum(datos$comuna_origen %in% comunas_validas, na.rm = TRUE),
    sum(datos$comuna_destino %in% comunas_validas, na.rm = TRUE),
    sum(!datos$comuna_origen %in% comunas_validas | is.na(datos$comuna_origen)),
    sum(!datos$comuna_destino %in% comunas_validas | is.na(datos$comuna_destino))
  )
)

ggplot(
  calidad_comunas,
  aes(x = reorder(Indicador, Registros), y = Registros, fill = Indicador)
) +
  geom_col(color = "#7D6570") +
  coord_flip() +
  scale_fill_brewer(palette = "Pastel1") +
  labs(
    title = "Calidad de los códigos de comuna",
    x = NULL,
    y = "Registros"
  ) +
  theme_minimal(base_size = 12) +
  theme(legend.position = "none")

5.4 5.4 Tipo de vehículo

vehiculos_qc <- datos_cali |>
  count(vehiculo, name = "registros") |>
  mutate(
    vehiculo = replace_na(vehiculo, "Sin información")
  )

ggplot(
  vehiculos_qc,
  aes(x = reorder(vehiculo, registros), y = registros, fill = vehiculo)
) +
  geom_col(color = "#7D6570") +
  coord_flip() +
  scale_fill_brewer(palette = "Pastel1") +
  labs(
    title = "Distribución del tipo de vehículo en la muestra Cali-Cali",
    x = NULL,
    y = "Registros"
  ) +
  theme_minimal(base_size = 12) +
  theme(legend.position = "none")

5.4.1 Resultado del control de calidad

La base contiene registros de viajes internos y externos. Para responder específicamente la pregunta sobre los desplazamientos dentro de Cali, el análisis elimina los registros cuyo municipio de origen o destino no corresponde a Cali y conserva solamente las comunas 01 a 22.

Además, se mantienen separados los registros sin tipo de vehículo de los tres modos solicitados. Por esta razón, los mapas específicos por modo utilizan únicamente:

  • bicicleta;
  • motocicleta;
  • automóvil.

Esto evita asignar artificialmente un modo de transporte a observaciones sin información.

6 Carga y preparación de la cartografía

El código siguiente busca automáticamente archivos geográficos dentro de CARTO_PATH. Se admiten Shapefile, GeoPackage, GeoJSON y otros formatos soportados por sf.

archivos_geo <- list.files(
  CARTO_PATH,
  pattern = "\\.(shp|gpkg|geojson|json|kml)$",
  recursive = TRUE,
  full.names = TRUE,
  ignore.case = TRUE
)

if (length(archivos_geo) == 0) {
  stop(
    "No se encontraron archivos geográficos en CARTO_PATH. ",
    "La carpeta debe contener la cartografía de las comunas de Cali."
  )
}

# Se intenta priorizar nombres relacionados con comunas.
prioridad <- grepl(
  "comun|cali|localidad|zona",
  basename(archivos_geo),
  ignore.case = TRUE
)

archivo_carto <- if (any(prioridad)) {
  archivos_geo[which(prioridad)[1]]
} else {
  archivos_geo[1]
}

cat("Archivo cartográfico seleccionado:", archivo_carto, "\n")
## Archivo cartográfico seleccionado: C:/Users/dcruz/OneDrive - Consorcio Programa Servicios de Tránsito/Documentos/ElectivaUn1/cali/Comunas.shp
mapa <- sf::st_read(
  archivo_carto,
  quiet = TRUE,
  stringsAsFactors = FALSE
)

mapa <- sf::st_make_valid(mapa)

# Unificación del sistema de referencia.
if (is.na(sf::st_crs(mapa))) {
  warning(
    "La cartografía no tiene CRS definido. Se conserva tal como fue entregada."
  )
}

names(mapa)
## [1] "OBJECTID" "gid"      "comuna"   "nombre"   "geometry"

6.1 Identificación del campo de comuna

nombres_mapa <- names(mapa)

candidatos_comuna <- nombres_mapa[
  grepl(
    "comun|cod.*com|com.*cod|codigo|código|zona",
    nombres_mapa,
    ignore.case = TRUE
  )
]

if (length(candidatos_comuna) == 0) {
  stop(
    "No fue posible identificar automáticamente el campo de comuna en la cartografía. ",
    "Revise names(mapa) y asigne manualmente el campo."
  )
}

campo_comuna <- candidatos_comuna[1]

cat("Campo de comuna seleccionado:", campo_comuna, "\n")
## Campo de comuna seleccionado: comuna
mapa <- mapa |>
  mutate(
    comuna_mapa_raw = .data[[campo_comuna]],
    comuna_mapa = limpiar_comuna(comuna_mapa_raw)
  ) |>
  filter(comuna_mapa %in% comunas_validas)

if (nrow(mapa) < 22) {
  warning(
    "La cartografía contiene menos de 22 polígonos después del filtro. ",
    "Revise el campo seleccionado: ", campo_comuna
  )
}

# Si hay múltiples geometrías por comuna, se disuelven.
mapa <- mapa |>
  group_by(comuna_mapa) |>
  summarise(geometry = sf::st_union(geometry), .groups = "drop")

7 Construcción de indicadores espaciales

origen_total <- datos_cali |>
  count(comuna_origen, name = "viajes")

destino_total <- datos_cali |>
  count(comuna_destino, name = "viajes")

origen_modo <- datos_cali |>
  filter(vehiculo %in% c("Bicicleta", "Motocicleta", "Automóvil")) |>
  count(vehiculo, comuna_origen, name = "viajes")

destino_modo <- datos_cali |>
  filter(vehiculo %in% c("Bicicleta", "Motocicleta", "Automóvil")) |>
  count(vehiculo, comuna_destino, name = "viajes")

mapear_comuna <- function(mapa, tabla, campo_tabla) {
  mapa |>
    left_join(
      tabla,
      by = setNames(campo_tabla, "comuna_mapa")
    ) |>
    mutate(viajes = replace_na(viajes, 0))
}

8 Función general para construir mapas

mapa_coropletico <- function(mapa_df, titulo, subtitulo = NULL) {

  ggplot() +
    geom_sf(
      data = mapa_df,
      aes(fill = viajes),
      color = "white",
      linewidth = 0.35
    ) +
    geom_sf_text(
      data = mapa_df,
      aes(label = comuna_mapa),
      size = 3,
      color = "grey20",
      fontface = "bold"
    ) +
    scale_fill_gradient(
      low = "#FCE4EC",
      high = "#AD1457",
      labels = scales::label_comma(),
      name = "Viajes"
    ) +
    labs(
      title = titulo,
      subtitle = subtitulo,
      caption = "Fuente: Encuesta Origen-Destino. Elaboración propia."
    ) +
    theme_void(base_size = 12) +
    theme(
      plot.title = element_text(face = "bold", size = 16),
      plot.subtitle = element_text(size = 11),
      plot.caption = element_text(size = 9),
      legend.position = "right"
    )
}

9 MAPA 1 — Orígenes de todos los desplazamientos

mapa_origen_total <- mapear_comuna(
  mapa,
  origen_total,
  "comuna_origen"
)

mapa_coropletico(
  mapa_origen_total,
  "Mapa 1. Concentración de los orígenes de los viajes",
  "Viajes internos Cali-Cali, todas las modalidades"
)

Interpretación. Las comunas con mayor intensidad representan los lugares donde se concentra una mayor cantidad de viajes que posteriormente se dirigen hacia otros puntos de Cali. El mapa debe interpretarse como una distribución de observaciones de la encuesta, no como una estimación directa de toda la población de Cali.

10 MAPAS 2–4 — Orígenes por tipo de vehículo

10.1 MAPA 2 — Bicicleta

mapa_origen_bici <- mapa |>
  left_join(
    origen_modo |> filter(vehiculo == "Bicicleta"),
    by = c("comuna_mapa" = "comuna_origen")
  ) |>
  mutate(viajes = replace_na(viajes, 0))

mapa_coropletico(
  mapa_origen_bici,
  "Mapa 2. Orígenes de viajes en bicicleta",
  "Modo: bicicleta"
)

10.2 MAPA 3 — Motocicleta

mapa_origen_moto <- mapa |>
  left_join(
    origen_modo |> filter(vehiculo == "Motocicleta"),
    by = c("comuna_mapa" = "comuna_origen")
  ) |>
  mutate(viajes = replace_na(viajes, 0))

mapa_coropletico(
  mapa_origen_moto,
  "Mapa 3. Orígenes de viajes en motocicleta",
  "Modo: motocicleta"
)

10.3 MAPA 4 — Automóvil

mapa_origen_auto <- mapa |>
  left_join(
    origen_modo |> filter(vehiculo == "Automóvil"),
    by = c("comuna_mapa" = "comuna_origen")
  ) |>
  mutate(viajes = replace_na(viajes, 0))

mapa_coropletico(
  mapa_origen_auto,
  "Mapa 4. Orígenes de viajes en automóvil",
  "Modo: automóvil"
)

11 MAPA 5 — Destinos de todos los desplazamientos

mapa_destino_total <- mapa |>
  left_join(
    destino_total,
    by = c("comuna_mapa" = "comuna_destino")
  ) |>
  mutate(viajes = replace_na(viajes, 0))

mapa_coropletico(
  mapa_destino_total,
  "Mapa 5. Concentración de los destinos de los viajes",
  "Viajes internos Cali-Cali, todas las modalidades"
)

Interpretación. Las comunas con mayor intensidad concentran los puntos de llegada declarados por las personas encuestadas. La diferencia entre el mapa de origen y el de destino permite identificar áreas que funcionan relativamente más como generadoras o atractoras de viajes.

12 MAPAS 6–8 — Destinos por tipo de vehículo

12.1 MAPA 6 — Bicicleta

mapa_destino_bici <- mapa |>
  left_join(
    destino_modo |> filter(vehiculo == "Bicicleta"),
    by = c("comuna_mapa" = "comuna_destino")
  ) |>
  mutate(viajes = replace_na(viajes, 0))

mapa_coropletico(
  mapa_destino_bici,
  "Mapa 6. Destinos de viajes en bicicleta",
  "Modo: bicicleta"
)

12.2 MAPA 7 — Motocicleta

mapa_destino_moto <- mapa |>
  left_join(
    destino_modo |> filter(vehiculo == "Motocicleta"),
    by = c("comuna_mapa" = "comuna_destino")
  ) |>
  mutate(viajes = replace_na(viajes, 0))

mapa_coropletico(
  mapa_destino_moto,
  "Mapa 7. Destinos de viajes en motocicleta",
  "Modo: motocicleta"
)

12.3 MAPA 8 — Automóvil

mapa_destino_auto <- mapa |>
  left_join(
    destino_modo |> filter(vehiculo == "Automóvil"),
    by = c("comuna_mapa" = "comuna_destino")
  ) |>
  mutate(viajes = replace_na(viajes, 0))

mapa_coropletico(
  mapa_destino_auto,
  "Mapa 8. Destinos de viajes en automóvil",
  "Modo: automóvil"
)

13 ¿Por qué determinadas comunas concentran los destinos?

El análisis espacial permite identificar patrones de atracción, pero la explicación debe apoyarse en las respuestas de propósito del viaje. Para evitar una interpretación causal no sustentada, se calcula primero la distribución de las actividades declaradas en el destino.

propositos <- datos_cali |>
  filter(vehiculo %in% c("Bicicleta", "Motocicleta", "Automóvil")) |>
  count(vehiculo, proposito_destino_label, name = "viajes") |>
  group_by(vehiculo) |>
  mutate(
    porcentaje = viajes / sum(viajes) * 100
  ) |>
  ungroup() |>
  arrange(vehiculo, desc(viajes))

knitr::kable(
  propositos |>
    group_by(vehiculo) |>
    slice_head(n = 8) |>
    select(vehiculo, proposito_destino_label, viajes, porcentaje),
  digits = 1,
  col.names = c("Vehículo", "Actividad en destino", "Viajes", "%")
)
Vehículo Actividad en destino Viajes %
Automóvil A trabajar 3630 42.2
Automóvil Asuntos de trabajo 1282 14.9
Automóvil Volver a casa 1200 14.0
Automóvil Trámites 539 6.3
Automóvil Buscar/dejar a alguien 413 4.8
Automóvil Compras 328 3.8
Automóvil Ver a alguien 289 3.4
Automóvil Atención de salud 267 3.1
Bicicleta A trabajar 679 55.5
Bicicleta Volver a casa 175 14.3
Bicicleta Asuntos de trabajo 125 10.2
Bicicleta Trámites 52 4.3
Bicicleta Compras 30 2.5
Bicicleta Buscar/dejar a alguien 27 2.2
Bicicleta Estudiar 26 2.1
Bicicleta Ver a alguien 26 2.1
Motocicleta A trabajar 5251 51.0
Motocicleta Asuntos de trabajo 1715 16.6
Motocicleta Volver a casa 1359 13.2
Motocicleta Trámites 464 4.5
Motocicleta Estudiar 258 2.5
Motocicleta Buscar/dejar a alguien 246 2.4
Motocicleta Compras 240 2.3
Motocicleta Atención de salud 206 2.0

13.1 Interpretación por modo

top_propositos <- propositos |>
  group_by(vehiculo) |>
  slice_max(viajes, n = 3, with_ties = FALSE) |>
  summarise(
    principales = paste0(
      proposito_destino_label,
      " (", round(porcentaje, 1), "%)",
      collapse = "; "
    ),
    .groups = "drop"
  )

knitr::kable(
  top_propositos,
  col.names = c("Vehículo", "Tres principales propósitos")
)
Vehículo Tres principales propósitos
Automóvil A trabajar (42.2%); Asuntos de trabajo (14.9%); Volver a casa (14%)
Bicicleta A trabajar (55.5%); Volver a casa (14.3%); Asuntos de trabajo (10.2%)
Motocicleta A trabajar (51%); Asuntos de trabajo (16.6%); Volver a casa (13.2%)

13.1.1 Bicicleta

Cuando el mapa de bicicleta muestre concentración en determinadas comunas, una explicación plausible es la proximidad entre vivienda y actividades cotidianas, especialmente trabajo, regreso a casa, estudio, compras o trámites. La existencia de infraestructura ciclista puede favorecer la elección de determinados corredores, pero la base de la encuesta no permite afirmar que una concentración se deba exclusivamente a las ciclovías.

Para comprobar esta hipótesis se recomienda cruzar posteriormente los viajes con la red de ciclovías, estaciones de bicicleta pública, pendientes, seguridad vial y distancia del viaje.

13.1.2 Motocicleta

La motocicleta puede presentar una distribución territorial amplia porque permite realizar viajes relativamente directos y flexibles entre zonas residenciales y zonas de empleo. Si las principales razones de destino corresponden a trabajo, asuntos laborales y regreso a casa, el patrón es compatible con desplazamientos cotidianos de carácter laboral.

La interpretación debe complementarse con distancia recorrida, tiempos de viaje, congestión, disponibilidad de estacionamiento y costos relativos frente al automóvil y al transporte público.

13.1.3 Automóvil

La concentración de destinos en determinadas comunas puede estar relacionada con centralidades urbanas, concentración de actividades económicas, servicios, comercio, educación, salud y otros equipamientos. Si predominan trabajo, asuntos de trabajo, regreso a casa, trámites y compras, existe evidencia descriptiva de que las actividades urbanas tienen un papel importante en la atracción de viajes.

No obstante, con esta encuesta no es posible establecer que una determinada actividad sea la causa única de la concentración espacial.

14 Matriz origen-destino

Además de los ocho mapas, se construye una matriz para identificar los pares origen-destino con mayor frecuencia.

matriz_od <- datos_cali |>
  filter(vehiculo %in% c("Bicicleta", "Motocicleta", "Automóvil")) |>
  count(vehiculo, comuna_origen, comuna_destino, name = "viajes") |>
  arrange(vehiculo, desc(viajes))

knitr::kable(
  matriz_od |>
    group_by(vehiculo) |>
    slice_head(n = 10) |>
    select(vehiculo, comuna_origen, comuna_destino, viajes),
  col.names = c("Vehículo", "Origen", "Destino", "Viajes")
)
Vehículo Origen Destino Viajes
Automóvil 19 02 215
Automóvil 02 02 189
Automóvil 17 02 128
Automóvil 17 22 116
Automóvil 02 19 106
Automóvil 19 19 101
Automóvil 03 02 96
Automóvil 02 03 93
Automóvil 03 19 90
Automóvil 18 02 90
Bicicleta 02 02 24
Bicicleta 19 02 22
Bicicleta 02 03 21
Bicicleta 18 03 21
Bicicleta 18 02 20
Bicicleta 02 19 17
Bicicleta 17 02 17
Bicicleta 17 22 17
Bicicleta 03 02 15
Bicicleta 18 19 15
Motocicleta 02 02 233
Motocicleta 19 02 212
Motocicleta 17 02 166
Motocicleta 02 19 155
Motocicleta 17 22 140
Motocicleta 18 02 131
Motocicleta 17 03 116
Motocicleta 02 03 115
Motocicleta 03 02 110
Motocicleta 19 19 109

15 Indicadores para comparar comunas generadoras y atractoras

generadoras <- datos_cali |>
  count(comuna_origen, name = "origenes")

atractoras <- datos_cali |>
  count(comuna_destino, name = "destinos")

comparacion_comunas <- tibble(
  comuna = comunas_validas
) |>
  left_join(
    generadoras,
    by = c("comuna" = "comuna_origen")
  ) |>
  left_join(
    atractoras,
    by = c("comuna" = "comuna_destino")
  ) |>
  mutate(
    origenes = replace_na(origenes, 0),
    destinos = replace_na(destinos, 0),
    balance = destinos - origenes
  ) |>
  arrange(desc(balance))

knitr::kable(
  comparacion_comunas,
  col.names = c("Comuna", "Orígenes", "Destinos", "Balance destino-origen")
)
Comuna Orígenes Destinos Balance destino-origen
02 2249 3772 1523
03 1608 2881 1273
22 979 1561 582
09 737 1055 318
04 1193 1496 303
19 2240 2438 198
07 583 598 15
08 862 830 -32
12 257 207 -50
05 521 440 -81
17 1762 1615 -147
16 836 645 -191
06 796 570 -226
11 717 483 -234
14 574 316 -258
21 693 434 -259
20 599 303 -296
10 1094 741 -353
01 626 187 -439
13 1031 560 -471
15 974 481 -493
18 1208 526 -682

El indicador balance = destinos - orígenes es descriptivo:

  • valor positivo: la comuna registra más llegadas que salidas en la muestra;
  • valor negativo: registra más salidas que llegadas;
  • valor cercano a cero: equilibrio relativo.

No debe interpretarse como un balance poblacional completo de la comuna, porque la encuesta es una muestra y su diseño de interceptación puede generar diferentes probabilidades de observación.

16 Resultados principales

resumen_modo <- datos_cali |>
  count(vehiculo, name = "viajes") |>
  mutate(
    porcentaje = viajes / sum(viajes) * 100
  ) |>
  arrange(desc(viajes))

knitr::kable(
  resumen_modo,
  digits = 1,
  col.names = c("Modo", "Viajes", "%")
)
Modo Viajes %
Motocicleta 10302 46.5
Automóvil 8598 38.8
NA 2016 9.1
Bicicleta 1223 5.5

A partir de los mapas y tablas deben destacarse tres aspectos:

  1. Generación: las comunas con mayor número de orígenes concentran una mayor cantidad de viajes observados.
  2. Atracción: las comunas con mayor número de destinos funcionan como áreas de llegada más frecuentes en la muestra.
  3. Diferenciación modal: el patrón espacial cambia según se trate de bicicleta, motocicleta o automóvil; por ello no es adecuado utilizar únicamente el mapa agregado para diseñar políticas de movilidad.

17 Conclusiones

  1. El control de calidad permite separar los viajes internos de Cali de los desplazamientos con origen o destino externo.
  2. El análisis utiliza las comunas 01–22 como unidad espacial común para origen y destino.
  3. Los ocho mapas permiten observar tanto el patrón general como las diferencias entre bicicleta, motocicleta y automóvil.
  4. Las comunas generadoras y atractoras no necesariamente coinciden. Esta diferencia puede asociarse a la distribución territorial de vivienda, empleo, comercio, servicios y equipamientos.
  5. Para bicicleta, la infraestructura ciclista constituye una hipótesis razonable para explicar parte de la concentración espacial, pero no puede demostrarse causalidad con esta base por sí sola.
  6. Para motocicleta y automóvil, los patrones pueden estar asociados con la distribución de empleo, actividades económicas, servicios, tiempos de viaje, congestión y disponibilidad de infraestructura.
  7. Los propósitos declarados en el destino son fundamentales para interpretar los mapas: trabajo, asuntos laborales, regreso a casa, estudio, trámites, compras y otras actividades permiten construir una explicación más sólida que observar únicamente el número de viajes.
  8. Para una segunda etapa se recomienda incorporar capas de ciclovías, red vial, estaciones del MIO, centralidades, establecimientos educativos, centros de empleo, comercio y equipamientos de salud.
  9. Una extensión estadística apropiada sería estimar modelos de atracción/generación de viajes por comuna y modo, incorporando exposición poblacional y variables socioeconómicas.
  10. Los resultados de este documento son descriptivos y exploratorios; no deben interpretarse como relaciones causales sin un diseño estadístico adicional.

18 Reproducibilidad

El documento lo diseñe para ejecutarse en RStudio y permite modificar únicamente las rutas de entrada:

ARCHIVO_EXCEL <- "C:/ruta/EncuestaOrigenDestino.xlsx"
CARTO_PATH <- "C:/ruta/cali"

El resto del flujo lo realiza automáticamente:

  1. lectura del Excel;
  2. selección de Hoja1;
  3. estandarización de nombres y códigos;
  4. filtro Cali-Cali;
  5. validación de comunas 01–22;
  6. control de calidad;
  7. carga de cartografía;
  8. identificación del campo de comuna;
  9. agregación de orígenes;
  10. agregación de destinos;
  11. segmentación por bicicleta, motocicleta y automóvil;
  12. construcción de los ocho mapas;
  13. análisis de propósitos de viaje;
  14. matriz origen-destino;
  15. indicadores de generación y atracción;
  16. conclusiones.

Esto se hace con el objeto de verificar la validez de los hallazgos y generar confianza en los resultados.

Nota para la cartografía: como la carpeta cali contiene varios archivos geográficos, éste código prioriza archivos cuyo nombre contiene términos como comun, cali, localidad o zona. Si existen varias capas posibles, se reviso y valido que en la salida de names(mapa) el campo identificado como campo_comuna sea efectivamente el número de comuna.