# Instalación y cargue de librerías
library(readxl)
library(dplyr) 
library(lubridate)
library(leaflet) 
library(sf)
library(mapview)
library(spatstat)
library(terra)
library(leaflet.extras)    
library(ggplot2)          
library(leafsync) 

1 Introducción

El presente ejercicio tiene como objetivo analizar la frecuencia y localización de eventos que afectan la movilidad en unas vías específicas de Cundinamarca, a partir de los registros de una base de datos construida a partir de información relativa a la plataforma Waze.

2 Preparación de datos

Se carga y presenta la estructura de los datos a utilizar en el análisis, la cual contiene un total de 19 atributos que caracterizan a 5070 entradas, a partir de las siguientes variables:

  • creation_Date: Fecha y hora en que se creó el reporte del evento.

  • type: Tipo de evento reportado, que puede ser uno de los siguientes:

PELIGRO: Reportes de peligros en la vía, como objetos en la carretera, vehículos detenidos, etc.

CONGESTIÓN: Reportes de congestión o trancones en el tráfico.

ACCIDENTE: Reportes de accidentes de tráfico.

VÍA CERRADA: Reportes de cierres de vías.

latitude: Latitud geográfica donde ocurrió el evento.

longitude: Longitud geográfica donde ocurrió el evento.

información_adicional: Información adicional proporcionada por el usuario sobre el evento (si está disponible).

otros_campos: Otras variables relevantes incluidas en el conjunto de datos, como el identificador del evento, estado del evento, etc.

# Cargue de base de datos
trama_waze <- read_excel("C:/Users/Juan/Desktop/Maestría/Casos - Información Geográfica/Trama_Waze.xlsx")

# Convertir la columna de fechas a formato adecuado
trama_waze$fecha = as.Date(trama_waze$creation_Date, format ="%Y-%m-%d %H:%M")

# Cambiar los nombres de los tipos de eventos a español
trama_waze$tipo_evento <- recode(trama_waze$type,
                                 "ACCIDENT" = "ACCIDENTE",
                                 "HAZARD" = "PELIGRO",
                                 "JAM" = "CONGESTIÓN",
                                 "ROAD_CLOSED" = "VÍA CERRADA")

head(trama_waze)

#Análisis del tipo de evento

Siendo el tipo de evento la variable de interés del análisis, se presenta un análisis descriptivo del comportamiento de la variable.

table(trama_waze$tipo_evento)
## 
##   ACCIDENTE  CONGESTIÓN     PELIGRO VÍA CERRADA 
##         125        3205         719        1021

Como se puede ver, la congestión vehicular representa el incidente con mayor frecuencia en la zona de estudio.

frecuencia_eventos <- trama_waze %>%
  group_by(tipo_evento) %>%                 # Agrupar por tipo de evento
  summarise(Frecuencia = n()) %>%     # Contar la frecuencia de cada tipo
  arrange(desc(Frecuencia))           # Ordenar por frecuencia descendente

# Crear un gráfico de barras con ggplot2 usando los datos de Trama_Waze
ggplot(frecuencia_eventos, aes(x = tipo_evento, y = Frecuencia, fill = tipo_evento)) +
  geom_bar(stat = "identity") +
  theme_minimal() +
  labs(title = "Distribución de Tipos de Eventos en Trama Waze", 
       x = "Tipo de Evento", y = "Frecuencia") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) +  # Rotar etiquetas para mejor visualización
  scale_fill_brewer(palette = "Set2")  # Utilizar una paleta de colores predefinida

3 Análisis eventos de PELIGRO.

Dado que hay un interés particular por entender la ocurrencia de peligros específicos el día 26, se hace el filtrado de la base de datos a analizar.

# Identificar eventos PELIGRO del día 26
# Convertir la fecha y extraer la hora y el día
fecha_hora = ymd_hms(trama_waze$creation_Date)
hora = hour(fecha_hora)
dia = day(fecha_hora)

# Agregar la columna de hora a los datos
trama_waze$hora = hora

pos <- which(trama_waze$tipo_evento == "PELIGRO" & dia == 26)
peligro26 <- trama_waze[pos,]

Posteriormente, se realiza la visualización de los eventos en el día priorizado.

# Ajustar las coordenadas de latitud y longitud
peligro26$lat <- peligro26$location_y / 10^(nchar(peligro26$location_y) - 1)
peligro26$long <- peligro26$location_x / 10^(nchar(peligro26$location_x) - 3)

# Filtrar eventos dentro del rango geográfico adecuado
peligro26 <- peligro26[peligro26$lat > 4 & peligro26$lat < 5,]

# Crear un mapa interactivo con leaflet
m26_peligro <- leaflet() %>%
  addTiles() %>%
  addCircleMarkers(lng = peligro26$long, lat = peligro26$lat,
                   clusterOptions = markerClusterOptions(),
                   label = peligro26$hora) %>%
  addControl(html = "<h3>Mapa de Riesgos</h3>", position = "topleft")

# Mostrar el mapa
m26_peligro

Así mismo, se realiza un análisis espacial que permita generar mapas de calor para denotar las zonas más afectadas por este tipo de evento.

# Filtrar datos relevantes de peligro26
peligro26 <- peligro26 %>%
  filter(lat > 4 & lat < 5, long > -75 & long < -73)  # Ajustar las coordenadas de interés

# Crear un mapa interactivo con leaflet y addHeatmap
leaflet(peligro26) %>%
  addProviderTiles("OpenStreetMap") %>%  # Añadir la capa base del mapa
  addHeatmap(
    lng = ~long, lat = ~lat,               # Especificar las columnas de longitud y latitud
    intensity = ~hora,                     # Intensidad opcional basada en la hora (o cualquier otra variable)
    blur = 20,                             # Nivel de desenfoque del mapa de calor
    max = 0.08,                            # Ajustar el valor máximo para la intensidad
    radius = 15                            # Radio de cada punto en el mapa de calor
  ) %>%
  addLegend("bottomright",                 # Añadir leyenda
            title = "Mapa de Calor de Riesgos",
            colors = c("blue", "green", "yellow", "red"),
            labels = c("Bajo", "Moderado", "Alto", "Muy Alto"))

En este sentido, siendo la variable hora la que define la intensidad del mapa de calor, se entiende que las zonas más rojas son aquellas donde los eventos tienden a ocurrir a altas horas de la noche.

4 Análisis eventos de VIA CERRADA.

En esta sección, se identifican las zonas más afectadas por cierre de vías en la zona de interés.

# Filtrar eventos VÍA CERRADA del día 26
pos2 <- which(trama_waze$tipo_evento == "VÍA CERRADA" & dia == 26)
via_cerrada_26 <- trama_waze[pos2,]

# Ajustar las coordenadas de latitud y longitud
via_cerrada_26$lat <- via_cerrada_26$location_y / 10^(nchar(via_cerrada_26$location_y) - 1)
via_cerrada_26$long <- via_cerrada_26$location_x / 10^(nchar(via_cerrada_26$location_x) - 3)

# Filtrar eventos dentro del rango geográfico adecuado
via_cerrada_26 <- via_cerrada_26[via_cerrada_26$lat > 4 & via_cerrada_26$lat < 5,]

# Crear el mapa interactivo
m26_via_cerrada = leaflet(via_cerrada_26) %>%
  addTiles() %>%
  addCircleMarkers(lng = ~long, lat = ~lat,
                   clusterOptions = markerClusterOptions(),
                   label = ~hora) %>%
  addControl(html = "<h3>Mapa de Cierre de Vías<h3>", position = "topleft")

# Mostrar el mapa interactivo
m26_via_cerrada

Además se hace una análisis estadístico espacial de la ocurrencia de este evento.

# Definir la zona de interés
zona <- owin(xrange = c(-74.04331, -73.9929), yrange = c(4.885736, 4.948562))

# Crear un patrón de puntos espaciales a partir de los eventos VÍA CERRADA
patron_via_cerrada <- ppp(x = via_cerrada_26$long, y = via_cerrada_26$lat, window = zona)

# Graficar el test de cuadrantes
plot(quadratcount(patron_via_cerrada), main = "Patrón de Puntos y Test de Cuadrantes")

# Superponer los puntos sobre los cuadrantes
points(patron_via_cerrada, col = "red")

# Gráfico independiente: Función K-Estimación
plot(Kest(patron_via_cerrada), main = "Función K-Estimación")

Además, se realiza un mapa de calor que denote la intensidad con la que tiene lugar este evento en la zona de interés.

# Calcular la densidad espacial
im1 <- density(patron_via_cerrada, sigma = 0.01)  # Ajusta sigma según sea necesario

# Convertir la densidad a un objeto raster usando terra
mapa_via_cerrada <- rast(im1)

# Convertir el raster a data.frame para leaflet
df_via_cerrada <- as.data.frame(mapa_via_cerrada, xy = TRUE)
colnames(df_via_cerrada) <- c("long", "lat", "intensity")

# Normalizar los valores de intensidad entre 0 y 1
df_via_cerrada$intensity <- (df_via_cerrada$intensity - min(df_via_cerrada$intensity)) / 
                            (max(df_via_cerrada$intensity) - min(df_via_cerrada$intensity))

# Crear un mapa interactivo usando leaflet
leaflet(df_via_cerrada) %>%
  addProviderTiles("OpenStreetMap") %>%  # Añadir la capa base
  addHeatmap(
    lng = ~long, lat = ~lat,              # Coordenadas de longitud y latitud
    intensity = ~intensity,               # Intensidad normalizada
    blur = 20,                            # Nivel de desenfoque
    max = 1,                              # Valor máximo de la intensidad normalizada
    radius = 15                           # Radio para reflejar la densidad
  ) %>%
  addLegend("bottomright",                # Añadir la leyenda
            title = "Mapa de Calor de Cierres de Vías",
            colors = c("blue", "green", "yellow", "red"),
            labels = c("Bajo", "Moderado", "Alto", "Muy Alto"))

5 Análisis de eventos de ACCIDENTES

A continuación, se replica análisis con la ocurrencia de accidentes.

# Filtrar eventos de accidentes del día 26
pos3 <- which(trama_waze$tipo_evento == "ACCIDENTE" & dia == 26)
accidente_26 <- trama_waze[pos3,]

# Ajustar las coordenadas de latitud y longitud
accidente_26$lat <- accidente_26$location_y / 10^(nchar(accidente_26$location_y) - 1)
accidente_26$long <- accidente_26$location_x / 10^(nchar(accidente_26$location_x) - 3)

# Filtrar eventos dentro del rango geográfico adecuado
accidente_26 <- accidente_26[accidente_26$lat > 4 & accidente_26$lat < 5,]
# Crear el mapa interactivo
m26_accidente <- leaflet(accidente_26) %>%
  addTiles() %>%
  addCircleMarkers(lng = ~long, lat = ~lat,
                   clusterOptions = markerClusterOptions(),
                   label = ~hora) %>%
  addControl(html = "<h3>Mapa de Accidentes</h3>", position = "topleft")

# Mostrar el mapa interactivo
m26_accidente

Además, para identificar si hay patrones espaciales en la ocurrencia del fenómeno se realiza el siguiente ejercicio:

# Crear un patrón de puntos espaciales a partir de los eventos ACCIDENTE
patron_accidente = ppp(x = accidente_26$long, y = accidente_26$lat, window = zona)

# Gráfico combinado: Test de Cuadrantes y Patrón de Puntos
par(mfrow = c(1, 1))  # Asegurarse de que solo haya una gráfica

# Graficar el test de cuadrantes
plot(quadratcount(patron_accidente), main = "Patrón de Puntos y Test de Cuadrantes")

# Superponer los puntos sobre los cuadrantes
points(patron_accidente, col = "red" )

# Calcular la función K-estimación
plot(Kest(patron_accidente))

En cuanto al análisis de la densidad de accidentes se identifica:

# Calcular la densidad espacial
im2 <- density(patron_accidente)

# Convertir la densidad a un objeto raster usando terra
mapa_accidente <- rast(im2)

# Convertir el raster a data.frame para leaflet
df_accidente <- as.data.frame(mapa_accidente, xy = TRUE)
colnames(df_accidente) <- c("long", "lat", "intensity")

# Normalizar los valores de intensidad entre 0 y 1
df_accidente$intensity <- (df_accidente$intensity - min(df_accidente$intensity)) / 
                          (max(df_accidente$intensity) - min(df_accidente$intensity))

# Crear un mapa interactivo usando leaflet
leaflet(df_accidente) %>%
  addProviderTiles("OpenStreetMap") %>%  # Añadir la capa base
  addHeatmap(
    lng = ~long, lat = ~lat,              # Coordenadas de longitud y latitud
    intensity = ~intensity,               # Intensidad normalizada
    blur = 15,                            # Nivel de desenfoque
    max = 0.5,                              # Valor máximo de la intensidad normalizada
    radius = 10                           # Ajustar el radio de los puntos
  ) %>%
  addLegend("bottomright",                # Añadir la leyenda para interpretar el mapa de calor
            title = "Mapa de Calor de Accidentes",
            colors = c("blue", "green", "yellow", "red"),
            labels = c("Bajo", "Moderado", "Alto", "Muy Alto"))

6 Análisis de CONGESTIÓN

# Filtrar eventos de congestión del día 26
pos4 <- which(trama_waze$tipo_evento == "CONGESTIÓN" & dia == 26)
congestion_26 <- trama_waze[pos4,]

# Ajustar las coordenadas de latitud y longitud
congestion_26$lat <- congestion_26$location_y / 10^(nchar(congestion_26$location_y) - 1)
congestion_26$long <- congestion_26$location_x / 10^(nchar(congestion_26$location_x) - 3)

# Filtrar eventos dentro del rango geográfico adecuado
congestion_26 <- congestion_26[congestion_26$lat > 4 & congestion_26$lat < 5,]

# Crear el mapa interactivo
m26_congestion <- leaflet(congestion_26) %>%
  addTiles() %>%
  addCircleMarkers(lng = ~long, lat = ~lat,
                   clusterOptions = markerClusterOptions(),
                   label = ~hora) %>%
  addControl(html = "<h3>Mapa de Congestión</h3>", position = "topleft")

# Mostrar el mapa interactivo
m26_congestion
# Crear un patrón de puntos espaciales a partir de los eventos CONGESTIÓN
patron_congestion <- ppp(x = congestion_26$long, y = congestion_26$lat, window = zona)

# Visualizar el patrón de puntos
par(mfrow = c(1, 1))  # Asegurarse de que solo haya una gráfica

# Graficar el test de cuadrantes
plot(quadratcount(patron_congestion), main = "Patrón de Puntos y Test de Cuadrantes")

# Superponer los puntos sobre los cuadrantes
points(patron_congestion, col = "red")

# Calcular la función K-estimación
plot(Kest(patron_congestion))

# Calcular la densidad espacial del patrón de puntos
im3 <- density(patron_congestion)

# Convertir la densidad en un raster utilizando terra
mapa_congestion <- rast(im3)

# Convertir el objeto raster a un data.frame para usarlo en leaflet
df_congestion <- as.data.frame(mapa_congestion, xy = TRUE)
colnames(df_congestion) <- c("long", "lat", "intensity")

# Normalizar los valores de intensidad entre 0 y 1
df_congestion$intensity <- (df_congestion$intensity - min(df_congestion$intensity)) / 
                           (max(df_congestion$intensity) - min(df_congestion$intensity))

# Crear un mapa interactivo usando leaflet
leaflet(df_congestion) %>%
  addProviderTiles("OpenStreetMap") %>%  # Añadir la capa base
  addHeatmap(
    lng = ~long, lat = ~lat,              # Coordenadas de longitud y latitud
    intensity = ~intensity,               # Intensidad normalizada
    blur = 35,                            # Incrementar el desenfoque para suavizar el mapa
    max = max(df_congestion$intensity) * 2,  # Ajustar el valor máximo de intensidad
    radius = 25                           # Aumentar el radio para que se vea más suave
  ) %>%
  addLegend("bottomright",                # Añadir la leyenda para interpretar el mapa de calor
            title = "Mapa de Calor de Congestión",
            colors = c("blue", "green", "yellow", "red"),
            labels = c("Bajo", "Moderado", "Alto", "Muy Alto"))

7 Análisis consolidado de eventos.

Con el fin de analizar de forma integral, en un mismo espacio geográfico, el comportamiento e interacción de eventos, se utiliza la sincronización de mapas interactivos de leaflet.

leafsync::sync(m26_peligro, m26_accidente, m26_congestion, m26_via_cerrada)

8 Conclusión

El análisis de la forma como se concentran distintos eventos en el transporte y el tránsito de un territorio, como accidentes, cierres viales, congestiones y otros peligros, permite planificar de manera más precisa la movilidad y gestionar soluciones de acuerdo a la identificación de patrones en la ocurrencia de dichos fenómenos, ya sea generando infraestructura, señalización u opciones de movilidad motorizada y no motorizada. Esto, evidencia la relevancia de tomar decisiones basadas en evidencia y la importancia, por ende, de registrar y procesar datos de manera correcta.