lapply(c(
"sf", "readxl", "dplyr", "stringr", "summarytools",
"leaflet", "htmltools", "mapview", "leafsync", "kableExtra",
"spatstat.geom", "spatstat.explore", "lubridate", "base64enc", "RColorBrewer",
"leaflet.extras", "leaflet.extras2"
), library, character.only = TRUE)
Definición del problema objeto de estudio
Este análisis explora los reportes de tráfico realizados por usuarios
de la aplicación Waze en el municipio de Chía, Cundinamarca, durante el
26 de septiembre de 2024. A partir de la información georreferenciada
sobre accidentes, peligros, congestiones y cierres de vía, se busca
caracterizar la distribución espacial de los eventos y contribuir a la
comprensión de patrones viales útiles para la gestión urbana y la
movilidad sostenible.
Carga y preparación de los datos
La primera etapa del análisis consistió en la carga, limpieza y
transformación inicial de los datos provenientes de la aplicación Waze.
Se utilizó la librería readxl para importar el archivo de
Excel con los reportes, y lubridate para convertir y
estandarizar la información temporal en variables de fecha y hora.
Durante la limpieza, se ajustaron las coordenadas de latitud
(lat) y longitud (lon) para corregir
inconsistencias en el formato y garantizar una representación precisa
sobre el mapa. Además, se tradujeron y homogenizaron las categorías de
los tipos de evento (type) y sus subtipos
(subtype), se rellenaron valores faltantes relevantes (por
ejemplo, subtipos según el tipo de reporte y nombres de calle conocidos
a partir de coordenadas), y se filtraron los registros para conservar
únicamente los reportes de interés en el área del municipio de
Chía y fecha seleccionada 2024-09-26. Estas
transformaciones permiten asegurar la calidad y la coherencia de la
información antes de su exploración y análisis espacial.
# data
waze <- read_excel("Data/Trama Waze.xlsx") %>%
mutate(
datetime = lubridate::ymd_hms(creation_Date),
date = as.Date(datetime),
time = format(datetime, "%H:%M:%S"),
type = dplyr::recode(type,
ACCIDENT = "Accidente",
HAZARD = "Peligro",
JAM = "Congestión",
ROAD_CLOSED = "Cierre de vía"),
lat = if_else(abs(location_y) > 100,
location_y / 10^(nchar(as.character(location_y)) - 1),
as.double(location_y)),
lon = if_else(abs(location_x) > 100,
location_x / 10^(nchar(as.character(location_x)) - 3),
as.double(location_x)),
subtype = case_when(
type == "Cierre de vía" & (is.na(subtype) | subtype == "ROAD_CLOSED_EVENT") ~ "Evento de cierre de vía",
type == "Accidente" & (is.na(subtype) | subtype == "ACCIDENT") ~ "Choque",
type == "Congestión" & (is.na(subtype) | subtype == "CONGESTION") ~ "Tráfico",
subtype == "JAM_HEAVY_TRAFFIC" ~ "Tráfico pesado",
subtype == "JAM_STAND_STILL_TRAFFIC" ~ "Tráfico detenido",
subtype == "HAZARD_ON_SHOULDER_CAR_STOPPED" ~ "Vehículo detenido en el costado de la vía",
subtype == "HAZARD_ON_ROAD" ~ "Peligro en la vía",
subtype == "HAZARD_ON_ROAD_OBJECT" ~ "Objeto en la vía",
subtype == "HAZARD_WEATHER" ~ "Condición climática peligrosa",
subtype == "HAZARD_WEATHER_FOG" ~ "Niebla",
subtype == "HAZARD_WEATHER_HEAVY_SNOW" ~ "Nieve intensa",
TRUE ~ subtype),
street = case_when(
lat == 4.938376 & lon == -74.01693 ~ "Zipaquirá-Cajicá / RN45A-04 >(S)",
lat == 4.919299 & lon == -74.01520 ~ "Calle 7ª",
lat == 4.901528 & lon == -74.02999 ~ "Puente hacia Cajicá",
lat == 4.899889 & lon == -74.03041 ~ "Variante Cajicá / RD45A Ramal A >(S)",
lat == 4.920176 & lon == -73.99840 ~ "Hatogrande",
lat == 4.921338 & lon == -73.99727 ~ "Bogotá-Tocancipá / RN55-01 >(N)",
TRUE ~ street)) %>%
filter(date == as.Date("2024-09-26"), lat > 4) %>%
select(id, date, time, type, subtype, street, lon, lat)
Resumen de eventos reportados en Waze (2024-09-26)
| Variable |
Stats / Values |
Freqs (% of Valid) |
Graph |
Missing |
| id
[numeric] |
| Mean (sd) : 1251.9 (534) | | min ≤ med ≤ max: | | 16 ≤ 1264 ≤ 2153 | | IQR (CV) : 905 (0.4) |
|
1747 distinct values |
 |
0
(0.0%) |
| date
[Date] |
1. 2024-09-26 |
|
 |
0
(0.0%) |
| time
[character] |
| 1. 23:38:02 | | 2. 23:40:03 | | 3. 23:44:02 | | 4. 19:52:02 | | 5. 19:54:02 | | 6. 23:36:02 | | 7. 23:42:02 | | 8. 23:46:02 | | 9. 23:48:02 | | 10. 23:50:02 | | [ 396 others ] |
|
| 11 | ( | 0.6% | ) | | 11 | ( | 0.6% | ) | | 11 | ( | 0.6% | ) | | 10 | ( | 0.6% | ) | | 10 | ( | 0.6% | ) | | 10 | ( | 0.6% | ) | | 10 | ( | 0.6% | ) | | 10 | ( | 0.6% | ) | | 10 | ( | 0.6% | ) | | 10 | ( | 0.6% | ) | | 1644 | ( | 94.1% | ) |
|
 |
0
(0.0%) |
| type
[character] |
| 1. Accidente | | 2. Cierre de vía | | 3. Congestión | | 4. Peligro |
|
| 36 | ( | 2.1% | ) | | 657 | ( | 37.6% | ) | | 923 | ( | 52.8% | ) | | 131 | ( | 7.5% | ) |
|
 |
0
(0.0%) |
| subtype
[character] |
| 1. Choque | | 2. Condición climática pelig | | 3. Evento de cierre de vía | | 4. Niebla | | 5. Nieve intensa | | 6. Objeto en la vía | | 7. Peligro en la vía | | 8. Tráfico | | 9. Tráfico detenido | | 10. Tráfico pesado | | 11. Vehículo detenido en el c |
|
| 36 | ( | 2.1% | ) | | 10 | ( | 0.6% | ) | | 657 | ( | 37.6% | ) | | 7 | ( | 0.4% | ) | | 6 | ( | 0.3% | ) | | 14 | ( | 0.8% | ) | | 25 | ( | 1.4% | ) | | 105 | ( | 6.0% | ) | | 360 | ( | 20.6% | ) | | 458 | ( | 26.2% | ) | | 69 | ( | 3.9% | ) |
|
 |
0
(0.0%) |
| street
[character] |
| 1. Calle 9ª Sur | | 2. Zipaquirá-Cajicá / RN45A- | | 3. Hatogrande | | 4. Cajicá-Chía / RN45A-04 | | 5. Cajicá-Tabio | | 6. Puente hacia Cajicá | | 7. Calle 3ª | | 8. Tocancipá-Bogotá / RN55-0 | | 9. Bogotá-Tocancipá / RN55-0 | | 10. Carrera 9ª | | [ 16 others ] |
|
| 636 | ( | 37.6% | ) | | 216 | ( | 12.8% | ) | | 143 | ( | 8.5% | ) | | 112 | ( | 6.6% | ) | | 87 | ( | 5.1% | ) | | 78 | ( | 4.6% | ) | | 43 | ( | 2.5% | ) | | 41 | ( | 2.4% | ) | | 35 | ( | 2.1% | ) | | 33 | ( | 2.0% | ) | | 266 | ( | 15.7% | ) |
|
 |
57
(3.3%) |
| lon
[numeric] |
| Mean (sd) : -74 (0) | | min ≤ med ≤ max: | | -74 ≤ -74 ≤ -74 | | IQR (CV) : 0 (0) |
|
103 distinct values |
 |
0
(0.0%) |
| lat
[numeric] |
| Mean (sd) : 4.9 (0) | | min ≤ med ≤ max: | | 4.9 ≤ 4.9 ≤ 4.9 | | IQR (CV) : 0 (0) |
|
102 distinct values |
 |
0
(0.0%) |
Exploración y visualización de los datos
La exploración descriptiva de los eventos reportados en Waze para el
municipio de Chía durante el 26 de septiembre de 2024 revela que las
incidencias más frecuentes corresponden a Congestiónvial
(52.8%) y Cierres de vía (37.6%), seguidas por los reportes
de Peligro (7.5%) y Accidente (2.1%). Entre
los subtipos, sobresalen los relacionados con
tráfico detenido o pesado,
eventos de cierre de vías, y
vehículos detenidos, evidenciando que los principales retos
de la jornada se asociaron a la movilidad y la gestión de incidentes
menores.
Al analizar conjuntamente la tabla resumen y la distribución espacial
en el mapa, se observa que la mayoría de los eventos se concentran a lo
largo de los corredores viales más transitados, especialmente en la
Calle 9ª Sur, la vía Zipaquirá-Cajicá /
RN45A-04, y el sector de Hatogrande. La
Calle 9ª Sur destaca visualmente en el mapa por la
acumulación de puntos correspondientes principalmente a cierres de vía,
mientras que otros tramos como la Cajicá-Chía /
RN45A-04, Variante Cajicá, y Puente
hacia Cajicá presentan densos agrupamientos de reportes de
congestión. Los accidentes, aunque menos frecuentes, se visualizan
puntualmente en sectores como la Carrera 9ª y la vía
Zipaquirá-Cajicá. Así, la observación integrada de la
información tabular y cartográfica permite identificar áreas críticas
donde se concentran los problemas viales y que requieren mayor atención
para la gestión de la movilidad urbana.
# icons
icon_files <- c(
Accidente = "icons_waze/accidente.png",
Peligro = "icons_waze/peligro.png",
Congestión = "icons_waze/congestion.png",
`Cierre de vía` = "icons_waze/cierre_via.png")
iconos <- do.call(iconList,
lapply(icon_files, makeIcon, iconWidth = 32, iconHeight = 32))
icon_b64 <- lapply(icon_files,
function(fp) paste0("data:image/png;base64,",
base64enc::base64encode(fp)))
#legend
leyenda <- htmltools::tags$div(
style = "background:rgba(255,255,255,0.8);padding:6px;",
htmltools::tags$strong("Tipo de reporte"), htmltools::tags$br(),
mapply(function(nm, img) htmltools::tags$div(
style = "margin:3px 0;",htmltools::tags$img(
src = img, width = "24px", height = "24px",
style = "vertical-align:middle;"),
htmltools::tags$span(style = "margin-left:5px;", nm)),
names(icon_b64), icon_b64, SIMPLIFY = FALSE))
# map cluster
mclus <- leaflet(waze) %>%
addProviderTiles("CartoDB.Positron") %>%
addMarkers(
lng = ~lon, lat = ~lat,
icon = iconos[as.character(waze$type)],
popup = ~paste0("<strong>", type, "</strong><br/>",
"<em>", subtype, "</em><br/>",street, "<br/>", time),
clusterOptions = markerClusterOptions()) %>%
addControl(leyenda, position = "bottomright")
# map points
mpoints <- leaflet(waze) %>%
addProviderTiles("CartoDB.Positron") %>%
addMarkers(
lng = ~lon, lat = ~lat,
icon = iconos[as.character(waze$type)],
popup = ~paste0("<strong>", type, "</strong><br/>",
"<em>", subtype, "</em><br/>",street, "<br/>", time))
# print
sync(mclus,mpoints)
Mapas de densidad Espacial
En esta etapa del análisis se generaron mapas de calor individuales
para cada tipo de evento reportado en Waze durante el día seleccionado.
Se empleó una visualización comparativa , permitiendo observar y
contrastar la distribución espacial de accidentes,
peligros, congestiones y
cierres de vía a lo largo del municipio. Los mapas de calor
revelan diferencias claras en la concentración de los reportes según el
tipo de evento. Por ejemplo, los reportes de congestión
muestran varias zonas críticas, con alta densidad especialmente en los
principales corredores viales y accesos a Chía, lo que sugiere focos
recurrentes de tráfico intenso. En contraste, los
accidentes y cierres de vía tienden a
concentrarse en áreas puntuales, mientras que los peligros
aparecen dispersos pero también destacan ciertos tramos con mayor
incidencia.
Esta visualización resulta clave para la identificación rápida de
áreas de riesgo y para priorizar intervenciones en puntos donde la
ocurrencia de eventos es más frecuente. El enfoque comparativo facilita
reconocer patrones y diferencias en el comportamiento espacial de cada
tipo de incidente, información esencial para la gestión urbana y la
planificación de estrategias orientadas a mejorar la movilidad y la
seguridad vial.
pal <- colorNumeric(
palette = rev(brewer.pal(9, "Spectral")),
domain = c(0.1, 0.3))
# FUN legend
gradient_legend <- function(titulo = "Intensidad de eventos", size = "13px") {
htmltools::tags$div(style = "background: rgba(255,255,255,0.8); padding:7px; border-radius:8px; width:185px;",
htmltools::tags$div(titulo,
style = paste("font-weight: bold; margin-bottom: 4px;",
sprintf("font-size:%s;", size))),
htmltools::tags$div(
style = sprintf("height: 18px; margin-bottom:3px;
background: linear-gradient(to right, %s);",
paste(pal(seq(0.1, 0.3, length.out=7)), collapse = ","))),
htmltools::tags$div(
style="display:flex; justify-content:space-between; font-size:11px;",
htmltools::tags$span("Bajo"),
htmltools::tags$span("Medio"),
htmltools::tags$span("Alto")))}
# FUN heatmap
mapa_calor_tipo <- function(df, tipo_evento) {
leaflet(df %>% filter(type == tipo_evento)) %>%
addProviderTiles("CartoDB.Positron") %>%
addHeatmap(lng = ~lon, lat = ~lat,
blur = 20, max = 0.08, radius = 15,
gradient = pal(seq(0.1, 0.3, length.out = 7)) %>%
setNames(round(seq(0.1, 0.3, length.out = 7), 2))) %>%
addControl(gradient_legend(paste("Heatmap", tipo_evento),
size = "15px"),position = "bottomright")}
# list types
tipos <- c("Accidente", "Peligro", "Congestión", "Cierre de vía")
# map apply
mapas <- lapply(tipos, function(t) mapa_calor_tipo(waze, t))
# maps print
sync(mapas[[1]], mapas[[2]], mapas[[3]], mapas[[4]])
Análisis de agrupamiento espacial: Conteo por cuadrantes y prueba de CSR
Para evaluar el grado de agrupamiento o dispersión espacial de los
eventos reportados en Waze, se aplicó el método de conteo por cuadrantes
(quadrat count) sobre el área de estudio, diferenciando
cada tipo de reporte. En los gráficos se observa la ubicación puntual de
los eventos, superpuestos a una cuadrícula que permite identificar la
densidad local en cada sector del municipio. En general, se evidencia un
patrón de agrupamiento espacial, con reportes que tienden a concentrarse
en celdas específicas del territorio y no se distribuyen de forma
homogénea. Esto es especialmente notorio en los casos de congestión y
cierre de vía, donde destacan celdas con conteos significativamente
altos en comparación con el resto del área.
Estos patrones visuales son consistentes con los resultados de la
prueba de cuadrantes para aleatoriedad espacial (CSR:
Complete Spatial Randomness), cuyos valores se resumen en la tabla. En
todos los casos, el estadístico de Chi-cuadrado es muy alto y los
valores de p son prácticamente cero, lo que lleva a
rechazar la hipótesis de aleatoriedad espacial. En otras palabras, los
reportes de eventos no se distribuyen de manera aleatoria, sino que
presentan una marcada tendencia a la agrupación o concentración en áreas
específicas.
# # bounding box
bb <- matrix(c(
min(waze$lon), min(waze$lat),
max(waze$lon), max(waze$lat)
), ncol=2, byrow=TRUE, dimnames = list(c("xrange", "yrange"), c("lon", "lat")))
win <- spatstat.geom::owin(xrange=range(bb[,"lon"]), yrange=range(bb[,"lat"]))
# Asignar colores para cada tipo
colores <- c("Accidente" = "#8db4ba", "Peligro" = "#fde544",
"Congestión" = "#ff5752", "Cierre de vía" = "#ff8043")
# FUN quadrat
quadrat_analisis <- function(df, tipo, nx=4, ny=4) {
pts <- spatstat.geom::ppp(
x = df$lon, y = df$lat, window = win
)
qcount <- spatstat.geom::quadratcount(pts, nx=nx, ny=ny)
qtest <- spatstat.explore::quadrat.test(pts, nx=nx, ny=ny)
list(tipo = tipo, qcount = qcount, qtest = qtest, pts = pts)
}
tipos <- c("Accidente", "Peligro", "Congestión", "Cierre de vía")
resultados <- lapply(tipos, function(tipo){
df_tipo <- waze[waze$type == tipo, ]
quadrat_analisis(df_tipo, tipo)
})
# plots
par(mfrow=c(2,2), mar=c(0.5,0.5,1,0.5))
for(i in seq_along(resultados)){
res <- resultados[[i]]
plot(res$qcount, main = paste("Conteo por cuadrantes:", res$tipo),
cex.main=2, cex.axis=1.4, cex.lab=1.4, frame.plot=FALSE, legend=FALSE)
points(res$pts, col=colores[res$tipo], pch=20, cex=1.5)
}

par(mfrow=c(1,1))
# table
tab_res <- data.frame(
Tipo = sapply(resultados, function(x) x$tipo),
Chi_sq = sapply(resultados, function(x) round(x$qtest$statistic, 2)),
gl = sapply(resultados, function(x) x$qtest$parameter),
p_value = sapply(resultados, function(x) signif(x$qtest$p.value, 3))
)
cat('<h5>`Resultados del test de cuadrantes (CSR: Complete Spatial Randomness) `</h5>')
Resultados del test de cuadrantes (CSR: Complete Spatial Randomness)
kable(tab_res, col.names = c("Tipo", "Chi_sq", "gl", "p_value")) %>%
kable_styling(full_width = FALSE)
|
Tipo
|
Chi_sq
|
gl
|
p_value
|
|
Accidente
|
479.56
|
15
|
0
|
|
Peligro
|
364.02
|
15
|
0
|
|
Congestión
|
1490.02
|
15
|
0
|
|
Cierre de vía
|
9204.48
|
15
|
0
|
Prueba de aleatoriedad espacial: Envelopes de la función K
Para evaluar en mayor detalle la aleatoriedad espacial de los
reportes, se aplicó la prueba de envelopes de la función
K de Ripley, comparando el patrón observado de cada tipo de
evento con el esperado bajo un proceso de Poisson homogéneo (CSR). En
los gráficos, la función K estimada para los datos reales (línea
continua en color) se contrasta con el envelope (banda sombreada)
obtenido a partir de 99 simulaciones bajo CSR y con la función teórica
(línea punteada). La comparación visual revela que, en todos los tipos
de reporte (Accidente, Peligro,
Congestión y Cierre de vía), la curva
observada de la función K se sitúa fuera del envelope de confianza,
superándolo para distintas distancias. Esto permite rechazar la
hipótesis de aleatoriedad espacial, evidenciando un agrupamiento
significativo de los eventos reportados.
# FUN envelope
envelope_kest <- function(df, tipo, nsim=99) {
pts <- spatstat.geom::ppp(x = df$lon, y = df$lat, window = win)
spatstat.explore::envelope(
pts,
fun = spatstat.explore::Kest,
nsim = nsim,
correction = c("iso", "trans", "border", "bord.modif"),
simulate = expression(rpoispp(pts$n / spatstat.geom::area.owin(win), win=win)),
savefuns = TRUE,
rank = 1)
}
# envelopes apply
envelopes <- lapply(tipos, function(tipo) {
df_tipo <- waze[waze$type == tipo, ]
if(nrow(df_tipo) > 4) {
envelope_kest(df_tipo, tipo, nsim = 99)
} else {NA}})
par(mfrow = c(2,2), mar = c(4,4,2,1))
for (i in seq_along(tipos)) {
if (is.list(envelopes[[i]])) {
plot(
envelopes[[i]],
main = paste("Ktest:", tipos[i]),
col = colores[tipos[i]],
legend = TRUE,
lwd = 2)}}

Conclusiones
En conjunto, los resultados del análisis exploratorio, la
visualización espacial y las pruebas estadísticas de agrupamiento ponen
en evidencia patrones de concentración no aleatoria en los reportes de
tráfico recopilados mediante la plataforma Waze. La integración de
información proveniente de usuarios en tiempo real permitió identificar
zonas críticas, horarios y tipos de eventos donde la congestión, los
cierres de vía y otros incidentes se agrupan de manera significativa,
superando lo que se esperaría bajo un patrón aleatorio. El uso de
métodos como el conteo por cuadrantes y la función K de Ripley aporta
evidencia robusta sobre la existencia de estos focos de agrupamiento, lo
que refuerza la utilidad del crowdsourcing como insumo estratégico para
la gestión de movilidad. Integrar este tipo de análisis en la toma de
decisiones no solo permite optimizar la respuesta ante emergencias, sino
que también facilita la planificación preventiva y el diseño de
políticas de movilidad urbana más eficaces y basadas en datos. De este
modo, se promueve un enfoque proactivo hacia la construcción de ciudades
más seguras, resilientes y orientadas al bienestar colectivo.