Quinta entrega

Author

Valentina Bustamante

Instrucciones

Realice un análisis descriptivo completo y actualizado al julio 31 2024, incluyendo graficos, tablas y georreferenciación de la base de datos Accidentalidad_en_Barranquilla.csv. Deben entregar un script .R con los códigos usados.

  1. Contextualizar tanto la base de datos como las variables describiendo en qué consiste cada una de ellas. La base de datos está en el sitio web: https://www.datos.gov.co/Transporte/Accidentalidad-en-Barranquilla/yb9r-2dsi

  2. Analizar las características de la base de datos. Estas pueden incluir: número de filas, número de columnas, nombres de las variables, tipos de variables, entre otros.

  3. Analizar cada una de las variables según su tipo: numéricas y categóricas.

  4. Filtrar la base de datos para entender mejor su estructura. Aplique filtros en al menos cinco oportunidades.

  5. Utilice la función table para explorar la base de datos.

  6. Identifique los valores NA (Not Available) en la base de datos.

  7. Analice la presencia de posibles valores atípicos.

  8. Aplique imputación de datos usando las siguientes opciones para method: method=“pmm” method=“norm.predict” method=“norm.nob” method=“norm”

  9. Identifique datos atípicos para cada variable en el dataset usando las técnicas estudiadas en clase. Además, realice imputación de los datos atípicos con base en lo desarrollado en el ítem anterior. Imputación/Capping/Predicción Test de Rosner, Dixon, Grubbs, Hampel, Percentiles, Boxplots, Histogramas, Descriptivos.

Realice una exploración y un análisis descriptivo completo (incluyendo tablas de resumen y gráficos) de la base de datos disponible en este enlace. Incluya gráficos que presenten los datos sobre un mapa de Colombia, para visualizar la distribución geográfica de los precios de los combustibles en el país (2018-2024).

Librerías

library(gridExtra)
library(dplyr)
library(readr)
library(ggplot2)
library(knitr)
library(leaflet)
library(stringr)
library(kableExtra)
library(patchwork)
library(DT)
library(ggmap)
library(EnvStats)
library(httr)
library(jsonlite)
library(sf)
library(naniar)
library(VIM)
library(tidyr)
library(summarytools)
library(lubridate)
library(mice)
library(skimr)

Desarrollo de la primera parte

Sobre los datos

FECHA_ACCIDENTE HORA_ACCIDENTE GRAVEDAD_ACCIDENTE CLASE_ACCIDENTE SITIO_EXACTO_ACCIDENTE CANT_HERIDOS_EN._SITIO_ACCIDENTE CANT_MUERTOS_EN._SITIO_ACCIDENTE CANTIDAD_ACCIDENTES AÑO_ACCIDENTE MES_ACCIDENTE DIA_ACCIDENTE
2018-01-01T00:00:00.000 01:30:00:am Con heridos Atropello CL 87 9H 24 1 NA 1 2018 January Mon
2018-01-01T00:00:00.000 02:00:00:pm Solo daños Choque CL 110 CR 46 NA NA 1 2018 January Mon
2018-01-01T00:00:00.000 04:00:00:am Solo daños Choque AV CIRCUNVALAR CR 9G NA NA 1 2018 January Mon
2018-01-01T00:00:00.000 04:30:00:am Solo daños Choque CLLE 72 CRA 29 NA NA 1 2018 January Mon
2018-01-01T00:00:00.000 05:20:00:pm Solo daños Choque VIA 40 CALLE 75 NA NA 1 2018 January Mon
2018-01-01T00:00:00.000 06:00:00:pm Con heridos Choque CR 8 CL 41 3 NA 1 2018 January Mon

La base de datos proporcionada contiene 25,600 registros con información detallada sobre los accidentes de tránsito ocurridos en Barranquilla, según los informes policiales de accidentes de tránsito (IPAT). Para cada incidente, se reportan datos como la fecha, hora, gravedad, clase del accidente, sitio exacto, y cantidades de heridos y muertos en el lugar del accidente. La información es proporcionada por la Alcaldía Distrital de Barranquilla.

La base de datos cuenta con 11 variables tanto numéricas como categóricas. Las variables que contienen datos de texto incluyen la fecha completa del incidente, la hora exacta en la que ocurrió, la gravedad del accidente, tipo del incidente, y el lugar exacto donde sucedió. Por otro lado, las variables numéricas abarcan el número de heridos y fallecidos en el sitio del accidente, así como el total de accidentes registrados en ese momento. Además, componentes temporales como el año, mes y día de la semana también se almacenan como texto.

Enfocándonos un poco en las variables numéricas, veamos ahora la distribución de el número de heridos en sitio del accidente. En promedio se registraron, 1.47 heridos por accidente, también se destaca la presencia de 15626 datos faltantes.

summary(be5$CANT_HERIDOS_EN._SITIO_ACCIDENTE)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
  1.000   1.000   1.000   1.472   2.000  42.000   15626 
ggplot(be5, aes(y = CANT_HERIDOS_EN._SITIO_ACCIDENTE)) +
  geom_boxplot() +
  labs(title = "Heridos en el Sitio del Accidente",
       y = "Cantidad de Heridos",
       x = "") +
  theme_minimal()

Al graficar esta variable, observamos la presencia de muchos datos atípicos, con valores extremos hacia arriba. El registro con más heridos presentados fue con 42 heridos. Ahora, para la variable cantidad de muertos en accidente.

summary(be5$CANT_MUERTOS_EN._SITIO_ACCIDENTE)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
  1.000   1.000   1.000   1.036   1.000   2.000   25358 
ggplot(be5, aes(y = CANT_MUERTOS_EN._SITIO_ACCIDENTE)) +
  geom_boxplot() +
  labs(title = "Muertos en el Sitio del Accidente",
       y = "Cantidad de muertos",
       x = "") +
  theme_minimal()

Para aquellos registros donde se presentaron decesos, se tiene un promedio de una persona fallecida por accidente, sin embargo, existen outliers donde la cantidad de muertos asciende a dos personas. De igual manera que con la variable anterior, se destaca la presencia de 25358 datos faltantes.Y por último, para la variable relacionada al total de accidentes reportados en ese momento

summary(be5$CANTIDAD_ACCIDENTES)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
      1       1       1       1       1       2 
ggplot(be5, aes(y = CANTIDAD_ACCIDENTES)) +
  geom_boxplot() +
  labs(title = "Accidentes en el mismo momento",
       y = "Cantidad de accidentes",
       x = "") +
  theme_minimal()

De manera similar a la variable anterior, se tiene una media de 1 (sd: 0.013971) y mediana de 1 (RIQ: 0). También se destaca la presencia de outliers que indican 2 accidentes simultaneos, en el mismo sitio y a la misma hora. A diferencia de las otras dos variables numéricas, no se encuentran datos faltantes en esta variable.

Para las variables categóricas, comenzando con la fecha del accidente:

kable(head(sort(table(be5$FECHA_ACCIDENTE), decreasing = TRUE))) %>%
  kable_styling(full_width = FALSE, bootstrap_options = c("striped", "hover", "condensed"))
Var1 Freq
2018-06-08T00:00:00.000 34
2018-11-16T00:00:00.000 33
2019-05-22T00:00:00.000 31
2019-10-05T00:00:00.000 30
2018-10-19T00:00:00.000 29
2019-11-20T00:00:00.000 29

Se tiene información de 2357 días, siendo el 8 de junio del 2018 el día con más accidentes (34 accidentes), seguido del 16 de noviembre del 2018 con 33 y el 22 de mayo del 2019 con 31. Realizando el mismo análisis con la hora de accidente.

kable(head(sort(table(be5$HORA_ACCIDENTE), decreasing = TRUE))) %>%
  kable_styling(full_width = FALSE, bootstrap_options = c("striped", "hover", "condensed"))
Var1 Freq
03:00:00:pm 408
04:00:00:pm 408
12:30:00:pm 387
05:00:00:pm 384
08:00:00:am 384
02:00:00:pm 371

Se nota que las horas con más accidentes registrados son las 3pm (408), 4pm (408) y 12:30pm (387).

kable(table(be5$GRAVEDAD_ACCIDENTE))%>%
  kable_styling(full_width = FALSE, bootstrap_options = c("striped", "hover", "condensed"))
Var1 Freq
Con heridos 9901
Con muertos 252
Solo daños 15457

Afortunadamente, la mayoría de los accidentes fueron solo daños materiales, seguido de heridos y en menor medida con muertos (0.98%).

kable(table(be5$CLASE_ACCIDENTE))%>%
  kable_styling(full_width = FALSE, bootstrap_options = c("striped", "hover", "condensed"))
Var1 Freq
Atropello 1344
Caida Ocupante 194
Choque 23819
Incendio 13
Otro 123
Volcamiento 117

De los 25610 accidentes reportados, 23819 (93%) son debido a choques, 1344 (5,24%) por atropellos, el 1,76% restante está compuesto entre incendios, volcamientos, caída del ocupante, entre otros. Para la variable año, encontramos que 2018 es el año con más accidentes registrados y 2023 el que menos accidentes presentó, excluyendo 2024 dado que no se ha finalizado el año.

kable(table(be5$AÑO))%>%
  kable_styling(full_width = FALSE, bootstrap_options = c("striped", "hover", "condensed"))
Var1 Freq
2018 5898
2019 5645
2020 3281
2021 4700
2022 3683
2023 1662
2024 741

En cuanto a la variable relacionada al mes del accidente, se puede observar que febrero es el mes con más accidentes reportados en la ciudad de Barranquilla, seguido de marzo y enero. Agosto, con 1918 registros, es el mes con menos accidentes.

kable(table(be5$MES_ACCIDENTE))%>%
  kable_styling(full_width = FALSE, bootstrap_options = c("striped", "hover", "condensed"))
Var1 Freq
April 2010
August 1918
December 2189
February 2477
January 2349
July 1932
June 2103
March 2446
May 2121
November 1995
October 2090
September 1980

Y por último, el día con más accidentes reportados es el día de martes, seguido por viernes y miércoles. En cambio, los domingos, son los días con menos accidentes reportados.

kable(table(be5$DIA_ACCIDENTE))%>%
  kable_styling(full_width = FALSE, bootstrap_options = c("striped", "hover", "condensed"))
Var1 Freq
Fri 3920
Mon 3774
Sat 3735
Sun 2577
Thu 3756
Tue 4009
Wed 3839

Filtros

Veamos ahora aquellos accidentes realizados los fines de semana por choques según el mes.

be5 %>%
  filter(DIA_ACCIDENTE %in% c('Sat', 'Sun')) %>%
  filter(CLASE_ACCIDENTE == 'Choque') %>%
  pull(MES_ACCIDENTE) %>%
  table() %>%
  kable() %>%
  kable_styling(full_width = FALSE, bootstrap_options = c("striped", "hover", "condensed"))
. Freq
April 443
August 446
December 492
February 557
January 511
July 436
June 498
March 578
May 478
November 423
October 495
September 465

Siendo Marzo (578) el mes con más accidentes por choques en los fines de semana, seguido por febrero (557) y enero (511).Noviembre fue el mes que presentó menos accidentes los fines de semana (423).

be5 %>%
  filter(!DIA_ACCIDENTE %in% c('Sat', 'Sun')) %>%
  filter(CLASE_ACCIDENTE == 'Choque') %>%
  pull(MES_ACCIDENTE) %>%
  table() %>%
  kable() %>%
  kable_styling(full_width = FALSE, bootstrap_options = c("striped", "hover", "condensed"))
. Freq
April 1428
August 1354
December 1536
February 1753
January 1670
July 1395
June 1454
March 1690
May 1490
November 1426
October 1435
September 1366

Para el resto de días de la semana, febrero es el mes con más choques (1753), seguido de marzo (1690) y enero (1670). Agosto fue el mes con menos accidentes entre semana (1354).

be5 %>%
  filter(MES_ACCIDENTE %in% c('January', 'February', 'March', 'April', 'May', 'June')) %>%
  filter(GRAVEDAD_ACCIDENTE == 'Con heridos') %>%
  pull(AÑO_ACCIDENTE) %>%
  table()%>%
  kable() %>%
  kable_styling(full_width = FALSE, bootstrap_options = c("striped", "hover", "condensed"))
. Freq
2018 865
2019 795
2020 533
2021 659
2022 861
2023 833
2024 722
be5 %>%
  filter(!MES_ACCIDENTE %in% c('January', 'February', 'March', 'April', 'May', 'June')) %>%
  filter(GRAVEDAD_ACCIDENTE == 'Con heridos') %>%
  pull(AÑO_ACCIDENTE) %>%
  table()%>%
  kable() %>%
  kable_styling(full_width = FALSE, bootstrap_options = c("striped", "hover", "condensed"))
. Freq
2018 866
2019 795
2020 524
2021 782
2022 892
2023 774

Durante la primera mitad del año, 2020 fue el año con menos accidentes con heridos, mientras 2018 fue el año con más accidentes graves. Para el segundo semestre del año, el año con más accidentes graves fue 2022, nuevamente 2020 siendo el año con menos de este tipo de accidentes.

be5 %>%
  filter(DIA_ACCIDENTE %in% c('Sat', 'Sun')) %>%
  filter(GRAVEDAD_ACCIDENTE == 'Con heridos') %>%
  pull(CANT_HERIDOS_EN._SITIO_ACCIDENTE) %>%
  table()%>%
  kable() %>%
  kable_styling(full_width = FALSE, bootstrap_options = c("striped", "hover", "condensed"))
. Freq
1 1798
2 694
3 154
4 49
5 23
6 9
7 1
9 1
11 1
be5 %>%
  filter(!DIA_ACCIDENTE %in% c('Sat', 'Sun')) %>%
  filter(GRAVEDAD_ACCIDENTE == 'Con heridos') %>%
  pull(CANT_HERIDOS_EN._SITIO_ACCIDENTE) %>%
  table()%>%
  kable() %>%
  kable_styling(full_width = FALSE, bootstrap_options = c("striped", "hover", "condensed"))
. Freq
1 5061
2 1588
3 309
4 103
5 34
6 20
7 13
8 8
9 5
10 7
11 5
12 5
13 2
14 2
15 1
16 1
18 1
19 1
20 1
21 1
22 1
23 1
42 1

Se puede observar como el rango de heridos en el sitio del accidente aumentó en los días de semana, manteniéndose entre 1 y 11 los sábados y domingo y entre 1 y 42 los días de semana. Aún así, son muchísimo más común que solo hayan entre 1 y 3 heridos.

Valores NA

summary(be5, na.rm = FALSE)
 FECHA_ACCIDENTE    HORA_ACCIDENTE     GRAVEDAD_ACCIDENTE CLASE_ACCIDENTE   
 Length:25610       Length:25610       Length:25610       Length:25610      
 Class :character   Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character  
                                                                            
                                                                            
                                                                            
                                                                            
 SITIO_EXACTO_ACCIDENTE CANT_HERIDOS_EN._SITIO_ACCIDENTE
 Length:25610           Min.   : 1.000                  
 Class :character       1st Qu.: 1.000                  
 Mode  :character       Median : 1.000                  
                        Mean   : 1.472                  
                        3rd Qu.: 2.000                  
                        Max.   :42.000                  
                        NA's   :15626                   
 CANT_MUERTOS_EN._SITIO_ACCIDENTE CANTIDAD_ACCIDENTES AÑO_ACCIDENTE 
 Min.   :1.000                    Min.   :1           Min.   :2018  
 1st Qu.:1.000                    1st Qu.:1           1st Qu.:2019  
 Median :1.000                    Median :1           Median :2020  
 Mean   :1.036                    Mean   :1           Mean   :2020  
 3rd Qu.:1.000                    3rd Qu.:1           3rd Qu.:2021  
 Max.   :2.000                    Max.   :2           Max.   :2024  
 NA's   :25358                                                      
 MES_ACCIDENTE      DIA_ACCIDENTE     
 Length:25610       Length:25610      
 Class :character   Class :character  
 Mode  :character   Mode  :character  
                                      
                                      
                                      
                                      

Se observa la presencia de NA’s solo en las columnas que brindan información sobre la cantidad de heridos y muertos en accidente, veamos ahora si el número de valores faltantes coincide con aquellos registros donde la gravedad del accidente sugiere la ausencia de personas heridas y muertas.

be5 %>%
  # Heridos
  filter(GRAVEDAD_ACCIDENTE == 'Con heridos') %>%
  summary(na.rm = FALSE)
 FECHA_ACCIDENTE    HORA_ACCIDENTE     GRAVEDAD_ACCIDENTE CLASE_ACCIDENTE   
 Length:9901        Length:9901        Length:9901        Length:9901       
 Class :character   Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character  
                                                                            
                                                                            
                                                                            
                                                                            
 SITIO_EXACTO_ACCIDENTE CANT_HERIDOS_EN._SITIO_ACCIDENTE
 Length:9901            Min.   : 1.000                  
 Class :character       1st Qu.: 1.000                  
 Mode  :character       Median : 1.000                  
                        Mean   : 1.469                  
                        3rd Qu.: 2.000                  
                        Max.   :42.000                  
                                                        
 CANT_MUERTOS_EN._SITIO_ACCIDENTE CANTIDAD_ACCIDENTES AÑO_ACCIDENTE 
 Min.   : NA                      Min.   :1           Min.   :2018  
 1st Qu.: NA                      1st Qu.:1           1st Qu.:2019  
 Median : NA                      Median :1           Median :2021  
 Mean   :NaN                      Mean   :1           Mean   :2021  
 3rd Qu.: NA                      3rd Qu.:1           3rd Qu.:2022  
 Max.   : NA                      Max.   :1           Max.   :2024  
 NA's   :9901                                                       
 MES_ACCIDENTE      DIA_ACCIDENTE     
 Length:9901        Length:9901       
 Class :character   Class :character  
 Mode  :character   Mode  :character  
                                      
                                      
                                      
                                      

Tomando los accidentes con heridos, se puede observar que el número de NA’s en el número de muertos coincide con el número de registros de este dataframe filtrado, es decir, no hay problema con la presencia de estos datos dado que representan el que no hayan habido muertos en estos accidentes

be5 %>%
  # Muertos
  filter(GRAVEDAD_ACCIDENTE == 'Con muertos') %>%
  summary(na.rm = FALSE)
 FECHA_ACCIDENTE    HORA_ACCIDENTE     GRAVEDAD_ACCIDENTE CLASE_ACCIDENTE   
 Length:252         Length:252         Length:252         Length:252        
 Class :character   Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character  
                                                                            
                                                                            
                                                                            
                                                                            
 SITIO_EXACTO_ACCIDENTE CANT_HERIDOS_EN._SITIO_ACCIDENTE
 Length:252             Min.   : 1.000                  
 Class :character       1st Qu.: 1.000                  
 Mode  :character       Median : 1.000                  
                        Mean   : 1.831                  
                        3rd Qu.: 2.000                  
                        Max.   :12.000                  
                        NA's   :169                     
 CANT_MUERTOS_EN._SITIO_ACCIDENTE CANTIDAD_ACCIDENTES AÑO_ACCIDENTE 
 Min.   :1.000                    Min.   :1           Min.   :2018  
 1st Qu.:1.000                    1st Qu.:1           1st Qu.:2019  
 Median :1.000                    Median :1           Median :2021  
 Mean   :1.036                    Mean   :1           Mean   :2021  
 3rd Qu.:1.000                    3rd Qu.:1           3rd Qu.:2023  
 Max.   :2.000                    Max.   :1           Max.   :2024  
                                                                    
 MES_ACCIDENTE      DIA_ACCIDENTE     
 Length:252         Length:252        
 Class :character   Class :character  
 Mode  :character   Mode  :character  
                                      
                                      
                                      
                                      
be5 %>%
  
  # Muertos con heridos
  filter(GRAVEDAD_ACCIDENTE == 'Con muertos') %>%
  filter(!is.na(CANT_HERIDOS_EN._SITIO_ACCIDENTE)) %>%
  summary(na.rm = FALSE)
 FECHA_ACCIDENTE    HORA_ACCIDENTE     GRAVEDAD_ACCIDENTE CLASE_ACCIDENTE   
 Length:83          Length:83          Length:83          Length:83         
 Class :character   Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character  
                                                                            
                                                                            
                                                                            
 SITIO_EXACTO_ACCIDENTE CANT_HERIDOS_EN._SITIO_ACCIDENTE
 Length:83              Min.   : 1.000                  
 Class :character       1st Qu.: 1.000                  
 Mode  :character       Median : 1.000                  
                        Mean   : 1.831                  
                        3rd Qu.: 2.000                  
                        Max.   :12.000                  
 CANT_MUERTOS_EN._SITIO_ACCIDENTE CANTIDAD_ACCIDENTES AÑO_ACCIDENTE 
 Min.   :1.00                     Min.   :1           Min.   :2018  
 1st Qu.:1.00                     1st Qu.:1           1st Qu.:2019  
 Median :1.00                     Median :1           Median :2021  
 Mean   :1.06                     Mean   :1           Mean   :2021  
 3rd Qu.:1.00                     3rd Qu.:1           3rd Qu.:2022  
 Max.   :2.00                     Max.   :1           Max.   :2024  
 MES_ACCIDENTE      DIA_ACCIDENTE     
 Length:83          Length:83         
 Class :character   Class :character  
 Mode  :character   Mode  :character  
                                      
                                      
                                      

En los 7 años de registros del dataframe se presentaron 252 accidentes con al menos un deceso, donde 169 no presentaron heridos. Es decir, en los 83 accidentes restantes se presentó por lo menos una persona herida.

Para entender de mejor manera lo que está pasando con los datos faltantes, se hizo uso de la función aggr() de VIMla cual muestran la proporción y el patrón de estos datos.

aggr(be5, 
     numbers = TRUE, 
     col = c("navyblue", "red"), 
     cex.axis = 0.5,
     gap = 3,
     ylab = c("Proportion of missingness", "Missingness pattern"),
     labels = c(
          "Fecha",
          "Hora",
          "gravedad",
          "clase",
          "sitio",
          "n_heridos",
          "n_muertos",
          "n_acc",
          "Año",
          "Mes",
          "Día"
        )
)

prop.table(table(be5$GRAVEDAD_ACCIDENTE))*100

Con heridos Con muertos  Solo daños 
 38.6606794   0.9839906  60.3553299 

Esto arroja un 60,36% registros con la ausencia de datos en en el número de heridos y número de muertos al tiempo, este porcentaje coincide con el 60,36% de accidentes relacionados a solo daño. De igual manera, corresponde también el 38,66% faltante de numero de muertos al 38,66% de aquellos accidentes con heridos. El porcentaje correspondiente al 0.9% de accidentes con muertes se reparte entre una ausencia de heridos de 0.66% y registros completos de 0.32%.

Esto quiere decir que los datos faltantes no requieren una imputación dado que no están relacionados a un error o datos no tomados, si no que hacen referencia a un valor que representa la ausencia de heridos o muertos como lo podría ser un 0.

be5s <- be5 %>%
  mutate_all(~replace(., is.na(.), 0))

aggr(be5s, 
     numbers = TRUE, 
     col = c("navyblue", "red"), 
     cex.axis = 0.5,
     gap = 3,
     ylab = c("Proportion of missingness", "Missingness pattern"),
     labels = c(
          "Fecha",
          "Hora",
          "gravedad",
          "clase",
          "sitio",
          "n_heridos",
          "n_muertos",
          "n_acc",
          "Año",
          "Mes",
          "Día"
        )
)

par(mfrow = c(1, 2), mar = c(5, 4, 4, 2) + 0.1)

hist(be5$CANT_HERIDOS_EN._SITIO_ACCIDENTE,
     main = "BE5 (con NAs)",
     xlab = "Cantidad de Heridos",
     ylab = "Frecuencia",
     col = "#b0ed9a",
     border = "black",
     xlim = c(0, 45),
     breaks = 10)

hist(be5s$CANT_HERIDOS_EN._SITIO_ACCIDENTE,
     main = "BE5S (con 0)",
     xlab = "Cantidad de Heridos",
     ylab = "Frecuencia",
     col = "#b0ed9a",
     border = "black",
     xlim = c(0, 45),
     breaks = 10)

par(mfrow = c(1, 1))

La distribución no parece cambiar mucho, excepto por la gran concentración ahora de los datos en la parte izquierda de la gráfica de BE5S.

par(mfrow = c(1, 2), mar = c(5, 4, 4, 2) + 0.1)

hist(be5$CANT_MUERTOS_EN._SITIO_ACCIDENTE,
     main = "BE5 (con NAs)",
     xlab = "Cantidad de muertos",
     ylab = "Frecuencia",
     col = "#b0ed9a",
     border = "black",
     xlim = c(0, 3),
     breaks =3)

hist(be5s$CANT_MUERTOS_EN._SITIO_ACCIDENTE,
     main = "BE5S (con 0)",
     xlab = "Cantidad de muertos",
     ylab = "Frecuencia",
     col = "#b0ed9a",
     border = "black",
     xlim = c(0, 3),
     breaks = 3)

par(mfrow = c(1, 1))

No se puede decir lo mismo de la cantidad de muertos, debido a que su distribución si cambia notoriamente. Esto era de esperarse debido al porcentaje de missingness que presenta esta variable (99,1%).

Debido al gran procentaje de datos faltantes también se podría proceder a eliminar las columnas involucradas, sin embargo para proceder con el análisis exploratorio se trabajará con la base de datos con los NAs. ### Gráficas

ggplot(be5, aes(x = AÑO_ACCIDENTE, fill = GRAVEDAD_ACCIDENTE)) +
  geom_bar(position = "dodge") +
  scale_x_continuous(breaks = seq(min(be5$AÑO_ACCIDENTE), max(be5$AÑO_ACCIDENTE), by = 1)) +
  labs(title = "Relación entre Año del Accidente y Gravedad del Accidente",
       x = "Año del Accidente",
       y = "Número de Accidentes",
       fill = "Gravedad del Accidente") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

Se puede observar como 2018 fue un año con una recurrencia de accidentes bastante alta, por suerte, predominan aquellos accidentes donde solo hubo daños materiales en la mayoría de los años. En lo transcurrido del 2024, pasa algo muy curioso, y es que la categoría dominante de accidentes fueron aquellos con heridos, seguido de con muertos. Además, se notó una disminución bastante notable de accidentes en el 2020 en comparación al 2019 y en el 2021 en comparación al 2023. Los accidentes con muertes tienen recurrencias similares en todos los años.

be5 %>%
  group_by(AÑO_ACCIDENTE, CLASE_ACCIDENTE) %>%
  summarise(n = n(), .groups = 'drop') %>%
  ggplot(aes(x = AÑO_ACCIDENTE, y = n, color = CLASE_ACCIDENTE, group = CLASE_ACCIDENTE)) +
  geom_line(size = 0.5) +  
  scale_x_continuous(breaks = seq(min(be5$AÑO_ACCIDENTE), max(be5$AÑO_ACCIDENTE), by = 1)) +
  labs(title = "Relación entre Año del Accidente y Tipo del Accidente",
       x = "Año del Accidente",
       y = "Número de Accidentes",
       color = "Tipo de accidente") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

Tal como se observó al momento de analizar cada variable individualmente, la proporción de accidentes por choques es muchísimo mayor en comparación a las otras razones. Se puede notar una disminución también los casos de accidentes por choque, mientras las otras categorías se mantienen constantes.

ggplot(be5, aes(x = CLASE_ACCIDENTE, y = CANT_HERIDOS_EN._SITIO_ACCIDENTE)) +
  geom_boxplot() +
  labs(title = "Distribución de la Cantidad de Heridos en el Sitio por Clase de Accidente",
       x = "Clase de Accidente",
       y = "Cantidad de Heridos en el Sitio del Accidente") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

Aquí vemos por primera vez los atípicos, como se puede ver son muchísimos. Ya se había observado que la variable de la cantidad de heridos tenía un rango bastante amplio, desde, ahora por la imputación realizada, 0 hasta 42.

out <- boxplot.stats(be5$CANT_HERIDOS_EN._SITIO_ACCIDENTE)$out
out_ind <- which(be5$CANT_HERIDOS_EN._SITIO_ACCIDENTE %in% c(out))
datatable(be5s[out_ind, c(3,4,6)])

Al tomar los datos atípicos, se observa la existencia de 303 datos atípicos en la variable CANT_HERIDOS_EN._SITIO_ACCIDENTE, tomando como atípicos los registros con más de 4 heridos en el accidente. Al realizar la misma observación en CANT_MUERTOS_EN._SITIO_ACCIDENTE se obtiene lo siguiente:

out <- boxplot.stats(be5$CANT_MUERTOS_EN._SITIO_ACCIDENTE)$out
out_ind <- which(be5$CANT_MUERTOS_EN._SITIO_ACCIDENTE %in% c(out))
datatable(be5s[out_ind, c(3,4,7)])

Aquí se puede observar que tomó como atípicos todos aquellos accidentes donde hubo más de un fallecido. Es curioso como la imputación realizada en be5s, al momento de hallarle los valores atípicos, se obtienen muchísimos más debido a la concentración que está generando en el cero de estas variables. Incluso, toma como atípicos en el caso de CANT_MUERTOS_EN._SITIO_ACCIDENTE a todos aquellos valores difentes de cero, es decir, los que originalmente se encontraban en el dataframe.

Desarrollo de la segunda parte

La base de datos analizada proviene del Sistema de Información de la Cadena de Distribución de Combustibles (SICOM), un sistema gestionado por el Ministerio de Minas y Energía. Este sistema integra a los diferentes agentes en la cadena de distribución de combustibles a nivel nacional, facilitando la gestión y sistematización de datos relacionados con la comercialización, distribución, transporte y almacenamiento de combustibles.

Importar dataframes:

t32019 <- read.csv("C:/Users/Valen/Downloads/precios_t32019.csv")
t32018 <- read.csv("C:/Users/Valen/Downloads/precios_t32018.csv")
t32021 <- read.csv("C:/Users/Valen/Downloads/precios_t32021.csv")
t32020 <- read.csv("C:/Users/Valen/Downloads/precios_t32020.csv")
t32022 <- read.csv("C:/Users/Valen/Downloads/precios_t32022.csv")
t32023 <- read.csv("C:/Users/Valen/Downloads/precios_t32023.csv")

t42019 <- read.csv("C:/Users/Valen/Downloads/precios_t42019.csv")
t42018 <- read.csv("C:/Users/Valen/Downloads/precios_t42018.csv")
t42021 <- read.csv("C:/Users/Valen/Downloads/precios_t42021.csv")
t42020 <- read.csv("C:/Users/Valen/Downloads/precios_t42020.csv")
t42022 <- read.csv("C:/Users/Valen/Downloads/precios_t42022.csv")
t42023 <- read.csv("C:/Users/Valen/Downloads/precios_t42023.csv")

t12019 <- read.csv("C:/Users/Valen/Downloads/precios_t12019.csv")
t12018 <- read.csv("C:/Users/Valen/Downloads/precios_t12018.csv")
t12021 <- read.csv("C:/Users/Valen/Downloads/precios_t12021.csv")
t12020 <- read.csv("C:/Users/Valen/Downloads/precios_t12020.csv")
t12022 <- read.csv("C:/Users/Valen/Downloads/precios _t12022.csv")
t12023 <- read.csv("C:/Users/Valen/Downloads/precios_t12023.csv")
t12024 <- read.csv("C:/Users/Valen/Downloads/precios_t12024.csv")

t22019 <- read.csv("C:/Users/Valen/Downloads/precios_t22019.csv")
t22018 <- read.csv("C:/Users/Valen/Downloads/precios_t22018.csv")
t22021 <- read.csv("C:/Users/Valen/Downloads/precios_t22021.csv")
t22020 <- read.csv("C:/Users/Valen/Downloads/precios_t22020.csv")
t22022 <- read.csv("C:/Users/Valen/Downloads/precios_t22022.csv")
t22023 <- read.csv("C:/Users/Valen/Downloads/precios_t22023.csv")
t22024 <- read.csv("C:/Users/Valen/Downloads/precios_t22024.csv")

Juntemos ahora los dataframes por años.

d2018 <- bind_rows(
  t32018,
  t42018,
  t12018,
  t22018
)

d2019 <- bind_rows(
  t32019,
  t42019,
  t12019,
  t22019
)


d2020 <- bind_rows(
  t32020,
  t42020,
  t12020,
  t22020
)

d2021 <- bind_rows(
  t32021,
  t42021,
  t12021,
  t22021
)

d2022 <- bind_rows(
  t32022,
  t42022,
  t12022,
  t22022
)

d2023 <- bind_rows(
  t32023,
  t42023,
  t12023,
  t22023
)

d2024 <- bind_rows(
  t12024,  
  t22024
)

d2018 <- d2018 %>% mutate(AÑO = 2018)
d2019 <- d2019 %>% mutate(AÑO = 2019)
d2020 <- d2020 %>% mutate(AÑO = 2020)
d2021 <- d2021 %>% mutate(AÑO = 2021)
d2022 <- d2022 %>% mutate(AÑO = 2022)
d2023 <- d2023 %>% mutate(AÑO = 2023)
d2024 <- d2024 %>% mutate(AÑO = 2024)

df <- bind_rows(d2018, d2019, d2020, d2021, d2022, d2023, d2024)
head(df)
  BANDERA                               NOMBRE.COMERCIAL       PRODUCTO
1  TERPEL ESTACION DE SERVICIO DISTRIBUIDORA EL PORVENIR         DIESEL
2  TERPEL ESTACION DE SERVICIO DISTRIBUIDORA EL PORVENIR GASOLINA MOTOR
3  TERPEL                   SERVICENTRO TERPEL ABEJORRAL GASOLINA MOTOR
4  TERPEL                   SERVICENTRO TERPEL ABEJORRAL         DIESEL
5  TERPEL          ESTACION DE SERVICIO TERPEL LA PALOMA GASOLINA MOTOR
6  PRIMAX          ESTACION DE SERVICIO PRIMAX AUTOPISTA GASOLINA MOTOR
  FECHA.REGISTRO DEPARTAMENTO MUNICIPIO VALOR.PRECIO  AÑO
1    01-Jul-2018     AMAZONAS   LETICIA         8550 2018
2    01-Jul-2018     AMAZONAS   LETICIA        11180 2018
3    01-Jul-2018    ANTIOQUIA ABEJORRAL        10150 2018
4    01-Jul-2018    ANTIOQUIA ABEJORRAL         9450 2018
5    01-Jul-2018    ANTIOQUIA     BELLO         8910 2018
6    01-Jul-2018    ANTIOQUIA     BELLO         9190 2018

Sobre los datos

Debido a la gran cantidad de datos, se limitó a trabajar con el dataframe del 2023.

La base de datos incluye un total de 267,943 registros, cada uno representando registros diarios relacionados a la venta combustibles. Las variables incluyen información sobre el producto, su precio, y su ubicación y fecha de registro.

names(d2023)
[1] "BANDERA"          "NOMBRE.COMERCIAL" "PRODUCTO"         "FECHA.REGISTRO"  
[5] "DEPARTAMENTO"     "MUNICIPIO"        "VALOR.PRECIO"     "AÑO"             

Esta consta de 9 variables, 8 categóricas y 1 numérica. La variable numérica corresponde a VALOR.PRECIO, una variable que contiene el precio del galón de gasolina el día de ese registro. `

summary(d2023$VALOR.PRECIO, na.rm = FALSE)
    Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
       0     9350    10880    12023    13849 14750147 
ggplot(d2023, aes(y = VALOR.PRECIO)) +
  geom_boxplot() +
  labs(title = "Precio del galón",
       y = "Precio",
       x = "") +
  theme_minimal()

Se destaca la presencia de muchos atípicos, con valor extremadamente altos como 14750147, el cual no tiene sentido para el precio de un galón de gasolina. Más adelante, se verán más a detalle estos valores.

En cuanto a las variables categóricas restantes, tenemos `BANDERA que hace referencia a la compañía que tiene la sede especificada en el registro. Esta está liderada por Terpel (35,39%), Primax (13,47%), Biomax (12,02%), Texaco (10,11%) y Petromil (6,43%).

kable((sort(prop.table(table(d2023$BANDERA))*100, decreasing = TRUE))) %>%
  kable_styling(
    full_width = FALSE, 
    bootstrap_options = c("striped", "hover"),
    position = "center",          
    font_size = 12,                
    fixed_thead = TRUE
  )
Var1 Freq
TERPEL 35.3892432
PRIMAX 13.4774933
BIOMAX 12.0268117
TEXACO 10.1111057
PETROMIL  6.4375632
COOMULPINORT 3.3283198
AYATAWACOOP 3.0069828
ZEUSS  2.7946242
PETROBRAS 1.8179240
DISCOWACOOP 1.4536674
ECOS 1.3484211
ESSO 1.3069944
PETRDECOL 1.3043819
PETRODECOL 1.2454141
PUMA 1.1416607
DISCOM 1.0886644
OCTANO 0.8289076
PLUS MAS 0.6740240
P Y B 0.6284919
BRIO  0.2683407
ZAPATA Y VELASQUEZ  0.1522712
SAVE 0.1063659
PROXXON 0.0623267

También se puede encontrar a la variable *NOMBRE.COMERCIAL* que indica el nombre de la estación de gasolina del registro. Esta cuenta con información sobre 5950 establecimientos en 911 municipios, guardados en la variable *MUNICIPIOS* de 32 departamentos, además de Bogotá D.C. como un departamento más representados en la variable `*DEPARTAMENTO*

length(unique(d2023$NOMBRE.COMERCIAL))
[1] 5950
length(unique(d2023$MUNICIPIO))
[1] 911
length(unique(d2023$DEPARTAMENTO))
[1] 33
kable((sort(prop.table(table(d2023$DEPARTAMENTO))*100, decreasing = TRUE))) %>%
  kable_styling(
    full_width = FALSE, 
    bootstrap_options = c("striped", "hover"),
    position = "center",          
    font_size = 12,                
    fixed_thead = TRUE
  )
Var1 Freq
NARIÑO 11.5897784
ANTIOQUIA 9.3337016
NORTE DE SANTANDER 8.1181445
VALLE DEL CAUCA 6.9992498
CUNDINAMARCA 6.2069171
BOGOTA D.C. 5.9829889
CESAR 5.8605748
LA GUAJIRA 5.0891421
SANTANDER 3.8243208
ATLANTICO 3.2077718
TOLIMA 2.9909346
BOYACA 2.8994973
CORDOBA 2.7767100
BOLIVAR 2.6225727
PUTUMAYO 2.4669426
HUILA 2.3717731
META 2.3325857
CAUCA 2.0963414
RISARALDA 2.0284165
MAGDALENA 1.9925880
CALDAS 1.6858063
SUCRE 1.6264653
CASANARE 1.0240984
CHOCO 0.9852842
QUINDIO 0.9535610
CAQUETA 0.8919808
ARAUCA 0.8628701
GUAVIARE 0.3892619
VICHADA 0.3441030
AMAZONAS 0.1914586
ARCHIPIELAGO DE SAN ANDRES, SANTA CATALINA Y PROVIDENCIA 0.0996481
GUAINIA 0.0974088
VAUPES 0.0571017

Los departamentos con mayor número de registros son Nariño (11,58%), Antioquia con un 9,3% seguido de Norte de Santander (8,11%). Departamentos como Vaupés, Guianía y Archipielago de San Andrés y Providencia representan menos del 0.1% cada uno.

kable((sort(table(d2023$MES), decreasing = TRUE))) %>%
  kable_styling(
    full_width = FALSE, 
    bootstrap_options = c("striped", "hover"),
    position = "center",          
    font_size = 12,                
    fixed_thead = TRUE
  )
x
NA
--:

El número de registros por mes no se mantuvo constante, siendo junio el mes con más registros (26887) y julio (18453) el mes con menos registros, más adelante veremos mejor el comportamiento de los datos según el mes.

d2023 <- d2023 %>%
  mutate(MES = month(dmy(FECHA.REGISTRO), label = TRUE))

resumen_precios <- d2023 %>%
  group_by(BANDERA, MES) %>%
  summarise(PROMEDIO_PRECIO = mean(VALOR.PRECIO, na.rm = TRUE), .groups = 'drop')

ggplot(resumen_precios, aes(x = MES, y = PROMEDIO_PRECIO, color = BANDERA, group = BANDERA)) +
  geom_line() +
  geom_point() +
  labs(title = "Precio Promedio por Mes y Bandera",
       x = "Mes",
       y = "Precio Promedio",
       color = "Bandera") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

Según un artículo publicado en septiembre del 2022 en portafolio.co, en Colombia, más del 90 % de la torta en la distribución de combustibles en el territorio nacional sigue concentrada en cinco grandes compañías: Terpel (42,6%), Primax (20,2%), Biomax (8,7%), Chevron (Texaco 10,7%) y Petromil (4,4%). Enfoquémonos en las principales 5 empresas.

d2023 <- d2023 %>%
  mutate(BANDERA = str_squish(BANDERA))

resumen_precios <- d2023 %>%
  filter(BANDERA %in% c("TERPEL", "PRIMAX", "BIOMAX", "TEXACO", "PETROMIL")) %>%
  group_by(BANDERA, MES) %>%
  summarise(PROMEDIO_PRECIO = mean(VALOR.PRECIO, na.rm = TRUE), .groups = 'drop')

ggplot(resumen_precios, aes(x = MES, y = PROMEDIO_PRECIO, color = BANDERA, group = BANDERA)) +
  geom_line() +
  geom_point() +
  labs(title = "Precio Promedio por Mes y Bandera",
       x = "Mes",
       y = "Precio Promedio",
       color = "Bandera") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

Se puede notar como en el transcurso del año, se mantiene un precio promedio similar en todas las compañías gasolineras, sin embargo en septiembre hubo un disparo en el precio de BIOMAX en comparación a las demás empresas. También se puede notar que en general PRIMAX maneja un precio más alto, seguido de TEXACO, luego TERPEL y por últim BIOMAX.

d2023_mensual <- d2023 %>%
  group_by(MES) %>%
  summarise(PRECIO_PROMEDIO = mean(VALOR.PRECIO, na.rm = TRUE))

ggplot(d2023, aes(x = MES, y = VALOR.PRECIO)) +
  geom_boxplot() +
  labs(title = "Distribución del Precio por Mes",
       x = "Mes",
       y = "Precio") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

Se pueden observar muchísimos datos atípicos, pero el que más llama la atención es el que se encuentra en septiembre, el que se vio cuando se analizaron las variables individualmente como un valor muy exagerado. Veamos entonces todos los posibles valores atípicos que tiene nuestra base de datos.

out <- boxplot.stats(d2023$VALOR.PRECIO)$out
out_ind <- which(d2023$VALOR.PRECIO %in% c(out))

datatable(d2023[out_ind, ])

Se puede observar una gran cantidad de datos atípicos (2907) tanto por arriba como por debajo, siendo 14750147 el precio que sobresalía tanto en la distribución, correspondiente a EDS DOÑA DANIELA en Bolívar. Lo más seguro es que la existencia de estos valores muy lejanos sean debido a errores al momento de digitar por tanto se realizará imputación de datos,

lb <- quantile(d2023$VALOR.PRECIO, 0.025)
up <- quantile(d2023$VALOR.PRECIO, 0.975)
outlier_ind <- which(d2023$VALOR.PRECIO < lb | d2023$VALOR.PRECIO > up)

d2023$VALOR.PRECIO[outlier_ind] <- median(d2023$VALOR.PRECIO, na.rm = TRUE)

summary(d2023)
   BANDERA          NOMBRE.COMERCIAL     PRODUCTO         FECHA.REGISTRO    
 Length:267943      Length:267943      Length:267943      Length:267943     
 Class :character   Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character  
                                                                            
                                                                            
                                                                            
                                                                            
 DEPARTAMENTO        MUNICIPIO          VALOR.PRECIO        AÑO      
 Length:267943      Length:267943      Min.   : 7550   Min.   :2023  
 Class :character   Class :character   1st Qu.: 9410   1st Qu.:2023  
 Mode  :character   Mode  :character   Median :10880   Median :2023  
                                       Mean   :11806   Mean   :2023  
                                       3rd Qu.:13589   3rd Qu.:2023  
                                       Max.   :20120   Max.   :2023  
                                                                     
      MES        
 jun    : 26887  
 nov    : 25872  
 may    : 25590  
 ene    : 22369  
 feb    : 22206  
 mar    : 22125  
 (Other):122894  
ggplot(d2023, aes(x = VALOR.PRECIO)) +
  geom_histogram(binwidth = 1000, fill = "blue", color = "black") +
  labs(title = "Distribución del Precio del Galón",
       x = "Precio",
       y = "Frecuencia") +
  theme_minimal()

ggplot(d2023, aes(y = VALOR.PRECIO)) +
  geom_boxplot() +
  labs(title = "Precio del galón",
       y = "Precio",
       x = "") +
  theme_minimal()

d2023 <- d2023 %>%
  mutate(BANDERA = str_squish(BANDERA))

resumen_precios <- d2023 %>%
  filter(BANDERA %in% c("TERPEL", "PRIMAX", "BIOMAX", "TEXACO", "PETROMIL")) %>%
  group_by(BANDERA, MES) %>%
  summarise(PROMEDIO_PRECIO = mean(VALOR.PRECIO, na.rm = TRUE), .groups = 'drop')

ggplot(resumen_precios, aes(x = MES, y = PROMEDIO_PRECIO, color = BANDERA, group = BANDERA)) +
  geom_line() +
  geom_point() +
  labs(title = "Precio Promedio por Mes y Bandera",
       x = "Mes",
       y = "Precio Promedio",
       color = "Bandera") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

En comparación a la gráfica realizada anteriormente con la base de datos sin imputar, podemos notar la gran diferencia con el pico que se apreciaba en el mes de septiembre puesto que ahí estaba el outlier más alejado por mucho.

Veamos ahora la distribución del precio promedio a nivel departamental.Por motivos estéticos se realizó con ggplot para evitar afectaciones en el formato del quarto. Se dejan los códigos correspondientes al mapa con leaf

Creación del mapa

api_key <- 'AIzaSyATpl4evKr2bgAOUhDrGz81NW7LeOfD9jY'
register_google(key = api_key)

mapa_base <- get_googlemap("Colombia",
                           maptype = "hybrid",
                           scale = 2,
                           zoom = 7)
ℹ <https://maps.googleapis.com/maps/api/staticmap?center=Colombia&zoom=7&size=640x640&scale=2&maptype=hybrid&key=xxx>
ℹ <https://maps.googleapis.com/maps/api/geocode/json?address=Colombia&key=xxx>
mapa_col <- st_read("C:/Users/Valen/Downloads/COLOMBIA.shp", quiet = FALSE)
Reading layer `COLOMBIA' from data source `C:\Users\Valen\Downloads\COLOMBIA.shp' using driver `ESRI Shapefile'
Simple feature collection with 33 features and 11 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -81.73575 ymin: -4.227907 xmax: -66.84735 ymax: 13.39453
Geodetic CRS:  WGS 84
mapa_col <- st_make_valid(mapa_col) 
departamentos_shapefile <- unique(mapa_col$DPTO_CNMBR)
departamentos_dataframe <- unique(d2023$DEPARTAMENTO)

departamentos_no_en_dataframe <- setdiff(departamentos_shapefile, departamentos_dataframe)
departamentos_no_en_shapefile <- setdiff(departamentos_dataframe, departamentos_shapefile)
mapa_col$DPTO_CNMBR <- str_replace_all(mapa_col$DPTO_CNMBR, "\\?", "Ñ")
mapa_col$DPTO_CNMBR <- str_replace_all(mapa_col$DPTO_CNMBR, "   
ARCHIPIELAGO DE SAN ANDRES, SANTA CATALINA Y PROVIDENCIA", "ARCHIPIELAGO DE SAN ANDRES")

mapa_col <- mapa_col %>% 
  rename(DEPARTAMENTO = DPTO_CNMBR)
precios_promedio <- d2023 %>%
  group_by(DEPARTAMENTO) %>%
  summarise(promedio = mean(VALOR.PRECIO, na.rm = TRUE))

datos_unidos <- merge(mapa_col, precios_promedio, by = "DEPARTAMENTO", all = TRUE, sort = FALSE)

datos_unidos_sf <- st_as_sf(datos_unidos)

datos_unidos_sf <- st_simplify(datos_unidos_sf, dTolerance = 0.001)
ggplot(data = datos_unidos_sf) +
  geom_sf(aes(fill = promedio), color = "white", size = 0.2) +
  scale_fill_distiller(palette = "Spectral", name = "Precio Promedio", direction = 1) + 
  geom_text(aes(geometry = geometry, label = round(promedio, 2)), size = 2, color = "black", stat = "sf_coordinates") +
  labs(title = "Precio promedio del galón por departamento") +
  theme_minimal() +
  theme(
    plot.title = element_text(hjust = 0.5),
    axis.title = element_blank(),
    axis.text = element_blank(),
    axis.ticks = element_blank()
  )

# Error in `to_ring.default()`: ! Don't know how to get polygon data from object of class XY,GEOMETRYCOLLECTION,sfg
leaflet(datos_unidos_sf) %>%
  addProviderTiles(providers$CartoDB.Positron) %>%  
  addPolygons(
    fillColor = ~colorNumeric(palette = "Spectral", domain = datos_unidos_sf$promedio)(promedio),
    weight = 1,
    opacity = 1,
    color = 'black', 
    fillOpacity = 0.7,
    label = ~paste0(DEPARTAMENTO, ": ", round(promedio, 2)), 
    labelOptions = labelOptions(
      style = list("font-weight" = "normal", padding = "3px 8px"),
      textsize = "15px",
      direction = "auto"
    )
  ) %>%
  addLegend(
    pal = colorNumeric(palette = "Spectral", domain = datos_unidos_sf$promedio),
    values = ~promedio,
    opacity = 0.7,
    title = "Precio Promedio",
    position = "bottomright"
  )