require(sf)
require(dplyr)
require(leaflet)
require(htmltools)

Datos

Se usa la encuesta de origen y destino de Cali, una encuesta de interceptación de vehículos realizada en 35 estaciones entre mayo y junio de 2015, junto con el shapefile de las 22 comunas.

Los conteos son viajes encuestados, no el total de viajes de la ciudad, así que también reflejan dónde estaban ubicadas las estaciones.

comunas=read_sf("Casos/cali/Comunas.shp") %>%
  st_transform(4326)
encuesta=readxl::read_xlsx("Casos/EncuestaOrigenDestino.xlsx")

viajes=encuesta[,c(7,9,11,13,26,27)]
names(viajes)=c("mun_origen","sdg_origen","mun_destino","sdg_destino",
                "comuna_origen","comuna_destino")

Limpieza

La columna de comuna no se puede usar directamente, porque tiene dos categorías que mezclan casos distintos.

Comuna 0. Cali no tiene comuna 0. El código SDG de cada lugar empieza por el número de su comuna, y casi todos los registros con 0 empiezan por 26: son corregimientos como Pance, La Buitrera y Montebello. Estos se agrupan como Zona rural. Los pocos que empiezan por el número de una comuna se devuelven a esa comuna.

Fuera de Cali. Esta categoría incluye lugares dentro de la ciudad cuyo código se perdió (código SDG Perdida, municipio CALI), como la Avenida Roosevelt o La 14. Estos se separan como Sin ubicar, y el resto se mantiene como Fuera de Cali.

También se eliminan los registros marcados como ANULADO.

clasificar=function(comuna, sdg, municipio) {
  prefijo=substr(sdg, 1, nchar(sdg) - 2)
  municipio=toupper(trimws(municipio))
  case_when(
    comuna %in% sprintf("%02d", 1:22) ~ sub("^0+", "", comuna),
    prefijo == "26" ~ "Zona rural",
    comuna == "0" & prefijo %in% as.character(1:22) ~ prefijo,
    sdg == "Perdida" & (is.na(municipio) | municipio %in% c("CALI", "SIN DATOS")) ~ "Sin ubicar",
    TRUE ~ "Fuera de Cali"
  )
}

viajes=viajes %>%
  filter(!(mun_origen %in% "ANULADO" | mun_destino %in% "ANULADO")) %>%
  mutate(
    zona_origen=clasificar(comuna_origen, sdg_origen, mun_origen),
    zona_destino=clasificar(comuna_destino, sdg_destino, mun_destino)
  )
# Count trips by comuna, and the ones that connect it with other municipalities
conteo_origen=viajes %>%
  count(zona_origen, name = "viajes_origen")

conteo_destino=viajes %>%
  count(zona_destino, name = "viajes_destino")

hacia_fuera=viajes %>%
  filter(zona_destino == "Fuera de Cali") %>%
  count(zona_origen, name = "hacia_fuera")

desde_fuera=viajes %>%
  filter(zona_origen == "Fuera de Cali") %>%
  count(zona_destino, name = "desde_fuera")

comunas_final=comunas %>%
  mutate(zona=as.character(comuna)) %>%
  left_join(conteo_origen, by = c("zona" = "zona_origen")) %>%
  left_join(conteo_destino, by = c("zona" = "zona_destino")) %>%
  left_join(hacia_fuera, by = c("zona" = "zona_origen")) %>%
  left_join(desde_fuera, by = c("zona" = "zona_destino")) %>%
  mutate(
    across(c(viajes_origen, viajes_destino, hacia_fuera, desde_fuera), ~coalesce(.x, 0L)),
    pct_fuera=100 * (hacia_fuera + desde_fuera) / (viajes_origen + viajes_destino)
  ) %>%
  arrange(comuna)

Mapa de origen y destino

El número sobre cada comuna es la cantidad de viajes. Use el selector de la esquina superior derecha para cambiar entre origen y destino: ambos usan la misma escala de color, así que se pueden comparar. Pase el cursor sobre una comuna para ver sus dos valores, o haga clic para ver el detalle.

miles=function(x) format(x, big.mark = ".", decimal.mark = ",", trim = TRUE)
porcentaje=function(x) sub(".", ",", sprintf("%.1f%%", x), fixed = TRUE)

centros=st_coordinates(st_point_on_surface(st_geometry(comunas_final)))

etiqueta_numero=labelOptions(
  noHide = TRUE, textOnly = TRUE, direction = "center",
  style = list(
    "color" = "#0b0b0b", "font-weight" = "600", "font-size" = "12px",
    "background" = "rgba(255,255,255,0.8)", "border-radius" = "3px", "padding" = "0 3px"
  )
)

hover=with(comunas_final, lapply(sprintf(
  "<b>%s</b><br>Origen: %s<br>Destino: %s",
  nombre, miles(viajes_origen), miles(viajes_destino)
), HTML))

popup=with(comunas_final, sprintf(
  "<b>%s</b><br>Viajes con origen aquí: %s<br>Viajes con destino aquí: %s<br>
  Hacia fuera de Cali: %s<br>Desde fuera de Cali: %s<br>Conexión con otros municipios: %s",
  nombre, miles(viajes_origen), miles(viajes_destino),
  miles(hacia_fuera), miles(desde_fuera), porcentaje(pct_fuera)
))

valores=c(comunas_final$viajes_origen, comunas_final$viajes_destino)
azules=c("#cde2fb", "#9ec5f4", "#6da7ec", "#3987e5", "#256abf", "#184f95", "#0d366b")
paleta=colorBin(azules, domain = valores, bins = pretty(valores, n = 6))

capa=function(mapa, columna, grupo) {
  mapa %>%
    addPolygons(
      fillColor = paleta(comunas_final[[columna]]), fillOpacity = 0.85,
      color = "#ffffff", weight = 1.5, group = grupo,
      label = hover, popup = popup,
      highlightOptions = highlightOptions(weight = 3, color = "#0b0b0b", bringToFront = TRUE)
    ) %>%
    addLabelOnlyMarkers(
      lng = centros[,1], lat = centros[,2], group = grupo,
      label = miles(comunas_final[[columna]]), labelOptions = etiqueta_numero
    )
}

leaflet(comunas_final, width = "100%", height = 680) %>%
  addProviderTiles(providers$Esri.WorldGrayCanvas) %>%
  capa("viajes_origen", "Origen") %>%
  capa("viajes_destino", "Destino") %>%
  addLayersControl(
    baseGroups = c("Origen", "Destino"),
    options = layersControlOptions(collapsed = FALSE)
  ) %>%
  addLegend(
    position = "bottomright", pal = paleta, values = valores,
    title = "Viajes encuestados", opacity = 0.85,
    labFormat = labelFormat(big.mark = ".")
  )

Viajes que no aparecen en el mapa

El shapefile cubre el perímetro urbano, así que estos viajes no tienen polígono. Se reportan aquí para que no se pierdan.

zonas_otras=c("Zona rural", "Fuera de Cali", "Sin ubicar")

tibble(Zona = zonas_otras) %>%
  left_join(conteo_origen, by = c("Zona" = "zona_origen")) %>%
  left_join(conteo_destino, by = c("Zona" = "zona_destino")) %>%
  mutate(across(c(viajes_origen, viajes_destino), miles)) %>%
  rename(Origen = viajes_origen, Destino = viajes_destino) %>%
  knitr::kable(align = "lrr")
Zona Origen Destino
Zona rural 695 786
Fuera de Cali 5.430 5.263
Sin ubicar 803 1.094

Conexión con otros municipios

Los viajes fuera de Cali no caben en el mapa de comunas, pero sí muestran qué tan conectada está cada comuna con la región. Este mapa muestra el porcentaje de los viajes de cada comuna que salen hacia otro municipio o llegan desde otro.

valores_fuera=comunas_final$pct_fuera
naranjas=c("#fde3d8", "#f7b89e", "#f08f68", "#eb6834", "#bd4c1f", "#8f3410")
paleta_fuera=colorBin(naranjas, domain = valores_fuera, bins = pretty(valores_fuera, n = 5))

leaflet(comunas_final, width = "100%", height = 680) %>%
  addProviderTiles(providers$Esri.WorldGrayCanvas) %>%
  addPolygons(
    fillColor = ~paleta_fuera(pct_fuera), fillOpacity = 0.85,
    color = "#ffffff", weight = 1.5,
    label = hover, popup = popup,
    highlightOptions = highlightOptions(weight = 3, color = "#0b0b0b", bringToFront = TRUE)
  ) %>%
  addLabelOnlyMarkers(
    lng = centros[,1], lat = centros[,2],
    label = sprintf("%.0f%%", valores_fuera), labelOptions = etiqueta_numero
  ) %>%
  addLegend(
    position = "bottomright", pal = paleta_fuera, values = valores_fuera,
    title = "% de viajes con<br>otros municipios", opacity = 0.85,
    labFormat = labelFormat(suffix = "%")
  )

Principales municipios conectados con Cali

La tabla cuenta los viajes con un extremo en Cali, incluida su zona rural.

en_cali=function(zona) !zona %in% c("Fuera de Cali", "Sin ubicar")
sin_municipio=c("CALI", "SIN DATOS")

hacia_cali=viajes %>%
  filter(zona_origen == "Fuera de Cali", en_cali(zona_destino)) %>%
  count(municipio = toupper(trimws(mun_origen)), name = "Hacia Cali")

desde_cali=viajes %>%
  filter(zona_destino == "Fuera de Cali", en_cali(zona_origen)) %>%
  count(municipio = toupper(trimws(mun_destino)), name = "Desde Cali")

full_join(hacia_cali, desde_cali, by = "municipio") %>%
  filter(!is.na(municipio), !municipio %in% sin_municipio) %>%
  mutate(
    across(c(`Hacia Cali`, `Desde Cali`), ~coalesce(.x, 0L)),
    Total = `Hacia Cali` + `Desde Cali`,
    Municipio = tools::toTitleCase(tolower(municipio))
  ) %>%
  arrange(desc(Total)) %>%
  slice_head(n = 8) %>%
  transmute(Municipio, `Hacia Cali` = miles(`Hacia Cali`),
            `Desde Cali` = miles(`Desde Cali`), Total = miles(Total)) %>%
  knitr::kable(align = "lrrr")
Municipio Hacia Cali Desde Cali Total
Yumbo 1.354 1.642 2.996
Jamundi 1.072 667 1.739
Palmira 827 871 1.698
Candelaria 255 278 533
Puerto Tejada 54 155 209
La Union 112 82 194
Santander De Quilichao 80 70 150
Guadalajara De Buga 73 65 138
de_paso=sum(viajes$zona_origen == "Fuera de Cali" & viajes$zona_destino == "Fuera de Cali")

Además, 636 viajes tienen ambos extremos fuera de Cali. Pasan por la ciudad sin empezar ni terminar en ella, y por eso no se cuentan en ninguna comuna.

Tabla por comuna

comunas_final %>%
  st_drop_geometry() %>%
  transmute(
    Comuna = comuna, Origen = miles(viajes_origen), Destino = miles(viajes_destino),
    `Hacia fuera` = miles(hacia_fuera), `Desde fuera` = miles(desde_fuera),
    `% otros municipios` = porcentaje(pct_fuera)
  ) %>%
  knitr::kable(align = "lrrrrr")
Comuna Origen Destino Hacia fuera Desde fuera % otros municipios
1 794 230 128 38 16,2%
2 2.968 4.810 568 855 18,3%
3 2.121 3.859 398 804 20,1%
4 1.493 1.856 229 302 15,9%
5 664 522 109 67 14,8%
6 998 716 159 124 16,5%
7 734 748 115 113 15,4%
8 1.124 1.025 187 157 16,0%
9 952 1.279 150 168 14,3%
10 1.401 927 212 143 15,2%
11 925 564 156 62 14,6%
12 348 246 70 27 16,3%
13 1.238 673 147 84 12,1%
14 685 379 82 55 12,9%
15 1.171 579 151 82 13,3%
16 993 743 119 66 10,7%
17 2.382 2.166 442 416 18,9%
18 1.536 662 235 108 15,6%
19 2.850 3.159 444 573 16,9%
20 745 360 94 36 11,8%
21 818 515 85 59 10,8%
22 1.153 1.860 127 217 11,4%