Accidentalidad del municipio de Madrid

ACCIDENTES
MADRID
CSV
Autor/a
Afiliación

Adrián Herrera Martí

Universitat de València

Fecha de publicación

10 de mayo de 2024

Input

Los datos empleados en este informe provienen de “https://datos.gob.es/es/catalogo”, un portal de datos abiertos impulsado por la secretaría de estado de digitalización e inteligencia artificial. En concreto estamos trabajando con los accidentes de tráfico de la ciudad de Madrid en el año 2023, se incluye un registro por persona implicada en el accidente. Cada registro cuenta con:

“num_expediente” ; “fecha” ; “hora” ; “localizacion” ; “numero” ; “cod_distrito” ; “tipo_accidente” ; “estado_meteorológico” ; “tipo_vehiculo” ; “tipo_persona” ; “rango_edad” ; “sexo” ; “cod_lesividad” ; “lesividad” ; “coordenada_x_utm” ; “coordenada_y_utm” ; “positiva_alcohol” ; “positiva_droga”.

Se pueden descargar los datos desde el siguiente enlace: https://datos.madrid.es/egob/catalogo/300228-28-accidentes-trafico-detalle.csv

A continuación se muestra información de interés:

Los datos están disponibles en formato CSV y XLS.

En nuestro caso hemos trabajado con los datos en formato .csv.

Descripcion

Cargamos las librerias necesarias:

libs <- c("tidyverse", "sf", "mapSpain", "osmdata", "ggspatial", "leaflet", "prettymapr", "prettydoc","spatstat", "sp", "sf","splancs","dplyr","tidygeocoder","knitr") 

installed_libs <- libs %in% rownames(installed.packages()) 
if (any(installed_libs == F)) 
  
{install.packages(libs[!installed_libs])} 
invisible(lapply(libs, library, character.only = T)) 
rm(libs, installed_libs)

Cargamos los datos:

datos <- read.csv(file = "data/2023_Accidentalidad.csv", sep = ";")

Como nuestro objetivo es representar los accidentes, nos quedamos con las variables necesarias para dicha tarea:

datos_filtrados <- datos[, c("num_expediente", "localizacion","numero", "coordenada_x_utm","coordenada_y_utm")]

Cada observación del objeto “datos_filtrados” se corresponde con una persona implicada en un mismo accidente, por lo que vamos a agrupar a todos los implicados en un mismo accidente para reducir el tamaño de los datos de cara a poder representarlos:

datos_agrupados <- aggregate(. ~ num_expediente, data = datos_filtrados, FUN = function(x) x[1])

Ahora, convertimos a formato numérico las variables “coordenada_x_utm” y “coordenada_y_utm”

datos_agrupados$coordenada_x_utm <- as.numeric(datos_agrupados$coordenada_x_utm) 
Warning: NAs introducidos por coerción
datos_agrupados$coordenada_y_utm <- as.numeric(datos_agrupados$coordenada_y_utm)
Warning: NAs introducidos por coerción

Como vemos se nos generan valores nulos (NA), lo que supone un impedimento a la hora de poder representar los datos. En concreto, las observaciones que corresponden con estos valores son:

which(is.na(datos_agrupados$coordenada_x_utm), arr.ind = TRUE)
[1] 19559 19794 19801 19873
which(is.na(datos_agrupados$coordenada_y_utm), arr.ind = TRUE)
[1] 19559 19794 19801 19873
kable(datos_agrupados[c(19559,19794,19801,19873),], caption = "Observaciones con coordenadas NA", align = "c")
Observaciones con coordenadas NA
num_expediente localizacion numero coordenada_x_utm coordenada_y_utm
19559 2023S037825 CALL. ATOCHA, 112 112 NA NA
19794 2023S038704 PASARELA DE LA PRINCESA +00500I NA NA
19801 2023S038728 AVDA. PALOMERAS / CALL. PUERTO DEL PICO 150 NA NA
19873 2023S038882 AUTOV. M-30, 07NC40 07NC40 NA NA

Con esto sabemos que esas observaciones tienen valores nulos en sus coordenadas.

Tratamiento

Para resolver esto, nos quedamos con un dataframe con los datos erróneos:

datos_erroneos <- subset(datos_agrupados, is.na(datos_agrupados$coordenada_x_utm))

Seguidamente, mediante google maps, obtenemos las coordenadas exactas de las observaciones faltantes:

coordenadas <- data.frame(
  lon = c(-3.6934083011307823,
          -3.697221725741146,
          -3.649032006107653),
  lat = c(40.409664499236506,
          40.38828814833728,
          40.38577512287311)     
)

La observación 19873 correspondiente a un accidente en la M-30 tiene un formato de dirección que no se puede identificar, por lo que prescindiremos de ella, debido a que es una observación entre 20672, los resultados sobre posible enfoques de análisis de los datos no se verían afectados. Su eliminación se hará tras añadir las coordenadas a las demás observaciones.

Creamos un objeto sf con el dataframe de las coordenadas

coords_sf <- st_as_sf(coordenadas, coords = c("lon", "lat"), crs = 4326)

Definimos el sistemas de coordenadas resultante (UTM)

crs_utm <- st_crs(32630)

Transformamos las coordenadas obtenidas anteriormente a formato UTM

coords_utm <- st_transform(coords_sf, crs_utm)

Aislamos las coordenadas UTM para tenerlas más controladas

coordenadas_utm <- st_coordinates(coords_utm)

Separamos las coordenadas individualmente

coordenadas_utm_x <- coordenadas_utm[, 1]
coordenadas_utm_y <- coordenadas_utm[, 2]

Creamos un dataframe con las coordenadas definitivas

coordenadas_utm_df <- data.frame(coordenada_x_utm = coordenadas_utm_x, coordenada_y_utm = coordenadas_utm_y)

Añadimos las coordenadas a un nuevo objeto igual a “datos_agrupados”

datos_agrupados_correctos <- datos_agrupados

datos_agrupados_correctos[19559, 'coordenada_x_utm'] <- coordenadas_utm_df[1,"coordenada_x_utm"]
datos_agrupados_correctos[19794, 'coordenada_x_utm'] <- coordenadas_utm_df[2,"coordenada_x_utm"]
datos_agrupados_correctos[19801, 'coordenada_x_utm'] <- coordenadas_utm_df[3,"coordenada_x_utm"]
 
  
datos_agrupados_correctos[19559, 'coordenada_y_utm'] <- coordenadas_utm_df[1,"coordenada_y_utm"]
datos_agrupados_correctos[19794, 'coordenada_y_utm'] <- coordenadas_utm_df[2,"coordenada_y_utm"]
datos_agrupados_correctos[19801, 'coordenada_y_utm'] <- coordenadas_utm_df[3,"coordenada_y_utm"]

Eliminamos la observación que habíamos comentado

datos_agrupados_correctos <- datos_agrupados_correctos[!is.na(datos_agrupados_correctos$coordenada_x_utm), ]

Representamos los datos

datos_agrupados_correctos_sf <- sf::st_as_sf(datos_agrupados_correctos,
                           coords = c("coordenada_x_utm",
                                      "coordenada_y_utm"), crs = 25830)

sf1 <- sf::st_transform(datos_agrupados_correctos_sf, crs = 4326)
sf1 %>%
  leaflet::leaflet() %>%
  leaflet::addTiles() %>%
  leaflet::addCircles(popup = ~localizacion)

Tras representar los datos, vemos que un punto nos aparece fuera del municipio de Madrid, vamos a tratar de detectar qué accidente ha sido:

madrid <- mapSpain::esp_get_munic() %>% filter(cpro=="28" & cmun == "079")
madrid <- sf::st_transform(madrid, crs=4326)
datos_agrupados_correctos_sf <- st_transform(datos_agrupados_correctos_sf, crs = st_crs(madrid))

pts_in <- sf::st_intersection(datos_agrupados_correctos_sf,madrid)
pts_out <- sf::st_difference(datos_agrupados_correctos_sf,madrid)
kable(pts_out, caption = "Puntos marcados fuera de Madrid", align = "c")
Puntos marcados fuera de Madrid
num_expediente localizacion numero codauto ine.ccaa.name cpro ine.prov.name cmun name LAU_CODE geometry
852 2023S001247 CTRA. MEDIODIA / CMNO. VIEJO DEL CURA 4 13 Madrid, Comunidad de 28 Madrid 079 Madrid 28079 POINT (-3.654725 40.51292)
1334 2023S002142 CALL. PORTICO DE LA GLORIA / COMUNICACION 2 13 Madrid, Comunidad de 28 Madrid 079 Madrid 28079 POINT (-3.662682 40.51917)
2646 2023S005326 AVDA. LOS ROSALES, 409 409 13 Madrid, Comunidad de 28 Madrid 079 Madrid 28079 POINT (-3.659836 40.32799)
3052 2023S006218 CTRA. MEDIODIA, 6 6 13 Madrid, Comunidad de 28 Madrid 079 Madrid 28079 POINT (-3.653736 40.51216)
4260 2023S009719 AVDA. CASA QUEMADA, 2 2 13 Madrid, Comunidad de 28 Madrid 079 Madrid 28079 POINT (-3.836651 40.47566)
6299 2023S016698 CTRA. M-616, KILOMETRO 3 +00500E 13 Madrid, Comunidad de 28 Madrid 079 Madrid 28079 POINT (-3.68213 40.55311)
6485 2023S017160 INCORPORACIÓN A M45 DIRECCIÓN VALENCIA 9 13 Madrid, Comunidad de 28 Madrid 079 Madrid 28079 POINT (-3.554614 40.41155)
8519 2023S021243 CTRA. VICALVARO A COSLADA / AVDA. MARCONI 18 13 Madrid, Comunidad de 28 Madrid 079 Madrid 28079 POINT (-3.576832 40.42856)
9084 2023S022269 CTRA. CARABANCHEL A ARAVACA / M502 2 13 Madrid, Comunidad de 28 Madrid 079 Madrid 28079 POINT (-3.77857 40.39956)
9453 2023S022935 AUTOV. A-4, +01200E; PK 4,700 DE A-4 ENTRADA A MADRID +01200E 13 Madrid, Comunidad de 28 Madrid 079 Madrid 28079 POINT (-3.691517 40.31985)
10502 2023S024414 AEROP. TERMINAL T-4, 0 0 13 Madrid, Comunidad de 28 Madrid 079 Madrid 28079 POINT (-7.488744 0)
10730 2023S024695 CALL. MAESTRO ARBOS, 13 13 13 Madrid, Comunidad de 28 Madrid 079 Madrid 28079 POINT (-3.764819 40.35924)
11581 2023S025851 AVDA. MARCONI / CTRA. VICALVARO A LA ESTACION DE O’DONNELL 1 13 Madrid, Comunidad de 28 Madrid 079 Madrid 28079 POINT (-3.576844 40.42859)
11979 2023S026398 CALL. JULIO CARO BAROJA / CTRA. MEDIODIA 2 13 Madrid, Comunidad de 28 Madrid 079 Madrid 28079 POINT (-3.648282 40.5105)
12466 2023S027158 CTRA. VICALVARO A LA ESTACION DE O’DONNELL / AVDA. MARCONI 137D 13 Madrid, Comunidad de 28 Madrid 079 Madrid 28079 POINT (-3.576718 40.42944)
13146 2023S028201 AVDA. MARCONI / CALL. CAMPEZO 14 13 Madrid, Comunidad de 28 Madrid 079 Madrid 28079 POINT (-3.576861 40.42858)
15213 2023S031205 CALL. CEA BERMUDEZ, 57 57 13 Madrid, Comunidad de 28 Madrid 079 Madrid 28079 POINT (-7.488744 0)
16822 2023S033647 CTRA. ZARZON, 6 6 13 Madrid, Comunidad de 28 Madrid 079 Madrid 28079 POINT (-3.780395 40.41454)
16870 2023S033711 CTRA. ACCESO A LA ESTACION DE O’DONNELL / AVDA. MARCONI 10 13 Madrid, Comunidad de 28 Madrid 079 Madrid 28079 POINT (-3.576828 40.42863)
17917 2023S035359 AUTOV. A-4, KM 5 ENTRADA MADRID +01200E 13 Madrid, Comunidad de 28 Madrid 079 Madrid 28079 POINT (-3.691967 40.32044)
19635 2023S038002 CALL. CAMPEZO / AVDA. MARCONI 16 13 Madrid, Comunidad de 28 Madrid 079 Madrid 28079 POINT (-3.576842 40.42863)

Como vemos, todos los puntos que en teoría están fuera de la superficie de madrid realmente tienen su ubicacíon en el própio municipio, vamos a ver qué está sucediendo.

leaflet() %>%
  addTiles() %>%
  addPolygons(data = madrid, color = "red", weight = 5) %>%
  addCircles(data = pts_out, popup = ~localizacion) %>%
  setView(lat=40.4380986,
          lng=-3.8443446,
          zoom=10)

Como vemos, casi todos los puntos se encuentran en el borde de la superficie por lo que realmente sí que están bien ubicados, aunque se considera que estan fuera del municipio.

Conociendo esto solo tenemos que corregir 2 puntos que comparten las mismas coordenadas:

puntos_perdidos <- pts_out[c(11, 17), ]
kable(puntos_perdidos, caption = "Puntos marcados fuera de Madrid", align = "c")
Puntos marcados fuera de Madrid
num_expediente localizacion numero codauto ine.ccaa.name cpro ine.prov.name cmun name LAU_CODE geometry
10502 2023S024414 AEROP. TERMINAL T-4, 0 0 13 Madrid, Comunidad de 28 Madrid 079 Madrid 28079 POINT (-7.488744 0)
15213 2023S031205 CALL. CEA BERMUDEZ, 57 57 13 Madrid, Comunidad de 28 Madrid 079 Madrid 28079 POINT (-7.488744 0)
leaflet() %>%
  addTiles() %>%
  addCircles(data = puntos_perdidos, popup = ~localizacion)

Para solucionar esto, volveremos a obtener las coordenadas precisas de las ubicaciones y las transformaremos a UTM como hemos hecho anteriormente:

coordenadas2 <- data.frame(
  lon = c(-3.5983969,
          -3.7179432),
  lat = c(40.4957946,
          40.4390803)     
)

coords_sf2 <- st_as_sf(coordenadas2, coords = c("lon", "lat"), crs = 4326)

coords_utm2 <- st_transform(coords_sf2, crs_utm)

coordenadas_utm2 <- st_coordinates(coords_utm2)

coordenadas_utm_x2 <- coordenadas_utm2[, 1]
coordenadas_utm_y2 <- coordenadas_utm2[, 2]

coordenadas_utm_df2 <- data.frame(coordenada_x_utm = coordenadas_utm_x2, coordenada_y_utm = coordenadas_utm_y2)

datos_agrupados_correctos[10502, 'coordenada_x_utm'] <- coordenadas_utm_df2[1,"coordenada_x_utm"]
datos_agrupados_correctos[15213, 'coordenada_x_utm'] <- coordenadas_utm_df2[2,"coordenada_x_utm"]

datos_agrupados_correctos[10502, 'coordenada_y_utm'] <- coordenadas_utm_df2[1,"coordenada_y_utm"]
datos_agrupados_correctos[15213, 'coordenada_y_utm'] <- coordenadas_utm_df2[2,"coordenada_y_utm"]

Output

Finalmente, tras todo este trabajo de depuración de datos, obtenemos el dataframe “accidentes_madrid_2023” que contiene 20671 observaciones correspondientes a cada accidente del año 2023 en el municipio de Madrid, preparado para ser usado con diversas metodologías de análisis como por ejemplo comprobar la existecia de CRS, la búsqueda de patrones entre otros.

Aquí representamos los datos:

accidentes_madrid_2023 <- sf::st_as_sf(datos_agrupados_correctos,
                           coords = c("coordenada_x_utm",
                                      "coordenada_y_utm"), crs = 25830)

sf1 <- sf::st_transform(accidentes_madrid_2023, crs = 4326)
sf1 %>%
  leaflet::leaflet() %>%
  leaflet::addTiles() %>%
  leaflet::addCircles(popup = ~localizacion)

Por último guardamos los datos

sf::st_write(accidentes_madrid_2023, "accidentes_madrid_2023.gpkg")



Proyecto de Innovación Educativa Emergente (PIEE-2737007)