1 Marco Teórico

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.


2 Introducción

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:

  • análisis temporal,
  • distribución de eventos,
  • mapas interactivos,
  • patrones puntuales,
  • estimación de densidad (kernel),
  • pruebas espaciales como cuadrantes y función K de Ripley,

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")
Vista preliminar del dataset Waze (sin notación científica)
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

3 Preparación y Limpieza de los Datos

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:

  • Selección de variables relevantes
  • Diagnóstico de inconsistencias
  • Limpieza de registros faltantes o inválidos
  • Transformación y estandarización de variables
  • Verificación final del dataset limpio

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.

3.1 Estado inicial del dataset

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)
Resumen de variables numéricas
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)
Dimensiones del Dataset
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)
Cantidad de NA por columna
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)
Tipo de datos por columna
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)
Número de valores únicos por columna
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)
Columnas sospechosas de haber sido convertidas en fecha
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)
Resumen inicial del dataset Waze
categoria cantidad
Registros totales 5070
Variables totales 17

3.2 Diagnóstico inicial de calidad de datos

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:

  • Valores faltantes
  • Subtipos sin clasificación
  • Registros sin coordenadas
  • Fechas inválidas
  • Registros duplicados

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)
Diagnóstico inicial de calidad del dataset Waze
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.

3.3 Proceso de limpieza aplicado

A continuación se detalla el proceso implementado para depurar el dataset y garantizar su calidad:

  1. Selección de variables clave para el análisis
  2. Eliminación de registros sin coordenadas
  3. Conversión de coordenadas
  4. Conversión del campo de fecha a formato estándar
  5. Eliminación de registros cuyo subtype es NA
  6. Eliminación de duplicados
library(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()

3.4 Estado final del dataset limpio

# -----------------------------
# 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)
Resumen del dataset después de la limpieza
categoria cantidad
Registros después de limpieza 5070
Variables finales 19

3.5 Comparación antes y después

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)
Comparación del dataset antes y después de la limpieza
categoria antes despues
Número de registros 5070 5070
Subtipos NA 858 858
Registros duplicados 0 0

3.6 Vista preliminar del dataset limpio

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")
Vista preliminar del dataset limpio
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

3.7 Conclusión de la etapa de limpieza

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.

4 Depuración adicional de coordenadas inválidas

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)
Ejemplos de coordenadas inválidas detectadas (latitud inicia en 2)
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.

5 Exploración descriptiva del conjunto de datos Waze

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:

  • distribución de tipos de incidentes (type);
  • subtipos (subtype);
  • nivel de confiabilidad y veracidad del reporte (confidence, reliability);
  • fechas y horarios de creación de los reportes;
  • ubicación espacial preliminar mediante coordenadas.

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
  )
Resumen general del dataset limpio
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")
Frecuencia de tipos de incidentes
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")
Frecuencia de subtipos reportados
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

6 Exploración inicial del conjunto de datos

En esta sección realizamos un análisis exploratorio básico gráfico de las variables más relevantes del dataset Waze, incluyendo:

  • Distribución de tipos y subtipos de eventos
  • Distribución de niveles de confianza y confiabilidad
  • Inspección de fechas de creación
  • Identificación de patrones generales previos al análisis espacial

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")
Frecuencia de tipos de eventos Waze
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")
Frecuencia de subtipos de eventos Waze
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

7 Análisis espacial del conjunto de datos Waze

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.

7.1 Visualización geográfica de los eventos

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)

7.2 Identificación de patrones espaciales

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:

  • Identificar corredores viales con alta densidad de eventos.
  • Detectar focos problemáticos donde los reportes se acumulan.
  • Contrastar áreas de baja actividad con zonas críticas.
  • Facilitar la interpretación visual del comportamiento espacial del tráfico.

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.

7.3 Análisis Temporal de los Evento

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"))
Frecuencia de eventos por hora del día
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.

7.4 Análisis Espacial por Tipo de Evento

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:

  • asegurar consistencia en los nombres de los eventos,
  • evitar errores en los filtros posteriores,
  • permitir que los gráficos y mapas utilicen nombres claros y comprensibles.
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:

  • HAZARD → PELIGRO
  • JAM → CONGESTIÓN
  • ACCIDENT → ACCIDENTE
  • ROAD_CLOSED → VÍA CERRADA
# 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.

7.5 Análisis Espacial Consolidado de Eventos Waze

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:

  • ACCIDENTE
  • CONGESTIÓN
  • PELIGRO
  • VÍA CERRADA

Se utilizan técnicas de análisis espacial para:

  1. Visualizar el comportamiento conjunto de todos los eventos.
  2. Generar mapas de densidad para detectar zonas críticas.
  3. Explorar patrones espaciales mediante herramientas avanzadas como kernel density.

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

7.6 Análisis de Patrones Puntuales

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:

  • Un conjunto de coordenadas (x = longitud, y = latitud)
  • Una ventana de análisis (window), que representa la zona de estudio

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:

  • La distribución espacial de los eventos
  • La presencia de agrupamientos o dispersión
  • Regiones con mayor actividad

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.

7.7 Test de Cuadrantes para la Detección de Patrones Espaciales

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:

  • Una fuerte concentración de eventos en el cuadrante (1,1), que acumula 1.874 reportes, representando la zona de mayor actividad vial.
  • Cuadrantes como (3,1) y (1,3) con valores cercanos a cero, indicando áreas con muy baja incidencia.
  • El estadístico obtenido (≈ 5805) confirma una alta desviación respecto a una distribución homogénea, lo cual sugiere presencia de patrones espaciales significativos.

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.

7.8 Análisis Exploratorio de la Distribución Espacial de Eventos

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:

  • Congestión muestra el comportamiento más fuertemente agrupado, coherente con la concentración de tráfico en corredores viales principales.
  • Peligro y Accidentes presentan agrupamientos moderados pero evidentes, probablemente asociados a intersecciones críticas y zonas con alta actividad vehicular.
  • Vía Cerrada exhibe una agrupación extrema, reflejando que estos eventos tienden a concentrarse en puntos específicos de obras, bloqueos o cierres recurrentes.

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.

8 Conclusiones Generales

  • 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.