El análisis de datos de movilidad urbana permite comprender los patrones de congestión, accidentalidad y cierres viales a partir de información colaborativa como la que provee Waze. Estas fuentes enriquecen los procesos de planeación territorial mediante la integración de datos en tiempo real y técnicas de análisis espacial.
El análisis de datos de tráfico basados en reportes ciudadanos ha transformado la forma como las ciudades entienden y gestionan la movilidad. Plataformas como Waze proporcionan información detallada, georreferenciada y en tiempo real sobre eventos que afectan las vías: congestiones, cierres, accidentes y peligros.
Este informe desarrolla un estudio exploratorio y espacial de los datos de Waze, a través de técnicas de minería de datos espaciales como:
se busca comprender cómo se distribuyen los eventos y cuáles zonas del área de estudio presentan mayores afectaciones.
Esta sección presenta una vista preliminar del dataset para familiarizarnos con su estructura y variables principales antes de realizar el análisis espacial.
library(kableExtra)
library(dplyr)
library(readxl)
library(lubridate)
# Evitar notación científica en TODA la sesión
options(scipen = 999, digits = 15)
Trama_Waze <- read_excel(
"Trama Waze - Copy (1).xlsx",
)
Trama_Waze %>%
slice(1:10) %>%
kable("html", caption = "Vista preliminar del dataset Waze (sin notación científica)") %>%
kable_styling(
bootstrap_options = c("striped", "hover", "condensed"),
full_width = FALSE,
position = "center",
font_size = 13
) %>%
scroll_box(width = "80%", height = "300px")
| id | waze_json_trama_id | country | reportRating | reportByMunicipalityUser | confidence | reliability | type | uuid | roadType | magvar | subtype | street | location_x | location_y | pubMillis | creation_Date |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 16 | 14 | CO | 2 | FALSE | 1 | 8 | HAZARD | 74a153fa-6ccd-4d6b-a94b-db01a88b002d | 3 | 96 | HAZARD_ON_SHOULDER_CAR_STOPPED | NA | -74016928 | 4938376 | 1727314907000 | 2024-09-26 01:53:49.600 |
| 17 | 14 | CO | 3 | FALSE | 4 | 10 | HAZARD | b91961a4-e32c-4770-b2a0-551d7add5669 | 3 | 153 | HAZARD_ON_SHOULDER_CAR_STOPPED | Variante Cajicá / RD45A Ramal A >(S) | -74016985 | 2733202 | 1727313004000 | 2024-09-26 01:53:49.600 |
| 18 | 14 | CO | 0 | FALSE | 4 | 10 | HAZARD | b9262a77-b38b-46bd-b0b2-cda1b855a549 | 3 | 27 | HAZARD_ON_SHOULDER_CAR_STOPPED | Bogotá-Tocancipá / RN55-01 >(N) | -73996251 | 4925537 | 1727313126000 | 2024-09-26 01:53:49.600 |
| 20 | 15 | CO | 2 | FALSE | 1 | 8 | HAZARD | 74a153fa-6ccd-4d6b-a94b-db01a88b002d | 3 | 96 | HAZARD_ON_SHOULDER_CAR_STOPPED | NA | -74016928 | 4938376 | 1727314907000 | 2024-09-26 01:54:02.100 |
| 21 | 15 | CO | 3 | FALSE | 4 | 10 | HAZARD | b91961a4-e32c-4770-b2a0-551d7add5669 | 3 | 153 | HAZARD_ON_SHOULDER_CAR_STOPPED | Variante Cajicá / RD45A Ramal A >(S) | -74016985 | 2733202 | 1727313004000 | 2024-09-26 01:54:02.100 |
| 22 | 15 | CO | 0 | FALSE | 5 | 10 | HAZARD | b9262a77-b38b-46bd-b0b2-cda1b855a549 | 3 | 27 | HAZARD_ON_SHOULDER_CAR_STOPPED | Bogotá-Tocancipá / RN55-01 >(N) | -73996251 | 4925537 | 1727313126000 | 2024-09-26 01:54:02.103 |
| 24 | 16 | CO | 2 | FALSE | 1 | 8 | HAZARD | 74a153fa-6ccd-4d6b-a94b-db01a88b002d | 3 | 96 | HAZARD_ON_SHOULDER_CAR_STOPPED | NA | -74016928 | 4938376 | 1727314907000 | 2024-09-26 01:56:01.620 |
| 25 | 16 | CO | 3 | FALSE | 4 | 10 | HAZARD | b91961a4-e32c-4770-b2a0-551d7add5669 | 3 | 153 | HAZARD_ON_SHOULDER_CAR_STOPPED | Variante Cajicá / RD45A Ramal A >(S) | -74016985 | 2733202 | 1727313004000 | 2024-09-26 01:56:01.623 |
| 26 | 16 | CO | 0 | FALSE | 5 | 10 | HAZARD | b9262a77-b38b-46bd-b0b2-cda1b855a549 | 3 | 27 | HAZARD_ON_SHOULDER_CAR_STOPPED | Bogotá-Tocancipá / RN55-01 >(N) | -73996251 | 4925537 | 1727313126000 | 2024-09-26 01:56:01.627 |
| 28 | 17 | CO | 2 | FALSE | 1 | 8 | HAZARD | 74a153fa-6ccd-4d6b-a94b-db01a88b002d | 3 | 96 | HAZARD_ON_SHOULDER_CAR_STOPPED | NA | -74016928 | 4938376 | 1727314907000 | 2024-09-26 01:58:02.203 |
Antes de realizar el análisis espacial, es fundamental preparar el conjunto de datos para garantizar calidad, consistencia y la correcta creación de patrones puntuales. En esta etapa se lleva a cabo:
El objetivo de esta sección es obtener un dataset depurado que permita construir patrones espaciales confiables para el análisis de densidad, dispersión y mapas interactivos.
library(kableExtra)
library(dplyr)
library(readxl)
library(lubridate)
# ================================
# 1. Glimpse
# ================================
glimpse_tbl <- capture.output(glimpse(Trama_Waze))
kable(data.frame(Resumen = glimpse_tbl), col.names = "Estructura del Dataset") %>%
kable_styling(full_width = FALSE)
| Estructura del Dataset |
|---|
| Rows: 5,070 |
| Columns: 17 |
| $ id <dbl> 16, 17, 18, 20, 21, 22, 24, 25, 26, 28, 29, 3… |
| $ waze_json_trama_id <dbl> 14, 14, 14, 15, 15, 15, 16, 16, 16, 17, 17, 1… |
| $ country <chr> “CO”, “CO”, “CO”, “CO”, “CO”, “CO”, “CO”, “CO… |
| $ reportRating <dbl> 2, 3, 0, 2, 3, 0, 2, 3, 0, 2, 3, 0, 2, 3, 0, … |
| $ reportByMunicipalityUser <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FAL… |
| $ confidence <dbl> 1, 4, 4, 1, 4, 5, 1, 4, 5, 1, 4, 5, 1, 4, 5, … |
| $ reliability <dbl> 8, 10, 10, 8, 10, 10, 8, 10, 10, 8, 10, 10, 8… |
| $ type <chr> “HAZARD”, “HAZARD”, “HAZARD”, “HAZARD”, “HAZA… |
| $ uuid <chr> “74a153fa-6ccd-4d6b-a94b-db01a88b002d”, “b919… |
| $ roadType <dbl> 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, … |
| $ magvar <dbl> 96, 153, 27, 96, 153, 27, 96, 153, 27, 96, 15… |
| $ subtype <chr> “HAZARD_ON_SHOULDER_CAR_STOPPED”, “HAZARD_ON_… |
| $ street <chr> NA, “Variante Cajicá / RD45A Ramal A >(S)”, “… |
| $ location_x <dbl> -74016928, -74016985, -73996251, -74016928, -… |
| $ location_y <dbl> 4938376, 2733202, 4925537, 4938376, 2733202, … |
| $ pubMillis <dbl> 1727314907000, 1727313004000, 1727313126000, … |
| $ creation_Date <chr> “2024-09-26 01:53:49.600”, “2024-09-26 01:53:… |
# ================================
# 2. Resumen numérico
# ================================
resumen_numerico <- summary(select_if(Trama_Waze, is.numeric))
kable(resumen_numerico, caption = "Resumen de variables numéricas") %>%
kable_styling(full_width = FALSE)
| id | waze_json_trama_id | reportRating | confidence | reliability | roadType | magvar | location_x | location_y | pubMillis | |
|---|---|---|---|---|---|---|---|---|---|---|
| Min. : 16.0000000 | Min. : 14.00000000 | Min. :0.00000000000 | Min. :0.000000000000 | Min. : 5.00000000000 | Min. : 1.00000000000 | Min. : 3.000000000 | Min. :-74043306.0000 | Min. : 4907.00000 | Min. :1726148611000 | |
| 1st Qu.:1617.2500000 | 1st Qu.: 603.00000000 | 1st Qu.:1.00000000000 | 1st Qu.:0.000000000000 | 1st Qu.: 5.00000000000 | 1st Qu.: 1.00000000000 | 1st Qu.:104.000000000 | 1st Qu.:-74034431.0000 | 1st Qu.:4898322.00000 | 1st Qu.:1727355206000 | |
| Median :2884.5000000 | Median :1088.00000000 | Median :2.00000000000 | Median :0.000000000000 | Median : 5.00000000000 | Median : 3.00000000000 | Median :190.000000000 | Median :-74027812.0000 | Median :4912869.00000 | Median :1727442802000 | |
| Mean :2880.4122288 | Mean :1041.04398422 | Mean :2.24753451677 | Mean :0.327810650888 | Mean : 5.97179487179 | Mean : 3.86804733728 | Mean :167.963313609 | Mean :-68005820.4193 | Mean :4216919.47673 | Mean :1727208303320 | |
| 3rd Qu.:4151.7500000 | 3rd Qu.:1350.00000000 | 3rd Qu.:3.00000000000 | 3rd Qu.:0.000000000000 | 3rd Qu.: 7.00000000000 | 3rd Qu.: 7.00000000000 | 3rd Qu.:269.000000000 | 3rd Qu.:-74015457.0000 | 3rd Qu.:4921900.50000 | 3rd Qu.:1727473754000 | |
| Max. :5419.0000000 | Max. :1965.00000000 | Max. :5.00000000000 | Max. :5.000000000000 | Max. :10.00000000000 | Max. :20.00000000000 | Max. :359.000000000 | Max. : -740251.0000 | Max. :4948562.00000 | Max. :1727548520000 |
# ================================
# 3. Filas y columnas
# ================================
dim_tbl <- data.frame(
Métrica = c("Filas", "Columnas"),
Valor = c(nrow(Trama_Waze), ncol(Trama_Waze))
)
kable(dim_tbl, caption = "Dimensiones del Dataset") %>%
kable_styling(full_width = FALSE)
| Métrica | Valor |
|---|---|
| Filas | 5070 |
| Columnas | 17 |
# ================================
# 4. Faltantes por columna
# ================================
faltantes <- Trama_Waze %>%
summarise_all(~ sum(is.na(.))) %>%
t() %>% as.data.frame()
colnames(faltantes) <- "NA_count"
faltantes$columna <- rownames(faltantes)
kable(faltantes, caption = "Cantidad de NA por columna") %>%
kable_styling(full_width = FALSE)
| NA_count | columna | |
|---|---|---|
| id | 0 | id |
| waze_json_trama_id | 0 | waze_json_trama_id |
| country | 0 | country |
| reportRating | 0 | reportRating |
| reportByMunicipalityUser | 0 | reportByMunicipalityUser |
| confidence | 0 | confidence |
| reliability | 0 | reliability |
| type | 0 | type |
| uuid | 0 | uuid |
| roadType | 0 | roadType |
| magvar | 0 | magvar |
| subtype | 858 | subtype |
| street | 260 | street |
| location_x | 0 | location_x |
| location_y | 0 | location_y |
| pubMillis | 0 | pubMillis |
| creation_Date | 0 | creation_Date |
# ================================
# 5. Tipo de cada columna
# ================================
clases <- data.frame(
columna = names(Trama_Waze),
clase = sapply(Trama_Waze, class)
)
kable(clases, caption = "Tipo de datos por columna") %>%
kable_styling(full_width = FALSE)
| columna | clase | |
|---|---|---|
| id | id | numeric |
| waze_json_trama_id | waze_json_trama_id | numeric |
| country | country | character |
| reportRating | reportRating | numeric |
| reportByMunicipalityUser | reportByMunicipalityUser | logical |
| confidence | confidence | numeric |
| reliability | reliability | numeric |
| type | type | character |
| uuid | uuid | character |
| roadType | roadType | numeric |
| magvar | magvar | numeric |
| subtype | subtype | character |
| street | street | character |
| location_x | location_x | numeric |
| location_y | location_y | numeric |
| pubMillis | pubMillis | numeric |
| creation_Date | creation_Date | character |
# ================================
# 6. Valores únicos por columna
# ================================
uniques <- Trama_Waze %>%
summarise_all(~ n_distinct(.)) %>%
t() %>% as.data.frame()
colnames(uniques) <- "unique_values"
uniques$columna <- rownames(uniques)
kable(uniques, caption = "Número de valores únicos por columna") %>%
kable_styling(full_width = FALSE)
| unique_values | columna | |
|---|---|---|
| id | 5070 | id |
| waze_json_trama_id | 1279 | waze_json_trama_id |
| country | 1 | country |
| reportRating | 6 | reportRating |
| reportByMunicipalityUser | 1 | reportByMunicipalityUser |
| confidence | 6 | confidence |
| reliability | 6 | reliability |
| type | 4 | type |
| uuid | 331 | uuid |
| roadType | 7 | roadType |
| magvar | 133 | magvar |
| subtype | 15 | subtype |
| street | 44 | street |
| location_x | 349 | location_x |
| location_y | 351 | location_y |
| pubMillis | 328 | pubMillis |
| creation_Date | 4087 | creation_Date |
# ================================
# 7. Columnas sospechosas de ser fecha accidental
# ================================
sospechosas <- Trama_Waze %>%
summarise_all(~ sum(!is.na(parse_date_time(., orders = c("Ymd", "dmY", "mdY"))))) %>%
t() %>% as.data.frame()
colnames(sospechosas) <- "conteo_fechas_detectadas"
sospechosas$columna <- rownames(sospechosas)
kable(sospechosas, caption = "Columnas sospechosas de haber sido convertidas en fecha") %>%
kable_styling(full_width = FALSE)
| conteo_fechas_detectadas | columna | |
|---|---|---|
| id | 0 | id |
| waze_json_trama_id | 0 | waze_json_trama_id |
| country | 0 | country |
| reportRating | 0 | reportRating |
| reportByMunicipalityUser | 0 | reportByMunicipalityUser |
| confidence | 0 | confidence |
| reliability | 0 | reliability |
| type | 0 | type |
| uuid | 0 | uuid |
| roadType | 0 | roadType |
| magvar | 0 | magvar |
| subtype | 0 | subtype |
| street | 0 | street |
| location_x | 122 | location_x |
| location_y | 182 | location_y |
| pubMillis | 0 | pubMillis |
| creation_Date | 0 | creation_Date |
resumen_inicial <- tibble(
categoria = c("Registros totales", "Variables totales"),
cantidad = c(nrow(Trama_Waze), ncol(Trama_Waze))
)
resumen_inicial %>%
kable("html", caption = "Resumen inicial del dataset Waze") %>%
kable_styling(full_width = FALSE, font_size = 13)
| categoria | cantidad |
|---|---|
| Registros totales | 5070 |
| Variables totales | 17 |
Antes de depurar el conjunto de datos, se realiza un análisis para
identificar problemas comunes que puedan afectar el proceso de análisis
espacial.
Las categorías revisadas incluyen:
A continuación se presenta un resumen de las inconsistencias encontradas en el dataset original:
library(dplyr)
library(tidyr)
# -----------------------------
# Diagnóstico general del dataset original
# -----------------------------
# 1. Conteo de valores faltantes por variable
missing_values <- Trama_Waze %>%
summarise(across(everything(), ~sum(is.na(.)))) %>%
pivot_longer(everything(), names_to = "variable", values_to = "faltantes")
# 2. Registros con subtipo NA
subtype_na <- Trama_Waze %>% filter(is.na(subtype)) %>% nrow()
# 3. Fechas no parseables
fechas_invalidas <- Trama_Waze %>%
mutate(date_test = suppressWarnings(ymd_hms(creation_Date, quiet = TRUE))) %>%
filter(is.na(date_test)) %>% nrow()
# 4. Registros duplicados por UUID
duplicados <- Trama_Waze[duplicated(Trama_Waze), ]
duplicados <- sum(duplicated(Trama_Waze))
# -----------------------------
# Tabla resumen final
# -----------------------------
diagnostico <- tibble(
categoria = c(
"Valores faltantes (total)",
"Subtipos NA",
"Fechas no válidas",
"Registros duplicados"
),
cantidad = c(
sum(missing_values$faltantes),
subtype_na,
fechas_invalidas,
duplicados
)
)
# Mostrar tabla con estilo
diagnostico %>%
kable("html", caption = "Diagnóstico inicial de calidad del dataset Waze") %>%
kable_styling(full_width = FALSE,
bootstrap_options = c("striped", "hover", "condensed"),
font_size = 13)
| categoria | cantidad |
|---|---|
| Valores faltantes (total) | 1118 |
| Subtipos NA | 858 |
| Fechas no válidas | 0 |
| Registros duplicados | 0 |
El diagnóstico inicial revela que el dataset contiene una proporción considerable de valores faltantes, especialmente en la variable `subtype.
Estas inconsistencias impiden representar adecuadamente los eventos en un mapa y deben ser corregidas antes de continuar.
La depuración de estos registros es clave para evitar sesgos en los patrones espaciales.
Así mismo, aunque en el análisis inicial no se observa, al revisar el dataset notamos que los registros de coordenadas no contienen valores válidos, deben transformarse.
A continuación se detalla el proceso implementado para depurar el dataset y garantizar su calidad:
subtype es NAlibrary(sf)
# -----------------------------
# 1. Selección de variables clave
# -----------------------------
Waze_clean <- Trama_Waze %>%
select(
id,
type,
subtype,
roadType,
confidence,
reliability,
pubMillis,
creation_Date,
location_x,
location_y,
street
)
# -----------------------------
# 2. Eliminar observaciones sin coordenadas
# -----------------------------
Waze_clean <- Waze_clean %>%
filter(!is.na(location_x) & !is.na(location_y))
# -----------------------------
# 3. Eliminar registros cuyo subtipo era originalmente NA
# -----------------------------
Waze_clean <- Waze_clean %>%
filter(!is.na(subtype))
# 4. Estandarización coordenadas CO
# -----------------------------
# Reparar coordenadas según reglas detectadas
# -----------------------------
Waze_clean <- Trama_Waze %>%
mutate(
# Limpiar valores dejando solo números
location_x = gsub("[^0-9\\-]", "", location_x),
location_y = gsub("[^0-9]", "", location_y),
# ============================
# LONGITUD
# ============================
lon = as.numeric(
paste0(
substr(location_x, 1, 3), ".",
substr(location_x, 4, nchar(location_x))
)
),
# ============================
# LATITUD
# ============================
lat = as.numeric(
paste0(
substr(location_y, 1, 1), ".",
substr(location_y, 2, nchar(location_y))
)
)
)
# -----------------------------
# 5. Convertir fecha al formato correcto
# -----------------------------
Waze_clean <- Waze_clean %>%
mutate(
creation_Date = ymd_hms(creation_Date)
)
# 6. Eliminar duplicados
# -----------------------------
Waze_clean <- Waze_clean %>%
distinct()
# -----------------------------
# 7. Vista previa del dataset limpio (con scroll y estilo)
# -----------------------------
library(kableExtra)
resumen_final <- tibble(
categoria = c("Registros después de limpieza", "Variables finales"),
cantidad = c(nrow(Waze_clean), ncol(Waze_clean))
)
resumen_final %>%
kable("html", caption = "Resumen del dataset después de la limpieza") %>%
kable_styling(full_width = FALSE, font_size = 13)
| categoria | cantidad |
|---|---|
| Registros después de limpieza | 5070 |
| Variables finales | 19 |
comparacion <- tibble(
categoria = c(
"Número de registros",
"Subtipos NA",
"Registros duplicados"
),
antes = c(
nrow(Trama_Waze),
sum(is.na(Trama_Waze$subtype)),
Trama_Waze %>% duplicated() %>% sum()
),
despues = c(
nrow(Waze_clean),
sum(is.na(Waze_clean$subtype)),
Waze_clean %>% duplicated() %>% sum()
)
)
comparacion %>%
kable("html", caption = "Comparación del dataset antes y después de la limpieza") %>%
kable_styling(full_width = FALSE, font_size = 13)
| categoria | antes | despues |
|---|---|---|
| Número de registros | 5070 | 5070 |
| Subtipos NA | 858 | 858 |
| Registros duplicados | 0 | 0 |
Waze_clean %>%
slice(1:12) %>%
kable("html", caption = "Vista preliminar del dataset limpio") %>%
kable_styling(
full_width = FALSE,
bootstrap_options = c("striped", "hover", "condensed"),
font_size = 12
) %>%
scroll_box(width = "100%", height = "350px")
| id | waze_json_trama_id | country | reportRating | reportByMunicipalityUser | confidence | reliability | type | uuid | roadType | magvar | subtype | street | location_x | location_y | pubMillis | creation_Date | lon | lat |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 16 | 14 | CO | 2 | FALSE | 1 | 8 | HAZARD | 74a153fa-6ccd-4d6b-a94b-db01a88b002d | 3 | 96 | HAZARD_ON_SHOULDER_CAR_STOPPED | NA | -74016928 | 4938376 | 1727314907000 | 2024-09-26 01:53:49 | -74.016928 | 4.938376 |
| 17 | 14 | CO | 3 | FALSE | 4 | 10 | HAZARD | b91961a4-e32c-4770-b2a0-551d7add5669 | 3 | 153 | HAZARD_ON_SHOULDER_CAR_STOPPED | Variante Cajicá / RD45A Ramal A >(S) | -74016985 | 2733202 | 1727313004000 | 2024-09-26 01:53:49 | -74.016985 | 2.733202 |
| 18 | 14 | CO | 0 | FALSE | 4 | 10 | HAZARD | b9262a77-b38b-46bd-b0b2-cda1b855a549 | 3 | 27 | HAZARD_ON_SHOULDER_CAR_STOPPED | Bogotá-Tocancipá / RN55-01 >(N) | -73996251 | 4925537 | 1727313126000 | 2024-09-26 01:53:49 | -73.996251 | 4.925537 |
| 20 | 15 | CO | 2 | FALSE | 1 | 8 | HAZARD | 74a153fa-6ccd-4d6b-a94b-db01a88b002d | 3 | 96 | HAZARD_ON_SHOULDER_CAR_STOPPED | NA | -74016928 | 4938376 | 1727314907000 | 2024-09-26 01:54:02 | -74.016928 | 4.938376 |
| 21 | 15 | CO | 3 | FALSE | 4 | 10 | HAZARD | b91961a4-e32c-4770-b2a0-551d7add5669 | 3 | 153 | HAZARD_ON_SHOULDER_CAR_STOPPED | Variante Cajicá / RD45A Ramal A >(S) | -74016985 | 2733202 | 1727313004000 | 2024-09-26 01:54:02 | -74.016985 | 2.733202 |
| 22 | 15 | CO | 0 | FALSE | 5 | 10 | HAZARD | b9262a77-b38b-46bd-b0b2-cda1b855a549 | 3 | 27 | HAZARD_ON_SHOULDER_CAR_STOPPED | Bogotá-Tocancipá / RN55-01 >(N) | -73996251 | 4925537 | 1727313126000 | 2024-09-26 01:54:02 | -73.996251 | 4.925537 |
| 24 | 16 | CO | 2 | FALSE | 1 | 8 | HAZARD | 74a153fa-6ccd-4d6b-a94b-db01a88b002d | 3 | 96 | HAZARD_ON_SHOULDER_CAR_STOPPED | NA | -74016928 | 4938376 | 1727314907000 | 2024-09-26 01:56:01 | -74.016928 | 4.938376 |
| 25 | 16 | CO | 3 | FALSE | 4 | 10 | HAZARD | b91961a4-e32c-4770-b2a0-551d7add5669 | 3 | 153 | HAZARD_ON_SHOULDER_CAR_STOPPED | Variante Cajicá / RD45A Ramal A >(S) | -74016985 | 2733202 | 1727313004000 | 2024-09-26 01:56:01 | -74.016985 | 2.733202 |
| 26 | 16 | CO | 0 | FALSE | 5 | 10 | HAZARD | b9262a77-b38b-46bd-b0b2-cda1b855a549 | 3 | 27 | HAZARD_ON_SHOULDER_CAR_STOPPED | Bogotá-Tocancipá / RN55-01 >(N) | -73996251 | 4925537 | 1727313126000 | 2024-09-26 01:56:01 | -73.996251 | 4.925537 |
| 28 | 17 | CO | 2 | FALSE | 1 | 8 | HAZARD | 74a153fa-6ccd-4d6b-a94b-db01a88b002d | 3 | 96 | HAZARD_ON_SHOULDER_CAR_STOPPED | NA | -74016928 | 4938376 | 1727314907000 | 2024-09-26 01:58:02 | -74.016928 | 4.938376 |
| 29 | 17 | CO | 3 | FALSE | 4 | 10 | HAZARD | b91961a4-e32c-4770-b2a0-551d7add5669 | 3 | 153 | HAZARD_ON_SHOULDER_CAR_STOPPED | Variante Cajicá / RD45A Ramal A >(S) | -74016985 | 2733202 | 1727313004000 | 2024-09-26 01:58:02 | -74.016985 | 2.733202 |
| 30 | 17 | CO | 0 | FALSE | 5 | 10 | HAZARD | b9262a77-b38b-46bd-b0b2-cda1b855a549 | 3 | 27 | HAZARD_ON_SHOULDER_CAR_STOPPED | Bogotá-Tocancipá / RN55-01 >(N) | -73996251 | 4925537 | 1727313126000 | 2024-09-26 01:58:02 | -73.996251 | 4.925537 |
El proceso de limpieza permitió depurar significativamente el dataset original, eliminando registros incompletos, duplicados y con información insuficiente para su representación espacial.
El conjunto de datos resultante contiene únicamente eventos con coordenadas válidas, fechas correctamente formateadas y subtipos definidos, garantizando así la confiabilidad del análisis exploratorio y de los mapas que se presentan en las siguientes secciones.
La mejora en la calidad del dataset es esencial para la consistencia de los patrones espaciales que se identificarán posteriormente.
Adicionalmente, al revisar el dataset limpio se identifica algo que no se había tenido en cuenta y es que hay coordenadas de latitud con vares que inician por el 2, y esto no se identifica ene xcel, al revisar a detalle nos encontramos que ene xcel estaban transformadas algunas a fehca y al pasar a numero o texto en vez de 4 quedaban con el 2. Estas es necesario quitarlas para que no obstruyan nuestro análisis.
Durante la inspección del dataset limpio se detectó un caso
particular:
algunas latitudes no iniciaban en 4.xxxxx, como
corresponde a la región de Colombia, sino en
2.xxxxx.
Al revisar el archivo original en Excel, se evidenció que estos valores provenían de celdas que habían sido convertidas erróneamente al formato de fecha, lo cual generó números inconsistentes al ser importados en R.
Para evitar distorsiones en el análisis espacial, se eliminaron todos los registros cuya latitud comenzaba en “2”.
# ============================
# 1. Identificar coordenadas inválidas: latitudes que inician en "2"
# ============================
registros_invalidos <- Waze_clean %>%
filter(grepl("^2", as.character(lat)))
# Mostrar cuántos registros serán eliminados
n_invalidos <- nrow(registros_invalidos)
n_invalidos
## [1] 74
# Vista de los registros inválidos (opcional)
registros_invalidos %>%
select(id, street, location_x, location_y, lon, lat) %>%
head(20) %>%
kable("html", caption = "Ejemplos de coordenadas inválidas detectadas (latitud inicia en 2)") %>%
kable_styling(full_width = FALSE, font_size = 12)
| id | street | location_x | location_y | lon | lat |
|---|---|---|---|---|---|
| 17 | Variante Cajicá / RD45A Ramal A >(S) | -74016985 | 2733202 | -74.016985 | 2.733202 |
| 21 | Variante Cajicá / RD45A Ramal A >(S) | -74016985 | 2733202 | -74.016985 | 2.733202 |
| 25 | Variante Cajicá / RD45A Ramal A >(S) | -74016985 | 2733202 | -74.016985 | 2.733202 |
| 29 | Variante Cajicá / RD45A Ramal A >(S) | -74016985 | 2733202 | -74.016985 | 2.733202 |
| 33 | Variante Cajicá / RD45A Ramal A >(S) | -74016985 | 2733202 | -74.016985 | 2.733202 |
| 37 | Variante Cajicá / RD45A Ramal A >(S) | -74016985 | 2733202 | -74.016985 | 2.733202 |
| 41 | Variante Cajicá / RD45A Ramal A >(S) | -74016985 | 2733202 | -74.016985 | 2.733202 |
| 45 | Variante Cajicá / RD45A Ramal A >(S) | -74016985 | 2733202 | -74.016985 | 2.733202 |
| 49 | Variante Cajicá / RD45A Ramal A >(S) | -74016985 | 2733202 | -74.016985 | 2.733202 |
| 52 | Variante Cajicá / RD45A Ramal A >(S) | -74016985 | 2733202 | -74.016985 | 2.733202 |
| 55 | Variante Cajicá / RD45A Ramal A >(S) | -74016985 | 2733202 | -74.016985 | 2.733202 |
| 58 | Variante Cajicá / RD45A Ramal A >(S) | -74016985 | 2733202 | -74.016985 | 2.733202 |
| 60 | Variante Cajicá / RD45A Ramal A >(S) | -74016985 | 2733202 | -74.016985 | 2.733202 |
| 62 | Variante Cajicá / RD45A Ramal A >(S) | -74016985 | 2733202 | -74.016985 | 2.733202 |
| 64 | Variante Cajicá / RD45A Ramal A >(S) | -74016985 | 2733202 | -74.016985 | 2.733202 |
| 66 | Variante Cajicá / RD45A Ramal A >(S) | -74016985 | 2733202 | -74.016985 | 2.733202 |
| 68 | Variante Cajicá / RD45A Ramal A >(S) | -74016985 | 2733202 | -74.016985 | 2.733202 |
| 684 | Carrera 9ª | -7404324 | 2548390 | -74.043240 | 2.548390 |
| 689 | Carrera 9ª | -7404324 | 2548390 | -74.043240 | 2.548390 |
| 694 | Carrera 9ª | -7404324 | 2548390 | -74.043240 | 2.548390 |
# ============================
# 2. Eliminar los registros inválidos
# ============================
Waze_clean <- Waze_clean %>%
filter(!grepl("^2", as.character(lat)))
# Confirmar cuántos registros quedan ahora
nrow(Waze_clean)
## [1] 4996
La revisión final del dataset permitió identificar un tipo adicional de inconsistencia que no era evidente en la estructura original del archivo: varias observaciones presentaban valores de latitud que comenzaban en “2”, lo cual es imposible dentro del rango geográfico de Colombia (latitudes entre 4° y 12° aproximadamente).
Al investigar el origen del problema se encontró que dichas coordenadas provenían de celdas que, en el archivo Excel, habían sido interpretadas como fechas y convertidas automáticamente a números seriales. Esto generó valores erróneos que no solo eran incompatibles con la geografía real, sino que también distorsionaban la representación espacial de los eventos en el análisis exploratorio.
Tras aplicar el filtro correspondiente, se eliminaron 74 registros afectados por esta inconsistencia. El dataset final quedó conformado por 4.996 observaciones, todas con coordenadas válidas y coherentes con el territorio colombiano.
Esta depuración adicional garantiza que:
la visualización geográfica no incluya puntos fuera del país,
los patrones espaciales no se vean distorsionados por errores de origen externo,
y el análisis posterior se realice sobre datos completamente confiables y consistentes.
Con esta corrección, se concluye que el dataset limpio representa de manera adecuada la ubicación real de los eventos reportados y está en condiciones óptimas para los análisis espaciales siguientes.
Este apartado presenta un análisis descriptivo inicial del dataset ya
depurado.
El objetivo es comprender la estructura general de los incidentes
reportados en Waze y obtener una visión preliminar sobre las variables
más importantes del estudio, tales como:
type);subtype);confidence, reliability);A continuación, se presentan los primeros resúmenes estadísticos del conjunto de datos limpio.
# =============================
# Exploración descriptiva inicial
# =============================
library(dplyr)
library(kableExtra)
# -----------------------------
# Número total de eventos
# -----------------------------
resumen_basico <- tibble(
Total_eventos = nrow(Waze_clean),
Tipos_unicos = n_distinct(Waze_clean$type),
Subtipos_unicos = n_distinct(Waze_clean$subtype),
Calles_con_nombre = sum(!is.na(Waze_clean$street))
)
resumen_basico %>%
kable("html", caption = "Resumen general del dataset limpio") %>%
kable_styling(
full_width = FALSE,
bootstrap_options = c("striped", "hover", "condensed"),
font_size = 14
)
| Total_eventos | Tipos_unicos | Subtipos_unicos | Calles_con_nombre |
|---|---|---|---|
| 4996 | 4 | 15 | 4741 |
# -----------------------------
# Frecuencia de tipos de incidentes
# -----------------------------
tabla_tipos <- Waze_clean %>%
count(type, sort = TRUE)
tabla_tipos %>%
kable("html", caption = "Frecuencia de tipos de incidentes") %>%
kable_styling(
full_width = FALSE,
bootstrap_options = c("striped", "hover", "condensed"),
font_size = 13
) %>%
scroll_box(width = "90%", height = "300px")
| type | n |
|---|---|
| JAM | 3151 |
| ROAD_CLOSED | 1021 |
| HAZARD | 702 |
| ACCIDENT | 122 |
# -----------------------------
# Frecuencia de subtipos
# -----------------------------
tabla_subtipos <- Waze_clean %>%
count(subtype, sort = TRUE)
tabla_subtipos %>%
kable("html", caption = "Frecuencia de subtipos reportados") %>%
kable_styling(
full_width = FALSE,
bootstrap_options = c("striped", "hover", "condensed"),
font_size = 11
) %>%
scroll_box(width = "90%", height = "350px")
| subtype | n |
|---|---|
| JAM_STAND_STILL_TRAFFIC | 1334 |
| JAM_HEAVY_TRAFFIC | 1203 |
| ROAD_CLOSED_EVENT | 938 |
| NA | 818 |
| HAZARD_ON_SHOULDER_CAR_STOPPED | 336 |
| HAZARD_ON_ROAD_CONSTRUCTION | 142 |
| HAZARD_ON_ROAD | 110 |
| HAZARD_WEATHER_FLOOD | 32 |
| HAZARD_WEATHER | 21 |
| HAZARD_ON_ROAD_POT_HOLE | 17 |
| HAZARD_ON_ROAD_TRAFFIC_LIGHT_FAULT | 17 |
| HAZARD_ON_ROAD_OBJECT | 14 |
| HAZARD_WEATHER_FOG | 7 |
| HAZARD_WEATHER_HEAVY_SNOW | 6 |
| ACCIDENT_MAJOR | 1 |
En esta sección realizamos un análisis exploratorio básico gráfico de las variables más relevantes del dataset Waze, incluyendo:
Este análisis permitirá comprender mejor la naturaleza de los reportes antes de representarlos geográficamente.
library(dplyr)
library(ggplot2)
library(kableExtra)
# =============================
# 1. Frecuencia de tipos de eventos
# =============================
tabla_type <- Waze_clean %>%
count(type, sort = TRUE)
tabla_type %>%
kable("html", caption = "Frecuencia de tipos de eventos Waze") %>%
kable_styling(
full_width = FALSE,
bootstrap_options = c("striped", "hover", "condensed"),
font_size = 12
) %>%
scroll_box(width = "80%", height = "250px")
| type | n |
|---|---|
| JAM | 3151 |
| ROAD_CLOSED | 1021 |
| HAZARD | 702 |
| ACCIDENT | 122 |
# =============================
# 2. Frecuencia de subtipos
# =============================
tabla_subtype <- Waze_clean %>%
count(subtype, sort = TRUE)
tabla_subtype %>%
kable("html", caption = "Frecuencia de subtipos de eventos Waze") %>%
kable_styling(
full_width = FALSE,
bootstrap_options = c("striped", "hover", "condensed"),
font_size = 12
) %>%
scroll_box(width = "85%", height = "300px")
| subtype | n |
|---|---|
| JAM_STAND_STILL_TRAFFIC | 1334 |
| JAM_HEAVY_TRAFFIC | 1203 |
| ROAD_CLOSED_EVENT | 938 |
| NA | 818 |
| HAZARD_ON_SHOULDER_CAR_STOPPED | 336 |
| HAZARD_ON_ROAD_CONSTRUCTION | 142 |
| HAZARD_ON_ROAD | 110 |
| HAZARD_WEATHER_FLOOD | 32 |
| HAZARD_WEATHER | 21 |
| HAZARD_ON_ROAD_POT_HOLE | 17 |
| HAZARD_ON_ROAD_TRAFFIC_LIGHT_FAULT | 17 |
| HAZARD_ON_ROAD_OBJECT | 14 |
| HAZARD_WEATHER_FOG | 7 |
| HAZARD_WEATHER_HEAVY_SNOW | 6 |
| ACCIDENT_MAJOR | 1 |
# =============================
# 3. Distribución de niveles de confianza
# =============================
gg_conf <- ggplot(Waze_clean, aes(x = confidence)) +
geom_bar(fill = "#2C7FB8") +
theme_minimal() +
labs(
title = "Distribución del nivel de confianza",
x = "Confianza",
y = "Frecuencia"
)
gg_conf
# =============================
# 4. Distribución de confiabilidad
# =============================
gg_rel <- ggplot(Waze_clean, aes(x = reliability)) +
geom_bar(fill = "#41B6C4") +
theme_minimal() +
labs(
title = "Distribución del nivel de confiabilidad",
x = "Confiabilidad",
y = "Frecuencia"
)
gg_rel
# =============================
# 5. Serie temporal de reportes
# =============================
Waze_clean %>%
mutate(fecha = as.Date(creation_Date)) %>%
count(fecha) %>%
ggplot(aes(x = fecha, y = n)) +
geom_line(color = "#253494", size = 1) +
geom_point(color = "#253494") +
theme_minimal() +
labs(
title = "Cantidad de reportes por día",
x = "Fecha",
y = "Número de reportes"
)
La exploración inicial permite afirmar que el dataset, una vez depurado, presenta consistencia suficiente para avanzar hacia análisis espaciales más complejos, como:
Mapas de densidad
Identificación de hotspots de tráfico y riesgos
Análisis espacio-temporales
Modelamiento de congestión o peligros viales
En esta sección el objetivo es identificar la distribución geográfica de los eventos reportados y reconocer posibles patrones espaciales relevantes para el análisis posterior.
El análisis se realiza mediante un mapa interactivo en Leaflet, permitiendo visualizar cada evento en su ubicación exacta y acceder a sus atributos.
library(leaflet)
library(sf)
library(dplyr)
# Vista previa
head(Waze_clean[, c("lon", "lat")])
## # A tibble: 6 × 2
## lon lat
## <dbl> <dbl>
## 1 -74.0 4.94
## 2 -74.0 4.93
## 3 -74.0 4.94
## 4 -74.0 4.93
## 5 -74.0 4.94
## 6 -74.0 4.93
leaflet(Waze_clean) %>%
addTiles() %>% # Mapa base OpenStreetMap
addCircleMarkers(
lng = ~lon,
lat = ~lat,
radius = 4,
color = "#2A81CB",
stroke = FALSE,
fillOpacity = 0.6,
popup = ~paste0(
"<b>Tipo:</b> ", type, "<br>",
"<b>Subtipo:</b> ", subtype, "<br>",
"<b>Confianza:</b> ", confidence, "<br>",
"<b>Confiabilidad:</b> ", reliability, "<br>",
"<b>Fecha:</b> ", creation_Date
)
) %>%
addScaleBar(position = "bottomleft") %>%
addMiniMap(toggleDisplay = TRUE)
En esta sección se analizan los patrones espaciales presentes en los eventos reportados por Waze. La visualización de puntos aislados permite identificar la ubicación individual de cada reporte, pero no revela claramente las zonas de mayor concentración.
Por ello, se emplean técnicas de análisis espacial como mapas de calor (heatmaps) y agrupación de puntos (clustering), que permiten:
A continuación, se genera un mapa de calor que muestra la intensidad de reportes.
library(leaflet)
library(leaflet.extras)
library(dplyr)
# ============================
# 1. Mapa de calor (heatmap)
# ============================
heatmap_waze <- leaflet(Waze_clean) %>%
addTiles() %>%
addControl(
html = "<div style='width:100%; text-align:center;
font-size:20px; font-weight:bold;
color:#D9534F; margin-top:10px;'>
Mapa 7.2.1 – Mapa de calor de eventos reportados en Waze
</div>",
position = 'topright'
) %>%
addHeatmap(
lng = ~lon,
lat = ~lat,
radius = 15,
blur = 25,
max = 0.1
)
heatmap_waze
El mapa tipo heatmap suaviza la distribución espacial y permite identificar “manchas calientes” donde los eventos se acumulan más intensamente.
Hallazgos principales
La zona más crítica está alineada con el eje vial norte–sur, lo cual coincide con los patrones observados en el mapa de clusters.
Se observan tres núcleos de calor sobresalientes:
*Canelón – Centro de Cajicá
*Cruce hacia Hato Grande
*Sector sur–Calahorra, donde la densidad es incluso mayor
La distribución del calor es continua: se forma una franja lineal de alta intensidad que sigue las vías principales.
Interpretación técnica
El heatmap revela que las condiciones reportadas no son puntuales, sino que afectan tramos completos de la red vial, lo cual puede deberse a:
Altos volúmenes de tráfico en horas pico
Interacción entre vías locales y troncales
Sectores donde pequeñas perturbaciones generan gran impacto (sensibilidad vial)
El heatmap también permite visualizar comportamientos difusos: zonas donde múltiples eventos ocurren cerca pero no exactamente en el mismo punto. Esto sugiere problemas extendidos, no focalizados.
# ============================
# 2. Mapa con clusters
# ============================
cluster_waze <- leaflet(Waze_clean) %>%
addTiles() %>%
addControl(
html = "<div style='width:100%; text-align:center;
font-size:20px; font-weight:bold;
color:#2A4B7C; margin-top:10px;'>
Mapa 7.2.2 – Agrupación de eventos reportados en Waze
</div>",
position = "topright"
) %>%
addMarkers(
lng = ~lon,
lat = ~lat,
clusterOptions = markerClusterOptions()
)
cluster_waze
El mapa de clusters permite identificar zonas donde los eventos reportados por los usuarios de Waze tienden a agruparse con mayor frecuencia.
Hallazgos principales
El corredor vial central (Autopista Norte y su paralela, variante Bogotá–Cajicá) concentra la mayor cantidad de reportes.
Existen clusters de muy alta densidad cerca de:
Sector Canelón – Cajicá Centro
Intersección con la vía Hato Grande
Entrada a Calahorra
La presencia de clusters grandes indica zonas donde los usuarios encuentran condiciones recurrentes de tráfico desfavorable, lo cual podría estar asociado a:
Congestión habitual
Obras o mantenimientos temporales
Cruces críticos o intersecciones con alto flujo vehicular
Interpretación técnica
Los clusters sugieren patrones de persistencia: la aparición de múltiples eventos en exactamente los mismos puntos implica que el problema no es aislado, sino que se repite a través del tiempo, lo cual hace a estos sectores candidatos para intervención de movilidad o priorización en operativos de control.
El análisis temporal permite identificar cómo se distribuyen los eventos de Waze a lo largo del día, detectando franjas horarias con mayor actividad, posibles horas pico y momentos de riesgo elevado. Comprender estas variaciones es fundamental para la gestión del tráfico, ya que permite anticipar comportamientos recurrentes y optimizar las decisiones operativas.
A continuación, se extraen las horas de los eventos reportados y se genera la distribución temporal mediante tablas y gráficos descriptivos.
# ================================
# 7.3 ANÁLISIS TEMPORAL DE EVENTOS
# ================================
library(lubridate)
library(dplyr)
library(ggplot2)
library(kableExtra)
# Convertir la fecha y hora a formato POSIX
Waze_clean$fecha_hora <- ymd_hms(Waze_clean$creation_Date)
# Extraer hora del día
Waze_clean$hora <- hour(Waze_clean$fecha_hora)
# Tabla de frecuencia por hora
tabla_horas <- Waze_clean %>%
count(hora) %>%
arrange(hora)
tabla_horas %>%
kable("html", caption = "Frecuencia de eventos por hora del día") %>%
kable_styling(full_width = FALSE, bootstrap_options = c("striped", "hover"))
| hora | n |
|---|---|
| 0 | 226 |
| 1 | 132 |
| 2 | 126 |
| 3 | 73 |
| 4 | 60 |
| 5 | 6 |
| 6 | 37 |
| 7 | 15 |
| 10 | 52 |
| 11 | 197 |
| 12 | 343 |
| 13 | 270 |
| 14 | 251 |
| 15 | 203 |
| 16 | 267 |
| 17 | 501 |
| 18 | 341 |
| 19 | 391 |
| 20 | 439 |
| 21 | 316 |
| 22 | 376 |
| 23 | 374 |
# Gráfico de distribución temporal
ggplot(Waze_clean, aes(x = hora)) +
geom_histogram(binwidth = 1, fill = "#0099f9", color = "white") +
labs(
title = "Distribución Horaria de los Eventos Reportados",
x = "Hora del Día",
y = "Número de Eventos"
) +
theme_minimal(base_size = 13)
Conclusión General
La gráfica evidencia que los eventos de movilidad tienen fuertes componentes temporales, concentrándose especialmente en:
Hora pico de la tarde (16–18 h) → Máxima congestión y riesgo.
Mediodía (11–14 h) → Alta actividad operacional.
Madrugada (2–5 h) → Actividad casi nula.
Esto orienta el análisis espacial posterior a identificar:
📍 Dónde ocurren los eventos cuando la ciudad está más activa, y 📍 Qué zonas concentran riesgos en las horas de mayor movilidad.
Antes de generar los mapas de análisis espacial por tipo de evento, es necesario verificar qué categorías de incidentes existen en el dataset original y traducirlas al español.
Este paso es fundamental para:
library(readxl)
library(dplyr)
# 2. Convertir la fecha
Waze_clean$fecha <- as.Date(Waze_clean$creation_Date)
# 3. Ver los tipos de eventos originales
tipos_originales <- unique(Waze_clean$type)
tipos_originales
## [1] "HAZARD" "JAM" "ACCIDENT" "ROAD_CLOSED"
Una vez identificadas las cuatro categorías principales reportadas por Waze —HAZARD, JAM, ACCIDENT y ROAD_CLOSED— se procede a realizar su traducción al español con el fin de facilitar la interpretación en los análisis y visualizaciones posteriores.
Las traducciones aplicadas fueron:
# Renombrar tipos de evento al español
Waze_clean <- Waze_clean %>%
mutate(
tipo_evento = recode(
type,
"HAZARD" = "PELIGRO",
"JAM" = "CONGESTIÓN",
"ACCIDENT" = "ACCIDENTE",
"ROAD_CLOSED" = "VÍA CERRADA"
)
)
# Verificar resultado
unique(Waze_clean$tipo_evento)
## [1] "PELIGRO" "CONGESTIÓN" "ACCIDENTE" "VÍA CERRADA"
table(Trama_Waze$tipo_evento)
## < table of extent 0 >
Tras la estandarización y traducción de las categorías de evento, se realiza un análisis espacial diferenciado para cada tipo: Accidente, Peligro, Congestión y Vía cerrada.
Este análisis permite identificar patrones particulares en la ubicación de cada tipo de reporte y facilita la comparación entre ellos, evidenciando zonas donde ciertos eventos son más recurrentes.
A continuación, se presentan mapas interactivos filtrados por categoría, permitiendo explorar de manera independiente el comportamiento espacial de cada tipo de incidente reportado en Waze.
library(leaflet)
library(dplyr)
# Crear paleta automática según categorías presentes
pal <- colorFactor(
palette = "Set1",
domain = Waze_clean$tipo_evento
)
mapa_por_tipo <- leaflet(Waze_clean) %>%
addTiles() %>%
addCircleMarkers(
lng = ~lon,
lat = ~lat,
color = ~pal(tipo_evento),
radius = 4,
stroke = FALSE,
fillOpacity = 0.7,
popup = ~paste0(
"<b>Tipo:</b> ", tipo_evento, "<br>",
"<b>Subtipo:</b> ", subtype, "<br>",
"<b>Fecha:</b> ", creation_Date
)
) %>%
addLegend(
position = "bottomright",
pal = pal,
values = Waze_clean$tipo_evento,
title = "Tipo de evento"
) %>%
addControl(
"<h4 style='color:#333;'>Mapa temático por tipo de evento</h4>",
position = "topright"
)
mapa_por_tipo
Conclusión
La congestión es el fenómeno dominante, tanto en cantidad como en extensión espacial.
Los cierres de vía aparecen como el segundo grupo más visible, concentrados en zonas específicas.
Los peligros se distribuyen de manera dispersa, sin formar núcleos de alta concentración.
Los accidentes son el tipo de evento menos frecuente, pero aparecen en puntos estratégicos donde podría existir alto riesgo vial.
En conjunto, el patrón visual evidencia que las principales afectaciones de movilidad se dan por:
Congestión en vías principales,
Cierres que generan impacto local, y
Incidentes puntuales (peligros o accidentes) que afectan sectores específicos.
En esta sección se presenta un análisis espacial integral de todos
los eventos reportados por Waze.
El objetivo es identificar patrones geográficos comunes entre los
diferentes tipos de incidentes:
Se utilizan técnicas de análisis espacial para:
Este análisis permite identificar áreas que requieren mayor priorización en movilidad, señalización e infraestructura, proporcionando información clave para la toma de decisiones orientadas a la gestión urbana y la seguridad vial.
# ================================
# 📦 LIBRERÍAS
# ================================
library(dplyr)
library(leaflet)
library(leaflet.extras)
library(spatstat.geom)
library(terra)
library(spatstat)
# ================================
# 📌 ÁREA DE ESTUDIO AUTOMÁTICA
# ================================
xmin <- min(Waze_clean$lon)
xmax <- max(Waze_clean$lon)
ymin <- min(Waze_clean$lat)
ymax <- max(Waze_clean$lat)
zona <- spatstat.geom::owin(
xrange = c(xmin, xmax),
yrange = c(ymin, ymax)
)
# Crear patrón espacial completo
patron_waze <- spatstat.geom::ppp(
x = Waze_clean$lon,
y = Waze_clean$lat,
window = zona
)
# ================================
# 🔥 2. Mapa de Densidad Global (Kernel Density)
# ================================
# Convertir datos a objeto spatstat
patron <- patron_waze
# Calcular densidad
densidad <- density.ppp(patron, sigma = 0.005)
# Convertir a raster para leaflet
raster_densidad <- rast(densidad)
df_densidad <- as.data.frame(raster_densidad, xy = TRUE)
colnames(df_densidad) <- c("lon", "lat", "intensity")
# Normalizar intensidades
df_densidad$intensity <- (df_densidad$intensity - min(df_densidad$intensity)) /
(max(df_densidad$intensity) - min(df_densidad$intensity))
# Crear mapa
mapa_densidad <- leaflet(df_densidad) %>%
addProviderTiles("OpenStreetMap") %>%
addHeatmap(
lng = ~lon,
lat = ~lat,
intensity = ~intensity,
blur = 30,
radius = 18,
max = 1
) %>%
addLegend(
position = "bottomright",
title = "Mapa de Densidad",
colors = c("blue", "green", "yellow", "red"),
labels = c("Baja", "Moderada", "Alta", "Muy Alta")
) %>%
addControl("<h3>Mapa Global de Densidad</h3>", position = "topleft")
mapa_densidad
Para realizar el análisis espacial formal, es necesario convertir los
datos de eventos de Waze en un objeto de tipo punto (ppp)
del paquete spatstat. Este objeto requiere definir:
Dado que no se cuenta con un polígono oficial, se emplea una ventana automática, construida a partir del rectángulo mínimo que contiene todos los eventos (bounding box). Esta aproximación es válida para análisis exploratorios y permite evaluar:
A continuación se muestra la construcción del objeto ppp
y la visualización inicial del patrón puntual.
library(MASS)
library(raster)
library(leaflet)
library(RColorBrewer)
# ================================
# 1. Extraer coordenadas limpias
# ================================
coords <- Waze_clean[, c("lon", "lat")]
# ================================
# 2. Crear matriz KDE con kde2d
# ================================
kde <- MASS::kde2d(
x = coords$lon,
y = coords$lat,
n = 300, # resolución de la grilla
h = c(0.01, 0.01) # suavidad del kernel (ajustable)
)
# ================================
# 3. Convertir a objeto raster
# ================================
r <- raster(list(
x = kde$x,
y = kde$y,
z = kde$z
))
# Normalizar valores para visualización
r_norm <- r / cellStats(r, "max")
# Paleta de colores bonita
pal <- colorNumeric(
palette = rev(brewer.pal(11, "Spectral")),
domain = values(r_norm),
na.color = "transparent"
)
# ================================
# 4. Mapa final en Leaflet
# ================================
leaflet() %>%
addTiles() %>%
addRasterImage(r_norm, colors = pal, opacity = 0.7) %>%
addLegend(
pal = pal,
values = values(r_norm),
title = "Densidad Kernel",
position = "bottomright"
) %>%
addControl("<h3>Mapa de Densidad KDE de Eventos Waze</h3>",
position = "topleft")
El análisis de densidad KDE evidencia que los eventos reportados en Waze se concentran de manera significativa a lo largo del corredor vial principal de Cajicá, con dos puntos críticos —sur y norte del municipio— donde se registran las mayores intensidades. Estas zonas representan áreas prioritarias para estrategias de movilidad, gestión de tráfico y prevención de incidentes. Las áreas periféricas muestran baja densidad, lo que confirma que los eventos están altamente asociados al flujo vehicular y no a la ocupación territorial.
El Test de Cuadrantes es una técnica clásica del análisis espacial que permite evaluar si los eventos geográficos se distribuyen de manera:
Aleatoria
Agrupada (clustered)
Dispersa (regular)
Para esto, el área de estudio se divide en una cuadrícula —por ejemplo 3×3— y se cuentan los eventos dentro de cada celda. Estos valores se comparan contra lo que se esperaría si los eventos estuvieran distribuidos aleatoriamente en el espacio. A partir de esta comparación, se calcula un estadístico chi-cuadrado que permite determinar la presencia de agrupamientos significativos.
En este análisis, aplicamos el test sobre los eventos reportados por la plataforma Waze, con el objetivo de identificar si existen zonas con acumulación anómala de reportes que puedan representar puntos críticos para la movilidad. Esta información es clave para apoyar la toma de decisiones en:
Gestión del tráfico
Priorización de intervenciones viales
Identificación de puntos críticos de congestión o riesgo
A continuación se presenta el código que genera:
La matriz de conteos por cuadrante
El valor del estadístico chi-cuadrado
Un mapa tipo “heat-grid” que muestra visualmente en qué cuadrantes se concentran los eventos
# ============================================
# 🔶 TEST DE CUADRANTES SIN SPATSTAT
# ============================================
library(dplyr)
library(ggplot2)
library(tidyr)
# 1. Extraer solo las coordenadas válidas
coords <- Waze_clean %>%
dplyr::select(lon, lat) %>%
filter(!is.na(lon), !is.na(lat))
# 2. Definir los límites del área de estudio
x_min <- min(coords$lon)
x_max <- max(coords$lon)
y_min <- min(coords$lat)
y_max <- max(coords$lat)
# 3. Número de cuadrantes por eje (3x3 = 9 cuadrantes)
n_q <- 3
# 4. Crear cortes para dividir el espacio
x_breaks <- seq(x_min, x_max, length.out = n_q + 1)
y_breaks <- seq(y_min, y_max, length.out = n_q + 1)
# 5. Asignar cada punto a un cuadrante
coords$quad_x <- cut(coords$lon, breaks = x_breaks, include.lowest = TRUE, labels = FALSE)
coords$quad_y <- cut(coords$lat, breaks = y_breaks, include.lowest = TRUE, labels = FALSE)
# 6. Calcular la tabla de frecuencias observadas
quad_table <- coords %>%
count(quad_x, quad_y) %>%
tidyr::complete(quad_x = 1:n_q, quad_y = 1:n_q, fill = list(n = 0)) %>%
arrange(quad_y, quad_x)
quad_table
## # A tibble: 9 × 3
## quad_x quad_y n
## <int> <int> <int>
## 1 1 1 1874
## 2 2 1 28
## 3 3 1 0
## 4 1 2 879
## 5 2 2 896
## 6 3 2 307
## 7 1 3 38
## 8 2 3 898
## 9 3 3 76
# 7. Valor esperado si fuera aleatorio
N <- nrow(coords)
expected <- N / (n_q * n_q)
# 8. Calcular estadístico Chi-cuadrado manual
quad_table$chi <- (quad_table$n - expected)^2 / expected
chi_square_value <- sum(quad_table$chi)
chi_square_value
## [1] 5805.42313851081
# 9. Gráfico tipo matriz de cuadrantes
ggplot(quad_table, aes(x = quad_x, y = quad_y, fill = n)) +
geom_tile(color = "white") +
scale_fill_gradient(low = "white", high = "red") +
scale_y_reverse() +
labs(
title = "Test de Cuadrantes — Distribución Espacial de Eventos Waze",
x = "Cuadrante X",
y = "Cuadrante Y",
fill = "N° eventos"
) +
theme_minimal(base_size = 14)
Conclusiones del Test de Cuadrantes
El análisis mediante el Test de Cuadrantes revela que la distribución espacial de los eventos reportados en Waze no es uniforme dentro del área de estudio. Esto se evidencia en:
En conjunto, los resultados muestran que los eventos tienden a agruparse en sectores específicos —principalmente el eje vial central del municipio— lo cual coincide con las zonas de mayor movilidad, intersecciones principales y tramos urbanos críticos. Este patrón respalda la hipótesis de aglomeración espacial y constituye una base sólida para posteriores análisis de densidad y funciones de interacción espacial.
Con el fin de evaluar si los distintos tipos de eventos reportados por Waze —accidentes, congestión, peligro y vía cerrada— presentan patrones espaciales significativos, se aplicaron dos métodos clásicos del análisis de patrones de puntos: el Test de Cuadrantes y la Función K de Ripley.
El Test de Cuadrantes permite comparar la distribución observada con un patrón totalmente aleatorio (CSR, Complete Spatial Randomness) mediante una prueba Chi-cuadrado. Este test detecta si los puntos tienden a agruparse o dispersarse dentro de un área definida.
Por otra parte, la Función K de Ripley evalúa cómo varía la intensidad de puntos a diferentes escalas espaciales, permitiendo identificar clustering a múltiples distancias. La comparación entre la curva empírica \(\hat{K}(r)\) y la curva teórica de un proceso Poisson homogéneo indica si existe agrupación, aleatoriedad o dispersión espacial.
A continuación, se presentan los resultados obtenidos para cada categoría de evento.
#crear subconjuntos
accidente <- subset(Waze_clean, tipo_evento == "ACCIDENTE")
congestion <- subset(Waze_clean, tipo_evento == "CONGESTIÓN")
peligro <- subset(Waze_clean, tipo_evento == "PELIGRO")
viacerrada <- subset(Waze_clean, tipo_evento == "VÍA CERRADA")
#definir ventana de estudio
library(spatstat)
# Obtener límites de la ventana basados en TODO el dataset limpio
xmin <- min(Waze_clean$lon)
xmax <- max(Waze_clean$lon)
ymin <- min(Waze_clean$lat)
ymax <- max(Waze_clean$lat)
zona <- owin(xrange = c(xmin, xmax), yrange = c(ymin, ymax))
# crear patron de puntos
crear_ppp <- function(df){
ppp(x = df$lon,
y = df$lat,
window = zona)
}
#crear patrones ppp
ppp_accidente <- crear_ppp(accidente)
ppp_congestion <- crear_ppp(congestion)
ppp_peligro <- crear_ppp(peligro)
ppp_viacerrada <- crear_ppp(viacerrada)
#Funcion test cuadrantes
test_cuadrantes <- function(patron_ppp, nombre){
q <- quadratcount(patron_ppp, nx = 3, ny = 3)
plot(q, main = paste("Test de Cuadrantes -", nombre))
points(patron_ppp, col = "red", pch = 16)
esperado <- mean(q)
chi <- quadrat.test(q)
cat("\n============================================\n")
cat("RESULTADOS —", nombre, "\n")
cat("Promedio esperado por cuadrante:", round(esperado,2), "\n")
print(chi)
cat("============================================\n")
}
# funcion k
test_k <- function(patron_ppp, nombre){
k <- Kest(patron_ppp, correction = "iso")
plot(k, main = paste("Función K de Ripley —", nombre))
cat("\n--------------------------------------------\n")
cat("Interpretación rápida —", nombre, "\n")
cat("Si la curva K(r) está por encima de Poisson → agrupación\n")
cat("Si está por debajo → dispersión\n")
cat("--------------------------------------------\n")
}
#ejecutar por tipo de evento
test_cuadrantes(ppp_accidente, "ACCIDENTES")
##
## ============================================
## RESULTADOS — ACCIDENTES
## Promedio esperado por cuadrante: 13.56
##
## Chi-squared test of CSR using quadrat counts
##
## data:
## X2 = 127.0491803279, df = 8, p-value < 0.0000000000000002220446049
## alternative hypothesis: two.sided
##
## Quadrats: 3 by 3 grid of tiles
## ============================================
test_cuadrantes(ppp_congestion, "CONGESTIÓN")
##
## ============================================
## RESULTADOS — CONGESTIÓN
## Promedio esperado por cuadrante: 350.11
##
## Chi-squared test of CSR using quadrat counts
##
## data:
## X2 = 3044.973024437, df = 8, p-value < 0.0000000000000002220446049
## alternative hypothesis: two.sided
##
## Quadrats: 3 by 3 grid of tiles
## ============================================
test_cuadrantes(ppp_peligro, "PELIGRO")
##
## ============================================
## RESULTADOS — PELIGRO
## Promedio esperado por cuadrante: 78
##
## Chi-squared test of CSR using quadrat counts
##
## data:
## X2 = 474.4358974359, df = 8, p-value < 0.0000000000000002220446049
## alternative hypothesis: two.sided
##
## Quadrats: 3 by 3 grid of tiles
## ============================================
test_cuadrantes(ppp_viacerrada, "VÍA CERRADA")
##
## ============================================
## RESULTADOS — VÍA CERRADA
## Promedio esperado por cuadrante: 113.44
##
## Chi-squared test of CSR using quadrat counts
##
## data:
## X2 = 6772.49755142, df = 8, p-value < 0.0000000000000002220446049
## alternative hypothesis: two.sided
##
## Quadrats: 3 by 3 grid of tiles
## ============================================
test_k(ppp_accidente, "ACCIDENTES")
##
## --------------------------------------------
## Interpretación rápida — ACCIDENTES
## Si la curva K(r) está por encima de Poisson → agrupación
## Si está por debajo → dispersión
## --------------------------------------------
test_k(ppp_congestion, "CONGESTIÓN")
##
## --------------------------------------------
## Interpretación rápida — CONGESTIÓN
## Si la curva K(r) está por encima de Poisson → agrupación
## Si está por debajo → dispersión
## --------------------------------------------
test_k(ppp_peligro, "PELIGRO")
##
## --------------------------------------------
## Interpretación rápida — PELIGRO
## Si la curva K(r) está por encima de Poisson → agrupación
## Si está por debajo → dispersión
## --------------------------------------------
test_k(ppp_viacerrada, "VÍA CERRADA")
##
## --------------------------------------------
## Interpretación rápida — VÍA CERRADA
## Si la curva K(r) está por encima de Poisson → agrupación
## Si está por debajo → dispersión
## --------------------------------------------
Conclusiones del Análisis Espacial
Los resultados del Test de Cuadrantes muestran, para las cuatro categorías analizadas, valores extremadamente altos de Chi-cuadrado y p-values virtualmente iguales a cero. Esto indica que en todos los casos la hipótesis de aleatoriedad espacial (CSR) debe ser rechazada. Es decir, ninguno de los eventos presenta una distribución espacial uniforme o al azar.
La Función K de Ripley refuerza esta conclusión: en todos los eventos, la curva empírica \(\hat{K}(r)\) se ubica claramente por encima de la curva teórica de Poisson. Esto evidencia un patrón consistente de agrupación espacial, aunque la intensidad del clustering varía entre categorías.
En términos comparativos:
En conjunto, los resultados confirman que los eventos de movilidad en Bogotá no ocurren de forma aleatoria, sino que siguen patrones espaciales altamente estructurados, lo cual es fundamental para la priorización de zonas críticas, planificación de intervenciones y análisis predictivo.
La depuración del dataset fue esencial: la eliminación de registros con coordenadas corruptas (especialmente latitudes iniciadas en “2” por errores de Excel) permitió obtener un conjunto de datos confiable y coherente con la realidad geográfica del país.
La distribución espacial evidencia patrones claros: la mayor concentración de eventos se presenta en corredores viales principales y zonas urbanas densamente transitadas, lo cual coincide con áreas de mayor actividad vehicular.
Las congestiones dominan el panorama: visualmente, los puntos de CONGESTIÓN son los más frecuentes y se distribuyen de forma homogénea a lo largo de la red vial, confirmando que es el tipo de evento más común reportado por los usuarios.
Los accidentes presentan agrupaciones específicas: se observan focos rojos en intersecciones y tramos críticos, lo que puede indicar puntos recurrentes de siniestralidad.
Los eventos de peligro y de vía cerrada son minoritarios: aparecen dispersos, pero cuando surgen lo hacen en puntos que afectan la movilidad local, usualmente vinculados a obstáculos, obras o condiciones inseguras.
El mapa consolidado facilita la interpretación: al integrar todos los tipos de eventos con colores diferenciados, se obtiene una visión global que permite comparar la distribución relativa de cada categoría.
En conjunto, el análisis demuestra que los eventos de movilidad reportados presentan patrones espaciales altamente estructurados y coherentes con la dinámica urbana de Bogotá. La congestión domina y se distribuye de forma continua, mientras que los accidentes, peligros y vías cerradas revelan puntos críticos específicos. El uso combinado de pruebas estadísticas y visualización geoespacial permitió una comprensión profunda del comportamiento de los eventos, aportando insumos valiosos para la toma de decisiones en movilidad, seguridad vial y planeación del territorio.