Ejercicios - Visualización de datos con R

Paquetes y librerias necesarias.

library(tidyr)
library(dplyr)
library(readr)
library(ggplot2)
library(gridExtra)
library(mice)
library(knitr)
library(VIM)
library(DT)
library(naniar)
library(minqa)
library(tidyverse)
library(hrbrthemes)
library(missForest)
library(outliers)
library(EnvStats)
library(leaflet)
library(mapview)
library(stringr)
library(sf)
library(Amelia)
library(ggrepel)
library(editrules)

Ejercicio 3

Instrucciones

  1. Importe la base de datos.

  2. Analice 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 otras.

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

  4. Filtre la base de datos para entender mejor su estructura.

  5. Explore la ayuda de la función table del paquete base y utilícela 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. Decida qué hacer con los valores NA.

Base de datos

library(DT)
data = read_csv("master.csv")
attach(data)
datatable(
  data[1:100, ],
  caption = "Base de datos: Suicide Rates Overview 1985 to 2016",
  options = list(
    scrollX = TRUE,
    scrollY = "450px"
  )
)

Variables

  • suicides/100k: suicidios por cada 100 mil habitantes (tasas de suicidio).

  • country: país.

  • year: año.

  • sex: género (male, female).

  • age: edad (grupo de edad).

  • suicides_no: número de suicidios.

  • population: población.

  • country-year: clave compuesta país-año.

  • HDI: índice de desarrollo humano (IDH) por año.

  • gdp_for_year ($): producto interno bruto (PIB) por año.

  • gdp_per_capita: producto interno bruto per capita.

  • generation: generación.

Características de la base de datos

Antes de profundizar en el análisis de un conjunto de datos, es fundamental comprender su estructura y características clave. Para ello, se utilizan las siguientes funciones que permiten explorar y analizar la base de datos de manera efectiva.

Inicialmente, names() nos permite conocer el nombre de cada variable involucrada.

names(data)
 [1] "country"            "year"               "sex"               
 [4] "age"                "suicides_no"        "population"        
 [7] "suicides/100k pop"  "country-year"       "HDI for year"      
[10] "gdp_for_year ($)"   "gdp_per_capita ($)" "generation"        

Con dim() podemos identificar la cantidad de observaciones (filas) y variables (columnas) que tiene la base de datos.

dim(data)
[1] 27820    12

Esta base de datos contiene 27820 observaciones y 12 variables.

Como complemento a las funciones mencionadas anteriormente, la función str() nos permite identificar los tipos de datos en el conjunto. Con esta función, podemos visualizar las diferentes variables y determinar si son categóricas o numéricas.

str(data)
spc_tbl_ [27,820 × 12] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
 $ country           : chr [1:27820] "Albania" "Albania" "Albania" "Albania" ...
 $ year              : num [1:27820] 1987 1987 1987 1987 1987 ...
 $ sex               : chr [1:27820] "male" "male" "female" "male" ...
 $ age               : chr [1:27820] "15-24 years" "35-54 years" "15-24 years" "75+ years" ...
 $ suicides_no       : num [1:27820] 21 16 14 1 9 1 6 4 1 0 ...
 $ population        : num [1:27820] 312900 308000 289700 21800 274300 ...
 $ suicides/100k pop : num [1:27820] 6.71 5.19 4.83 4.59 3.28 2.81 2.15 1.56 0.73 0 ...
 $ country-year      : chr [1:27820] "Albania1987" "Albania1987" "Albania1987" "Albania1987" ...
 $ HDI for year      : num [1:27820] NA NA NA NA NA NA NA NA NA NA ...
 $ gdp_for_year ($)  : num [1:27820] 2.16e+09 2.16e+09 2.16e+09 2.16e+09 2.16e+09 ...
 $ gdp_per_capita ($): num [1:27820] 796 796 796 796 796 796 796 796 796 796 ...
 $ generation        : chr [1:27820] "Generation X" "Silent" "Generation X" "G.I. Generation" ...
 - attr(*, "spec")=
  .. cols(
  ..   country = col_character(),
  ..   year = col_double(),
  ..   sex = col_character(),
  ..   age = col_character(),
  ..   suicides_no = col_double(),
  ..   population = col_double(),
  ..   `suicides/100k pop` = col_double(),
  ..   `country-year` = col_character(),
  ..   `HDI for year` = col_double(),
  ..   `gdp_for_year ($)` = col_number(),
  ..   `gdp_per_capita ($)` = col_double(),
  ..   generation = col_character()
  .. )
 - attr(*, "problems")=<externalptr> 

Finalmente, con la función summary() obtenemos un análisis general de los estadísticos de cada variable, como el conteo, la media, la mediana, la moda, entre otros.

summary(data)
   country               year          sex                age           
 Length:27820       Min.   :1985   Length:27820       Length:27820      
 Class :character   1st Qu.:1995   Class :character   Class :character  
 Mode  :character   Median :2002   Mode  :character   Mode  :character  
                    Mean   :2001                                        
                    3rd Qu.:2008                                        
                    Max.   :2016                                        
                                                                        
  suicides_no        population       suicides/100k pop country-year      
 Min.   :    0.0   Min.   :     278   Min.   :  0.00    Length:27820      
 1st Qu.:    3.0   1st Qu.:   97498   1st Qu.:  0.92    Class :character  
 Median :   25.0   Median :  430150   Median :  5.99    Mode  :character  
 Mean   :  242.6   Mean   : 1844794   Mean   : 12.82                      
 3rd Qu.:  131.0   3rd Qu.: 1486143   3rd Qu.: 16.62                      
 Max.   :22338.0   Max.   :43805214   Max.   :224.97                      
                                                                          
  HDI for year   gdp_for_year ($)    gdp_per_capita ($)  generation       
 Min.   :0.483   Min.   :4.692e+07   Min.   :   251     Length:27820      
 1st Qu.:0.713   1st Qu.:8.985e+09   1st Qu.:  3447     Class :character  
 Median :0.779   Median :4.811e+10   Median :  9372     Mode  :character  
 Mean   :0.777   Mean   :4.456e+11   Mean   : 16866                       
 3rd Qu.:0.855   3rd Qu.:2.602e+11   3rd Qu.: 24874                       
 Max.   :0.944   Max.   :1.812e+13   Max.   :126352                       
 NA's   :19456                                                            

Análisis de las variables

Variables categóricas

Las variables categóricas son: country, sex, age, country-year, generation.

Variable country

paises <- transform(table(country), Rel_Freq = prop.table(Freq), Cum_Freq = cumsum(Freq))
knitr::kable(head(paises))
country Freq Rel_Freq Cum_Freq
Albania 264 0.0094896 264
Antigua and Barbuda 324 0.0116463 588
Argentina 372 0.0133717 960
Armenia 298 0.0107117 1258
Aruba 168 0.0060388 1426
Australia 360 0.0129403 1786

Variable sex

table(data$sex)

female   male 
 13910  13910 
data$sex <- factor(data$sex, 
                   levels = c("female", "male"), 
                   labels = c("Femenino", "Masculino"))
ggplot(data = data, aes(x = sex)) +
  geom_bar(color = "#104E8B", fill = "#87CEFA") +
  labs(title = "Distribución del sexo", x = "Sexo", y = "Frecuencia") + 
  theme_minimal()

Variable generation

table(generation)
generation
        Boomers G.I. Generation    Generation X    Generation Z      Millenials 
           4990            2744            6408            1470            5844 
         Silent 
           6364 
ggplot(data = data, aes(x = generation)) +
  geom_bar(color = "#104E8B", fill = "#87CEFA") +
  labs(title = "Distribución de la generación", x = "Generación", y = "Frecuencia") + 
  theme_minimal()

Variable age

data$age<-str_replace_all(age,'years','' )
data$age <- as.factor(data$age)
ggplot(data = data, aes(x = age)) +
  geom_bar(color = "#104E8B", fill = "#87CEFA") +
  labs(title = "Distribución de la edad", x = "Edad", y = "Frecuencia") + 
  theme_minimal()

edades <- transform(table(age), Rel_Freq = prop.table(Freq), Cum_Freq = cumsum(Freq))
knitr::kable(edades)
age Freq Rel_Freq Cum_Freq
15-24 years 4642 0.1668584 4642
25-34 years 4642 0.1668584 9284
35-54 years 4642 0.1668584 13926
5-14 years 4610 0.1657081 18536
55-74 years 4642 0.1668584 23178
75+ years 4642 0.1668584 27820

Variables numéricas

Las variables numéricas son: year, suicides_no, population, suicides/100k pop, HDI for year, gdp_for_year, gdp_per_capita

Variable year

Dado que la variable yearpresenta una amplia variedad de observaciones, resulta útil agrupar estos datos en intervalos y calcular las estadísticas correspondientes. Para ello, se utilizará la regla de Sturges, que permite determinar el número óptimo de intervalos y clasificar las observaciones en consecuencia. Posteriormente, se graficará un diagrama de barras y una tabla de frecuencias que represente la distribución de la variable year en la base de datos analizada.

n_sturges = 1 + log(length(data))/log(2) # Regla de sturges
# Divide las clases dependiendo el tamaño de la muestra
n_sturgesc = ceiling(n_sturges) # Redondea el numero de intervalos hacia arriba
n_sturgesf = floor(n_sturges) # Redondea hacia abajo

n_clases = 0 # Inicia el número de clases en 0 
# Determina el número de intervalos dependiendo si es par o impar
if (n_sturgesc%%2 == 0) {
  n_clases = n_sturgesf
} else {
  n_clases = n_sturgesc
}

R = max(data$year) - min(data$year) # Rango
w = ceiling(R/n_clases) # Ancho de los intervalos

bins <- seq(min(data$year), max(data$year) + w, by = w) # Límites de los intervalos
años <- cut(data$year, bins) # Clasifica los años basados en los límites
frecuencia <- transform(table(años), Rel_Freq = prop.table(Freq), Cum_Freq = cumsum(Freq))
knitr::kable(frecuencia)
años Freq Rel_Freq Cum_Freq
(1985,1992] 4752 0.1744237 4752
(1992,1999] 6324 0.2321245 11076
(1999,2006] 7188 0.2638379 18264
(2006,2013] 7140 0.2620761 25404
(2013,2020] 1840 0.0675378 27244
df2 <- data.frame(x = frecuencia$años, y = frecuencia$Freq)
ggplot(data=df2, aes(x = x, y = y)) +
  geom_bar(stat="identity", color = "#104E8B", fill = "#87CEFA") +
  labs(title = "Distribución de los años", x = "Rango de años", y = "Frecuencia") + theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
  theme_minimal()

Variable suicides_no

summary(suicides_no)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
    0.0     3.0    25.0   242.6   131.0 22338.0 

Variable population

summary(population)
    Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
     278    97498   430150  1844794  1486143 43805214 

Variable suicides/100k pop

summary(`suicides/100k pop`)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   0.00    0.92    5.99   12.82   16.62  224.97 

Variable HDI for year

summary(`HDI for year`)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
  0.483   0.713   0.779   0.777   0.855   0.944   19456 

Variable gdp_for_year

summary(`gdp_for_year ($)`)
     Min.   1st Qu.    Median      Mean   3rd Qu.      Max. 
4.692e+07 8.985e+09 4.811e+10 4.456e+11 2.602e+11 1.812e+13 

Variable gdp_per_capita

summary(`gdp_per_capita ($)`)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
    251    3447    9372   16866   24874  126352 

Filtración de la base de datos

Para comprender mejor la estructura de la base de datos, aplicaremos un filtro utilizando la función filter(), seleccionando únicamente las observaciones correspondientes al año 2011. A continuación, se mostrarán las primeras 5 observaciones de este subconjunto.

data_2011 <- data %>% filter(year == 2011)
datatable(
  data_2011[1:100, ],
  options = list(
    scrollX = TRUE,
    scrollY = "450px"
  )
)

Identificación de NA.

En una base de datos, es crucial identificar y abordar cualquier problema que pueda surgir. Uno de los problemas más comunes es la presencia de valores faltantes, conocidos como NA. En este punto, nos enfocaremos en identificar y analizar estos valores faltantes para comprender mejor su impacto en el conjunto de datos. Inicialmente, se hace uso de la función colSums(is.na()) para identificar en que columnas (variables) hay datos faltantes. Luego con ayuda de un missmap visualizaremos la proporción de estos datos faltantes en la base de datos.

colSums(is.na(data))
           country               year                sex                age 
                 0                  0                  0                  0 
       suicides_no         population  suicides/100k pop       country-year 
                 0                  0                  0                  0 
      HDI for year   gdp_for_year ($) gdp_per_capita ($)         generation 
             19456                  0                  0                  0 
missmap(data, col = c("#C1FFC1", "#BF3EFF"), legend = TRUE)
Warning: Unknown or uninitialised column: `arguments`.
Unknown or uninitialised column: `arguments`.
Warning: Unknown or uninitialised column: `imputations`.

aggr(data, numbers = TRUE, col = c("#C1FFC1", "#BF3EFF"), 
     cex.axis = 0.7, gap = 3, ylab = c("Proporción de datos faltantes", "Patrón de datos faltantes"))

Se puede afirmar que existe una presencia significativa de datos faltantes en la base de datos, particularmente en la variable “HDI for year”. De hecho, aproximadamente el 70% de los datos en esta variable están ausentes, lo cual representa una proporción considerable y relevante.

Tratamiento para los NA

Para poder realizar un tratamiento adecuado a los datos faltantes, es importante verificar si la variable HDI for year sigue una distribución normal. Inicialmente, vemos graficamente como es la distribución de esta variable. Seguido a ello, eliminamos cualquier valor faltante en esta variable utilizando la función na.omit(). Luego, aplicamos la prueba de Kolmogorov-Smirnov (ks.test) para comparar la distribución de “HDI for year” con una distribución normal.

ggplot(data, aes(x = `HDI for year`)) + 
  geom_histogram(binwidth = 0.05, color = "#104E8B", fill = "#C1FFC1") + 
  labs(title = "Histograma del índice de desarrollo humano (HDI)",
       x = "Índice de desarrollo humano",
       y = "Frecuencia") +
  theme_minimal()

HDI<-na.omit(data$`HDI for year`)
ks.test(HDI, "pnorm", mean = mean(HDI), sd = sd(HDI)) # Prueba de normalidad de la variable

    Asymptotic one-sample Kolmogorov-Smirnov test

data:  HDI
D = 0.055914, p-value < 2.2e-16
alternative hypothesis: two-sided

El histograma sugiere una asimetría hacia la derecha, lo que indica una distribución sesgada. Además el resultado de la prueba de normal permite afirmar que la variable no se distribuye normalmente, ya que el p-valor obtenido es menor que cualquier nivel de significancia. Por lo tanto, utilizaremos la mediana para realizar la imputación de los datos faltantes.

mediana = median(`HDI for year`, na.rm = TRUE) # Se usará la mediana dada la anormalidad de los datos
data$`HDI for year` <- replace_na(data$`HDI for year`, mediana)
suppressWarnings(missmap(data, col = c("#C1FFC1", "#BF3EFF"))) 

Para abordar la anormalidad en la distribución de la variable “HDI for year”, calculamos la mediana de esta variable utilizando median(), omitiendo los valores faltantes con na.rm = TRUE. Luego, imputamos los valores faltantes en la variable “HDI for year” reemplazándolos por la mediana calculada mediante la función replace_na(). Finalmente, usamos missmap() para visualizar la distribución de los datos faltantes en la base de datos después de la imputación.

ks.test(data$`HDI for year`, "pnorm", mean = mean(data$`HDI for year`), sd = sd(data$`HDI for year`))

    Asymptotic one-sample Kolmogorov-Smirnov test

data:  data$`HDI for year`
D = 0.35594, p-value < 2.2e-16
alternative hypothesis: two-sided

Es crucial asegurarse de que el proceso de imputación de datos no modifique la distribución original de los mismos. Por esta razón, se realiza una nueva prueba de normalidad, y los resultados confirman que la distribución de los datos permanece igual, es decir, sigue sin ajustarse a una distribución normal.

Valores atípicos

Por otro lado, es crucial identificar los valores atípicos en un conjunto de datos, ya que estos representan observaciones que se desvían significativamente de lo común y pueden influir en el análisis. Para explorar la existencia de valores atípicos, utilizaremos un diagrama de caja (boxplot). Este método es particularmente útil para las variables numéricas, ya que nos permite visualizar de manera clara los valores que se encuentran fuera de los rangos típicos.

par(mfrow=c(1,2))
boxplot(suicides_no, main="Número de suicidios", col = "#BFEFFF")
boxplot(population,main="Población", col = "#BFEFFF")

par(mfrow=c(1,2))
boxplot(`suicides/100k pop`,main="Suicidio por cada 100mil habitantes", col = "#BFEFFF")
boxplot(`HDI for year`,main="Índice de desarrollo humano por año", col = "#BFEFFF")

par(mfrow=c(1,2))
boxplot(`gdp_for_year ($)`,main="Producto interno bruto por año", col = "#BFEFFF")
boxplot(`gdp_per_capita ($)`,main="Producto interno bruto per capita", col = "#BFEFFF")

Es posible observar una cantidad significativa de datos atípicos en las diferentes variables, lo cual puede deberse a la diversidad presente en los datos. Por ejemplo, en el caso de la población, cada país tiene características distintas; algunos países tienen una población mucho mayor que otros, lo que puede resultar en valores atípicos en las variables relacionadas con la población.

Ejercicio 4

Instrucciones

Considere la base de datos master y realice lo siguiente (utilice los operadores pipe de continuidad y compuesto):

  1. Edite y explore reglas para verificar que la base de datos no contenga posibles registros erróneos.

  2. Filtre los datos de Colombia y los de EEUU, generando dos bases de datos, llamadas master_col y master_eu.

  3. Realice un análisis de la evolución de los suicidios por cada 100.000 habitantes, del PIB per cápita y del IDH, a lo largo de los años en ambos países.

  4. Realice un análisis de la evolución de los suicidios por cada 100.000 habitantes, del PIB per cápita y del IDH, a lo largo de los años en ambos países por género.

  5. Realice un análisis de la evolución de los suicidios por cada 100.000 habitantes, del PIB per cápita y del IDH, a lo largo de los años en ambos países por grupo de edad.

Exploración de reglas

Para asegurarse de que la base de datos no contenga registros erróneos, se definen y exploran una serie de reglas de validación. Las reglas establecidas son:

  1. "age > 0": La edad debe ser mayor que 0.

  2. "suicides_no >= 0": El número de suicidios debe ser mayor o igual a 0.

  3. "generation %in% c('Boomers', 'G.I. Generation', 'Generation X', 'Generation Z', 'Millennials', 'Silent')": La generación debe estar dentro del conjunto especificado de valores válidos.

Se utiliza la función editset() para aplicar estas reglas al conjunto de datos y luego se verifican los registros que violan estas reglas con violatedEdits().

 reglas <-c("age > 0",
 "suicides_no >= 0",
 "generation%in%c('Boomers','G.I. Generation','Generation X','Generation Z','Millenials', 'Silent')")
 Condiciones<-editset(reglas)
 Condiciones

Data model:
dat1 : generation %in% c('Boomers', 'G.I. Generation', 'Generation X', 'Generation Z', 'Millenials', 'Silent') 

Edit set:
num1 : 0 < age
num2 : 0 <= suicides_no 

A continuación, se muestran las primeras 10 filas de los errores encontrados con Errores[1:10, ] y se grafican los resultados con plot(Errores) para proporcionar una visualización más clara de los problemas en los datos.

Errores <- violatedEdits(c(Condiciones), data)
Warning in Ops.factor(0, age): '<' no es significativo para factores
Errores[1:10, ]  # Primeras 10 filas
      edit
record num1  num2  dat1
    1    NA FALSE FALSE
    2    NA FALSE FALSE
    3    NA FALSE FALSE
    4    NA FALSE FALSE
    5    NA FALSE FALSE
    6    NA FALSE FALSE
    7    NA FALSE FALSE
    8    NA FALSE FALSE
    9    NA FALSE FALSE
    10   NA FALSE FALSE
plot(Errores)

Filtración de datos

Se filtran los datos para crear dos bases de datos: master_col para Colombia y master_eu para Estados Unidos. Se usa la función filter() para seleccionar los registros correspondientes a cada país y se muestran las primeras filas de ambos filtros.

# Filtrar datos para Colombia y EE. UU.
master_col <- data %>%
  filter(country == "Colombia")
datatable(
  master_col[1:10, ],
  options = list(
    scrollX = TRUE,
    scrollY = "450px"
  )
)
master_eu <- data %>%
  filter(country == "United States")
datatable(
  master_eu[1:10, ],
  options = list(
    scrollX = TRUE,
    scrollY = "450px"
  )
)

Análisis

Análisis en ambos países

Análisis de la evolución de los suicidios por cada 100.000 habitantes, del PIB per cápita y del IDH, a lo largo de los años en ambos países.

colyeu <- bind_rows(
  master_col %>% mutate(country = "Colombia"),
  master_eu %>% mutate(country = "EE. UU.")
) # Une y añade los nombres (en una nueva columna para identificar) para identificar
color_palette <- c("Colombia" = "#8B6914", "EE. UU." = "#1874CD")

suicidios <- colyeu %>%
  group_by(year, country) %>%
  summarize(mean_suicidios = mean(`suicides/100k pop`, na.rm = TRUE), .groups = 'drop') %>% # : Elimina los agrupamientos adicionales después de la operación de resumen
  ggplot(aes(x = year, y = mean_suicidios, color = country, group = country)) + geom_line() + labs( title = "Evolución de los Suicidios por Cada 100,000 Habitantes",
    x = "Año", y = "Suicidios por Cada 100,000 Habitantes", color = "País"
  ) + theme_minimal() +  scale_color_manual(values = color_palette)

grid.arrange(suicidios)

La gráfica muestra que la tasa de suicidios por cada 100,000 habitantes ha sido consistentemente más alta en EE. UU. que en Colombia desde 1985. En Estados Unidos, la tasa disminuyó hasta 2000 y luego aumentó ligeramente hasta 2015. En Colombia, la tasa fue más estable, con un notable pico alrededor del año 2000, seguido de una disminución y una leve tendencia al alza en los últimos años.

pib <- colyeu %>%
  group_by(year, country) %>%
  summarize(mean_gdp = mean(`gdp_per_capita ($)`, na.rm = TRUE), .groups = 'drop') %>%
  ggplot(aes(x = year, y = mean_gdp, color = country, group = country)) +
  geom_line() +
  scale_color_manual(values = color_palette) +
  labs(title = "Evolución del PIB per Cápita", x = "Año", y = "PIB per Cápita", color = "País") + theme_minimal()
grid.arrange(pib)

La gráfica del PIB per cápita muestra un crecimiento constante y pronunciado en EE. UU. desde 1985 hasta 2015, alcanzando niveles significativamente más altos que en Colombia, cuyo PIB per cápita presenta un aumento lento y estable. Esta diferencia refleja la disparidad económica entre ambos países, con EE. UU. experimentando un crecimiento sostenido en su riqueza per cápita, mientras que Colombia mantiene un nivel considerablemente más bajo.

idh <- colyeu %>%
  group_by(year, country) %>%
  summarize(mean_idh = mean(`HDI for year`, na.rm = TRUE), .groups = 'drop') %>%
  ggplot(aes(x = year, y = mean_idh, color = country, group = country)) +
  geom_line() + scale_color_manual(values = color_palette) +
  labs(title = "Evolución del Índice de Desarrollo Humano", x = "Año", y = "IDH", color = "País"
  ) +
  theme_minimal()
grid.arrange(idh)

La gráfica del índice de desarrollo humano indica que EE. UU. ha mantenido consistentemente un nivel más alto de desarrollo humano en comparación con Colombia durante todo el período analizado. Aunque ambos países han mostrado mejoras en su IDH, Colombia tiene variaciones notables en sus datos, particularmente antes de 2000, mientras que EE. UU. muestra una evolución más estable. A partir de 2010, Colombia experimenta un incremento sostenido en su IDH, acortando ligeramente la brecha con EE. UU.

Análisis en ambos países por género

Análisis de la evolución de los suicidios por cada 100.000 habitantes, del PIB per cápita y del IDH, a lo largo de los años en ambos países por género.

plot_col_s <- master_col %>%
  group_by(year, sex) %>%
  summarize(mean_suicides = mean(`suicides/100k pop`, na.rm = TRUE), .groups = 'drop') %>%
  ggplot(aes(x = year, y = mean_suicides, group = sex, color = sex)) +
  geom_line() +
  labs(title = "Colombia",
       x = "Año",
       y = "Suicidios por Cada 100,000 Habitantes") +
  theme_minimal()

plot_eu_s <- master_eu %>%
  group_by(year, sex) %>%
  summarize(mean_suicides = mean(`suicides/100k pop`, na.rm = TRUE), .groups = 'drop') %>%
  ggplot(aes(x = year, y = mean_suicides, group = sex, color = sex)) +
  geom_line() +
  labs(title = "Estados Unidos",
       x = "Año",
       y = "Suicidios por Cada 100,000 Habitantes") +
  theme_minimal()

grid.arrange(plot_col_s, plot_eu_s, ncol = 2,
  top = "Evolución de los suicidios por género")

Las gráficas muestran la evolución de la tasa de suicidios por género en Colombia y Estados Unidos.

  • Colombia: La tasa de suicidios en hombres es significativamente más alta que en mujeres, con picos alrededor del año 2000, alcanzando más de 10 suicidios por cada 100,000 habitantes. Las mujeres presentan una tasa mucho más baja y estable, sin superar los 3 suicidios por cada 100,000 habitantes.

  • Estados Unidos: Similar a Colombia, la tasa de suicidios en hombres es considerablemente más alta que en mujeres. Sin embargo, la tendencia en hombres muestra una disminución hasta el año 2000, seguida de un aumento, mientras que la tasa en mujeres permanece baja y estable, en torno a los 5 suicidios por cada 100,000 habitantes.

NOTA:

Las gráficas del PIB e IDH presentan una particularidad: al no poder desglosarse por género o edad, debido a su naturaleza macroeconómica, se muestran como una única serie temporal. Esto significa que los datos de estos indicadores reflejan la evolución económica general de un país a lo largo de los años, sin distinción entre grupos poblacionales específicos. Esta característica permite analizar las tendencias generales del crecimiento económico y el desarrollo humano, pero limita la posibilidad de identificar disparidades entre diferentes segmentos de la población.

plot_col_gdp <- master_col %>%
  group_by(year, sex) %>%
  summarize(mean_GDP = mean(`gdp_per_capita ($)`, na.rm = TRUE), .groups = 'drop') %>%
  ggplot(aes(x = year, y = mean_GDP, group = sex, color = sex)) +
  geom_line() +
  labs(title = "Colombia",
       x = "Año",
       y = "PIB per Cápita") +
  theme_minimal()

plot_eu_gdp <- master_eu %>%
  group_by(year, sex) %>%
  summarize(mean_GDP = mean(`gdp_per_capita ($)`, na.rm = TRUE), .groups = 'drop') %>%
  ggplot(aes(x = year, y = mean_GDP, group = sex, color = sex)) +
  geom_line() +
  labs(title = "Estados Unidos",
       x = "Año",
       y = "PIB per Cápita") +
  theme_minimal()


grid.arrange(plot_col_gdp, plot_eu_gdp, ncol = 2 ,
  top = "Evolución del PIB per Cápita por Género")

# Índice de Desarrollo Humano (IDH)
plot_col_hdi <- master_col %>%
  group_by(year, sex) %>%
  summarize(mean_HDI = mean(`HDI for year`, na.rm = TRUE), .groups = 'drop') %>%
  ggplot(aes(x = year, y = mean_HDI, group = sex, color = sex)) +
  geom_line() +
  labs(title = "Colombia",
       x = "Año",
       y = "IDH") +
  theme_minimal()

# Gráfico para EE. UU.
plot_eu_hdi <- master_eu %>%
  group_by(year, sex) %>%
  summarize(mean_HDI = mean(`HDI for year`, na.rm = TRUE), .groups = 'drop') %>%
  ggplot(aes(x = year, y = mean_HDI, group = sex, color = sex)) +
  geom_line() +
  labs(title = "Estados unidos",
       x = "Año",
       y = "IDH") +
  theme_minimal()

grid.arrange(plot_col_hdi, plot_eu_hdi, ncol = 2 ,
  top = "Evolución del Índice de Desarrollo Humano por género")

Análisis en ambos países por grupo de edad

Análisis de la evolución de los suicidios por cada 100.000 habitantes, del PIB per cápita y del IDH, a lo largo de los años en ambos países por grupo de edad.

col_age <- master_col %>% group_by(year, age) %>%
  summarize(mean_suicides = mean(`suicides/100k pop`, na.rm = TRUE), .groups = 'drop')  %>%  ggplot(aes(x = year, y = mean_suicides, group = age, color = age)) +
  geom_line() + labs( title = "Evolución de los Suicidios por Cada 100,000 Habitantes en Colombia", x = "Año", y = "Suicidios por Cada 100,000 Habitantes", color = "Grupo de edad") + 
  theme_minimal() 

grid.arrange(col_age)

En la gráfica se observa que las tasas de suicidio han variado significativamente a lo largo del tiempo. Los grupos de edad de 35-54 y 55-74 años han tenido un incremento notable en la tasa de suicidios desde principios de los años 90, alcanzando picos alrededor de 2005. Sin embargo, la tendencia muestra una ligera disminución después de este punto. El grupo de 75 años o más también ha mostrado un aumento, aunque con menos variabilidad. Los jóvenes (15-24 años) y los niños (5-14 años) presentan tasas más bajas, con algunas variaciones pero sin un patrón claro de aumento o disminución.

eu_age <- master_eu %>%  group_by(year, age) %>%
  summarize(mean_suicides = mean(`suicides/100k pop`, na.rm = TRUE), .groups = 'drop') %>%  ggplot(aes(x = year, y = mean_suicides, group = age, color = age)) +
  geom_line() + labs( title = "Evolución de los Suicidios por Cada 100,000 Habitantes en EE. UU", x = "Año", y = "Suicidios por Cada 100,000 Habitantes", color = "Grupo de edad") +
  theme_minimal() 

grid.arrange(eu_age)

En la gráfica es posible ver que las tasas de suicidio son considerablemente más altas en comparación con Colombia, especialmente en los grupos de edad mayores. El grupo de 75 años o más muestra las tasas más altas, con una tendencia a la disminución desde los años 90. Sin embargo, para los otros grupos, especialmente los de 35-54 años, se nota una estabilización o incluso un ligero aumento en los últimos años. Los jóvenes y los niños, al igual que en Colombia, presentan las tasas más bajas, pero con una tendencia al alza desde mediados de los años 2000.

# PIB per cápita
plot_col_gdp_age <- master_col %>%
  group_by(year, age) %>%
  summarize(mean_GDP = mean(`gdp_per_capita ($)`, na.rm = TRUE), .groups = 'drop') %>%
  ggplot(aes(x = year, y = mean_GDP, color = age)) +
  geom_line() +
  labs(title = "Colombia",
       x = "Año",
       y = "PIB per Cápita",
       color = "Grupo de Edad") +
  theme_minimal()

plot_eu_gdp_age <- master_eu %>%
  group_by(year, age) %>%
  summarize(mean_GDP = mean(`gdp_per_capita ($)`, na.rm = TRUE), .groups = 'drop') %>%
  ggplot(aes(x = year, y = mean_GDP, color = age)) +
  geom_line() +
  labs(title = "Estados Unidos",
       x = "Año",
       y = "PIB per Cápita",
       color = "Grupo de Edad") +
  theme_minimal()

grid.arrange(
  plot_col_gdp_age, plot_eu_gdp_age,
  ncol = 2,
  top = "Evolución del PIB per Cápita por Grupo de Edad"
)

# Índice de Desarrollo Humano (IDH)
plot_col_hdi_age <- master_col %>%
  group_by(year, age) %>%
  summarize(mean_HDI = mean(`HDI for year`, na.rm = TRUE), .groups = 'drop') %>%
  ggplot(aes(x = year, y = mean_HDI, color = age)) +
  geom_line() +
  labs(title = "Colombia",
       x = "Año",
       y = "IDH",
       color = "Grupo de Edad") +
  theme_minimal()

plot_eu_hdi_age <- master_eu %>%
  group_by(year, age) %>%
  summarize(mean_HDI = mean(`HDI for year` , na.rm = TRUE), .groups = 'drop') %>%
  ggplot(aes(x = year, y = mean_HDI, color = age)) +
  geom_line() +
  labs(title = "Estados Unidos",
       x = "Año",
       y = "IDH",
       color = "Grupo de Edad") +
  theme_minimal()

# Combinar gráficos de IDH en una fila
grid.arrange(
  plot_col_hdi_age, plot_eu_hdi_age,
  ncol = 2,
  top = "Evolución del IDH por Grupo de Edad"
)

Ejercicio 5

Instrucciones

  1. Realice un análisis descriptivo completo y actualizado a 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.
  2. 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).

Análisis descriptivo de la accidentalidad en Barranquilla.

Contextualización de la base de datos

Esta base de datos, suministrada por la Alcaldía Distrital de Barranquilla, recopila información sobre los accidentes de tránsito ocurridos en la ciudad desde 2018 hasta el 30 de junio de 2024, de acuerdo con los informes policiales de accidentes de tránsito (IPAT). Cada columna ofrece detalles específicos sobre cada incidente, como la fecha y el lugar del accidente, la cantidad de heridos, la gravedad del evento, entre otros.

Inicialmente, se hace la descarga del conjunto de datos

df = read_csv("Accidentalidad_en_Barranquilla_20240827.csv")
datatable(
  df[1:5, ],
  caption = "Base de datos: Accidentalidad en Barranquilla",
  options = list(
    scrollX = TRUE,
    scrollY = "450px",
    paging=FALSE
)
)

Diccionario de variables

Variable Descripción Tipo de Variable Categorías (si aplica)
FECHA_ACCIDENTE Fecha en que ocurrió el accidente Fecha
HORA_ACCIDENTE Hora en que ocurrió el accidente Hora
GRAVEDAD_ACCIDENTE Gravedad del accidente Categórica Con heridos, Solo daños, Con muertos
CLASE_ACCIDENTE Tipo de accidente Categórica Atropello, Choque, Caida Ocupante, Volcamiento, Otro, Incendio
SITIO_EXACTO_ACCIDENTE Ubicación exacta del accidente Dirección
CANT_HERIDOS_EN_SITIO_ACCIDENTE Cantidad de heridos en el sitio del accidente Numérica (Discreta/Factor)
CANT_MUERTOS_EN_SITIO_ACCIDENTE Cantidad de muertos en el sitio del accidente Numérica (Discreta/Factor)
CANTIDAD_ACCIDENTES Cantidad total de accidentes registrados Numérica (Discreta/Factor)
AÑO_ACCIDENTE Año en que ocurrió el accidente Numérica (Discreta/Factor) 2018, 2019, 2020, 2021, 2022, 2023, 2024
MES_ACCIDENTE Mes en que ocurrió el accidente Categórica January, February, March, April, May, June, July,August, September, October, November, December
DIA_ACCIDENTE Dia de la semana en que ocurrió el accidente Categórica Mon, Tue, Wed, Thu, Fri, Sat, Sun

Características de la base de datos

Entender la estructura de la base de datos y la naturaleza de la información contenida es fundamental para poder visualizar los datos de manera efectiva. Por esta razón, en esta sección se examinarán la dimensión de la base de datos y los diferentes tipos de variables que se encuentran en ella.

En este caso, contamos con 25610 observaciones, las cuales representan cada accidente, y 11 variables.

names(df)
 [1] "FECHA_ACCIDENTE"                  "HORA_ACCIDENTE"                  
 [3] "GRAVEDAD_ACCIDENTE"               "CLASE_ACCIDENTE"                 
 [5] "SITIO_EXACTO_ACCIDENTE"           "CANT_HERIDOS_EN _SITIO_ACCIDENTE"
 [7] "CANT_MUERTOS_EN _SITIO_ACCIDENTE" "CANTIDAD_ACCIDENTES"             
 [9] "AÑO_ACCIDENTE"                    "MES_ACCIDENTE"                   
[11] "DIA_ACCIDENTE"                   

El conjunto de datos contiene cuatro variables numéricas (CANT_HERIDOS_EN_SITIO_ACCIDENTE, CANT_MUERTOS_EN_SITIO_ACCIDENTE, CANTIDAD_ACCIDENTES, AÑO_ACCIDENTE ) y las siete restantes son categóricas.

Análisis de las variables

Variables numéricas:

A continuación se explorán las variables numéricas.

summary(df)
 FECHA_ACCIDENTE                  HORA_ACCIDENTE     GRAVEDAD_ACCIDENTE
 Min.   :2018-01-01 00:00:00.00   Length:25610       Length:25610      
 1st Qu.:2019-02-02 00:00:00.00   Class :character   Class :character  
 Median :2020-04-23 12:00:00.00   Mode  :character   Mode  :character  
 Mean   :2020-07-31 19:57:36.05                                        
 3rd Qu.:2021-12-13 00:00:00.00                                        
 Max.   :2024-06-30 00:00:00.00                                        
                                                                       
 CLASE_ACCIDENTE    SITIO_EXACTO_ACCIDENTE CANT_HERIDOS_EN _SITIO_ACCIDENTE
 Length:25610       Length:25610           Min.   : 1.000                  
 Class :character   Class :character       1st Qu.: 1.000                  
 Mode  :character   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  
                                      
                                      
                                      
                                      

Mediante el uso de la función summary(), es posible observar las principales estadísticas descriptivas de cada variable numérica.

Variables: Cantidad de heridos y muertos en sitio del accidente

summary(df$`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 
boxplot(df$`CANT_HERIDOS_EN _SITIO_ACCIDENTE`, main="Cantidad de heridos en el sitio", col ="#8FBC8F", horizontal = TRUE)

summary(df$`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 

El análisis de las estadísticas y las gráficas muestra que en los 9984 registros de accidentes, el promedio de heridos en el sitio del accidente es de 1.47, con un mínimo de 1 y un máximo de 42 heridos. La mayoría de los accidentes tienen entre 1 y 2 heridos, como lo indican los percentiles. En cuanto a las muertes en el sitio del accidente, en 252 registros, el promedio es de aproximadamente 1, con un mínimo de 1 y un máximo de 2 muertes. La columna de cantidad de accidentes indica que la mayoría de los casos son incidentes únicos, mientras que la columna del año muestra que los datos cubren un rango de 2018 a 2024, con una distribución bastante uniforme en los años analizados.

Variables Categóricas:

Variable: Hora del accidente

frec <- df %>%
  count(HORA_ACCIDENTE) %>%
  arrange(desc(n))

datatable(frec)

La variable HORA_ACCIDENTE muestra que las horas con mayor incidencia de accidentes de tránsito son las 3:00 PM y las 4:00 PM, con 408 casos cada una.

Variable: Gravedad del accidente

table(df$GRAVEDAD_ACCIDENTE)

Con heridos Con muertos  Solo daños 
       9901         252       15457 
(table(df$GRAVEDAD_ACCIDENTE))/length(df$GRAVEDAD_ACCIDENTE)*100

Con heridos Con muertos  Solo daños 
 38.6606794   0.9839906  60.3553299 
  barplot(table(df$GRAVEDAD_ACCIDENTE),col = "#8FBC8F", main = "Distribución de Accidentes por tipo de Gravedad") 

Utilizando el método table(), se puede observar la frecuencia de cada categoría en la variable GRAVEDAD_ACCIDENTE. Al ver la gráfica y la frecuencia de los datos en cuanto a la variableGRAVEDAD_ACCIDENTE, se puede afirmar que aproximadamente el 60% (15457) de los accidentes resultaron en solo daños materiales, el 39% (9901) en heridos, y el 1% (252) restante en fallecidos

Variable: Clase del accidente

Esta variable cuenta con 6 posibles casos:

table(df$CLASE_ACCIDENTE)

     Atropello Caida Ocupante         Choque       Incendio           Otro 
          1344            194          23819             13            123 
   Volcamiento 
           117 
   barplot(table(df$CLASE_ACCIDENTE),col = "#8FBC8F", main = "Distribución de Accidentes por tipo de clase")

Al ver la gráfica y la frecuencia de los datos en cuanto a la variableCLASE_ACCIDENTE, se puede afirmar que la clase de accidente más común del conjunto de datos fue producida por choques.

Variable: Mes del accidente

table(df$MES_ACCIDENTE)

    April    August  December  February   January      July      June     March 
     2010      1918      2189      2477      2349      1932      2103      2446 
      May  November   October September 
     2121      1995      2090      1980 
barplot(table(df$MES_ACCIDENTE),col = "#8FBC8F", main = "Distribución de Accidentes por mes") 

El análisis de la variable MES_ACCIDENTE muestra que febrero es el mes con la mayor cantidad de accidentes de tránsito, con 2,477 ocurrencias, seguido de cerca por marzo con 2,446 accidentes. Enero también presenta un número elevado, con 2,349 accidentes. En contraste, agosto es el mes con la menor cantidad de accidentes, registrando 1,918 casos. Estos datos sugieren una variabilidad en la frecuencia de accidentes a lo largo del año, con picos significativos en los primeros meses

Variable: Día del accidente

```{r}
table(df$DIA_ACCIDENTE)
```
 barplot(table(df$DIA_ACCIDENTE),col = "#8FBC8F", main = "Distribución de Accidentes por día") 

La distribución de los accidentes de tránsito a lo largo de los días de la semana muestra que los días laborables, especialmente el martes y el viernes, son cuando ocurren más accidentes, con 4,009 y 3,920 casos respectivamente. Esto podría estar relacionado con el aumento del tráfico. Por otro lado, el domingo, que es generalmente un día de menor actividad y tráfico, presenta la menor cantidad de accidentes, con solo 2,577 casos.

Filtrado de la base de datos

A continuación se filtrará el DataFrame de 5 maneras distintas, según diferentes condiciones. Este tipo de análisis es útil para enfocar la atención en casos específicos que pueden ser de interés particular para estudios más detallados.

Filtro 1

Accidentes de tipo choque ocurridos un martes en 2020 o 2022, clasificados como “Con muertos” y con una cantidad de accidentes igual a 1

df_filtrado1 <- df[df$GRAVEDAD_ACCIDENTE == 'Con muertos' &
               df$CLASE_ACCIDENTE == 'Choque' &
               df$AÑO_ACCIDENTE %in% c(2020, 2022) &
               df$DIA_ACCIDENTE == 'Tue' &
               df$CANTIDAD_ACCIDENTES == 1, ]

datatable(df_filtrado1)

Este filtrado ha reducido el conjunto de datos a solo aquellos accidentes que son particularmente graves (con muertos), ocurrieron en martes durante los años 2020 o 2022, fueron choques, y fueron incidentes únicos. En total se encontraron tres observaciones con las condiciones dadas.

Filtro 2

Accidentes de tipo choque ocurridos en días laborales (lunes a viernes) durante el mes de diciembre de cualquier año

df_filtrado2 <- df[df$CLASE_ACCIDENTE == 'Choque' &
               df$DIA_ACCIDENTE %in% c('Mon', 'Tue', 'Wed', 'Thu', 'Fri') &
               df$MES_ACCIDENTE == 'December', ]

dim(df_filtrado2)
[1] 1536   11
datatable(df_filtrado2)

Este filtrado ha reducido el conjunto de datos a solo aquellos accidentes de tipo “choque” que ocurrieron en días laborales (de lunes a viernes) durante el mes de diciembre, sin importar el año. En total se encontraron 1,536 observaciones que cumplen con estas condiciones. Para verificar la cantidad de datos restantes después del filtrado, se utilizó la función dim().

Filtro 3

Accidentes de tipo atropello ocurridos en octubre y diciembre ocurridos a las 6 am y 6 pm

df_filtrado3 <- df %>% filter(df$CLASE_ACCIDENTE =="Atropello" & df$MES_ACCIDENTE == c("October", "December") &
df$HORA_ACCIDENTE == c("06:00:00:am", "06:00:00:pm"))
dim(df_filtrado3)
[1]  2 11
datatable(df_filtrado3)

En este caso, se puede observar que solo 2 registros cumplen con los filtros establecidos. De estos, uno ocurrió un martes y el otro un jueves.

Filtro 4

Accidentes ocurridos con exactamente 2 accidentes reportados

df_filtrado4 <- df %>% filter(df$CANTIDAD_ACCIDENTES == 2)
datatable(df_filtrado4)
dim(df_filtrado4)
[1]  5 11

Este filtrado ha reducido el conjunto de datos a solo 5 observaciones.

Filtro 5

Accidentes ocurridos los días miércoles y jueves en los años 2020 y 2021, a las 6 AM y 12 PM)

df_filtrado5 <- df %>% filter(df$DIA_ACCIDENTE == c("Wed", "Thu") & df$AÑO_ACCIDENTE == c(2020,2021) &
    df$HORA_ACCIDENTE == c("06:00:00:am", "12:00:00:pm"))
dim(df_filtrado5)
[1]  6 11

Este filtrado ha reducido el conjunto de datos a los accidentes ocurridos los días miércoles y jueves durante los años 2020 y 2021, en los horarios de 6:00 AM y 12:00 PM. En total se encontraron observaciones que cumplen con estas condiciones.

Identificación y tratamiento de valores NA

Identificación de datos

Primeramente, calculemos cuántos hay por columna.

cantidad_na <- sapply(df, function(x) sum(is.na(x)))
for (columna in names(cantidad_na)) {
  cat(columna, ":", cantidad_na[[columna]], "NA's \n")
}
FECHA_ACCIDENTE : 0 NA's 
HORA_ACCIDENTE : 0 NA's 
GRAVEDAD_ACCIDENTE : 0 NA's 
CLASE_ACCIDENTE : 0 NA's 
SITIO_EXACTO_ACCIDENTE : 0 NA's 
CANT_HERIDOS_EN _SITIO_ACCIDENTE : 15626 NA's 
CANT_MUERTOS_EN _SITIO_ACCIDENTE : 25358 NA's 
CANTIDAD_ACCIDENTES : 0 NA's 
AÑO_ACCIDENTE : 0 NA's 
MES_ACCIDENTE : 0 NA's 
DIA_ACCIDENTE : 0 NA's 

Ahora, veamos esto gráficamente.

gg_miss_var(df, show_pct = TRUE)

Los resultados permiten deducir que las variables CANT_HERIDOS_EN _SITIO_ACCIDENTE y CANT_MUERTOS_EN _SITIO_ACCIDENTE presentan una cantidad significativa de datos faltantes, con 15,626 observaciones faltantes (61.0%) y 25,358 observaciones faltantes (99.0%), respectivamente. El resto de las columnas no tienen datos faltantes, es decir, el porcentaje de valores faltantes es 0% para todas ellas.

Tratamiento de datos faltantes

Las variables CANT_HERIDOS_EN _SITIO_ACCIDENTE y CANT_MUERTOS_EN _SITIO_ACCIDENTE pueden ser consideradas como factores, ya que sus valores no son continuos. Por lo tanto, no es necesario realizar una imputación en estas variables. Esto se debe a que los valores NA en estas variables pueden representar de manera adecuada la información relacionada con las consecuencias del accidente.

Variable: Cantidad de heridos en sitio del accidente

El objetivo de este filtrado es identificar aquellos casos donde, a pesar de que el accidente tuvo una gravedad significativa (“Con muertos”) o no tuvo heridos (“Solo daños”), no se registraron heridos en el sitio del accidente.

heridos_f <- df[is.na(df$`CANT_HERIDOS_EN _SITIO_ACCIDENTE`) & 
                df$GRAVEDAD_ACCIDENTE %in% c('Solo daños', 'Con muertos'), ]
knitr::kable(head(heridos_f))
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-01 02:00:00:pm Solo daños Choque CL 110 CR 46 NA NA 1 2018 January Mon
2018-01-01 04:00:00:am Solo daños Choque AV CIRCUNVALAR CR 9G NA NA 1 2018 January Mon
2018-01-01 04:30:00:am Solo daños Choque CLLE 72 CRA 29 NA NA 1 2018 January Mon
2018-01-01 05:20:00:pm Solo daños Choque VIA 40 CALLE 75 NA NA 1 2018 January Mon
2018-01-02 02:30:00:pm Solo daños Choque CARRERA 25 37-42 NA NA 1 2018 January Tue
2018-01-02 03:00:00:pm Solo daños Choque CR 51B 1D 35 NA NA 1 2018 January Tue

El resultado muestra las 10 primeras filas de los datos filtrados, donde se observa que todas las entradas cumplen con estas condiciones: no tienen información sobre heridos (NaN en la columna CANT_HERIDOS_EN _SITIO_ACCIDENTE) y corresponden a accidentes clasificados como “Solo daños” o “Con muertos”.

La imputación de valores faltantes en esta variable no es realmente necesaria, ya que se observa que los NA’s en esta variable tienden a aparecer cuando la variable GRAVEDAD_ACCIDENTE toma los valores “Con muertos” o “Solo daños”, lo que indica una relación directa entre la gravedad del accidente y la ausencia de datos en la variable CANT_HERIDOS_EN _SITIO_ACCIDENTE

Variable: Cantidad de muertos en sitio del accidente

El objetivo de este filtrado es encontrar casos en los que, a pesar de que el accidente fue grave en términos de heridos o solo daños, no se reportaron fallecidos (NaN en la columna CANT_MUERTOS_EN _SITIO_ACCIDENTE).

muertos_f <- df[is.na(df$`CANT_MUERTOS_EN _SITIO_ACCIDENTE`) & 
                df$GRAVEDAD_ACCIDENTE %in% c('Solo daños', 'Con heridos'), ]
knitr::kable(head(muertos_f))
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-01 01:30:00:am Con heridos Atropello CL 87 9H 24 1 NA 1 2018 January Mon
2018-01-01 02:00:00:pm Solo daños Choque CL 110 CR 46 NA NA 1 2018 January Mon
2018-01-01 04:00:00:am Solo daños Choque AV CIRCUNVALAR CR 9G NA NA 1 2018 January Mon
2018-01-01 04:30:00:am Solo daños Choque CLLE 72 CRA 29 NA NA 1 2018 January Mon
2018-01-01 05:20:00:pm Solo daños Choque VIA 40 CALLE 75 NA NA 1 2018 January Mon
2018-01-01 06:00:00:pm Con heridos Choque CR 8 CL 41 3 NA 1 2018 January Mon

La variable CANT_MUERTOS_EN _SITIO_ACCIDENTE tiene un total de 25,358 observaciones faltantes, lo que representa el 99% del total de observaciones. Es importante destacar que este número corresponde a la suma de las observaciones en las categorías de GRAVEDAD_ACCIDENTE “Con heridos” (9901) y “Solo daños” (15457). Esto podría indicar que, más que tratarse de datos faltantes, la ausencia de datos en la variable CANT_MUERTOS_EN _SITIO_ACCIDENTE refleja que no hubo muertos en esos accidentes, sino solo heridos o daños materiales. Por lo tanto, la presencia de NA’s en esta variable puede interpretarse como un indicador de que el accidente no resultó en ninguna fatalidad, sino en consecuencias menos graves.

Valores atípicos

En esta parte, las variables numéricas se pueden considerar como factores o variables continuas discretas. Por lo tanto, la identificación de datos atípicos no será particularmente útil, dado que no se dispone de un rango continuo de valores que permita detectar desviaciones significativas. En su lugar, se calcularán tablas de frecuencias para observar la distribución de las categorías y entender mejor cómo se distribuyen los valores en estas variables discretas, lo que permitirá un análisis más adecuado y relevante para este tipo de datos.

Variable: Cantidad de heridos en sitio de accidente

unique(df$`CANT_HERIDOS_EN _SITIO_ACCIDENTE`)
 [1]  1 NA  3  2  5  7  8  4 11  6 10 12 20 22 13  9 16 14 42 19 21 23 18 15
bins <- c(0, 5, 10, 15, 20, 25, 50)
labels <- c('1 - 5', '6 - 10', '11 - 15', '16 - 20', '21 - 25', '26 - 50')

# Crear la columna de intervalos en el data frame
df$Intervalo <- cut(df$`CANT_HERIDOS_EN _SITIO_ACCIDENTE`, breaks = bins, labels = labels, right = FALSE)

# Filtrar los valores NA y calcular la frecuencia
table <- df %>%
  filter(!is.na(Intervalo)) %>%   # Excluir NA
  count(Intervalo) %>%
  arrange(Intervalo) %>%
  rename(`Número de Heridos` = Intervalo, Frecuencia = n)

# Mostrar la tabla en un formato elegante
knitr::kable(table, format = "pipe", align = "c", caption = "Frecuencia por Intervalo de Heridos")
Frecuencia por Intervalo de Heridos
Número de Heridos Frecuencia
1 - 5 9836
6 - 10 115
11 - 15 24
16 - 20 4
21 - 25 4
26 - 50 1

Este análisis permite observar que la mayoría de los accidentes reportan entre 1 y 5 heridos, lo que es el caso más común. Por otro lado, un único accidente reporta 42 heridos, lo que lo agrupa en el intervalo de 26-50 heridos, destacando su diferencia y gravedad en comparación con la mayoría de los datos. Este resultado resalta la importancia de evaluar estos casos raros con más detalle.

Variable: Cantidad de muertos en sitio de accidente

unique(df$`CANT_MUERTOS_EN _SITIO_ACCIDENTE`)
[1] NA  1  2
table2 <- df %>%
  filter(!is.na(`CANT_MUERTOS_EN _SITIO_ACCIDENTE`)) %>%  # Excluir NA
  count(`CANT_MUERTOS_EN _SITIO_ACCIDENTE`) %>%
  rename(`Número de Muertos` = `CANT_MUERTOS_EN _SITIO_ACCIDENTE`, Frecuencia = n)

knitr::kable(table2, format = "pipe", align = "c", caption = "Frecuencia de Muertos en el Sitio del Accidente")
Frecuencia de Muertos en el Sitio del Accidente
Número de Muertos Frecuencia
1 243
2 9

Se puede observar que la mayoría de los accidentes reportan 1 muerto, lo que es el caso más común y solo 9 de todos los accidentes tuvieron 2 muertos.

Variable: Año del accidente

table3 <- df %>%
  filter(!is.na(AÑO_ACCIDENTE)) %>%  
  count(AÑO_ACCIDENTE) %>%
  rename(`Año del accidente` = AÑO_ACCIDENTE, Frecuencia = n)

knitr::kable(table3, format = "pipe", align = "c", caption = "Frecuencia por Año del Accidente")
Frecuencia por Año del Accidente
Año del accidente Frecuencia
2018 5898
2019 5645
2020 3281
2021 4700
2022 3683
2023 1662
2024 741

Se observa que el año 2018 tuvo el mayor número de accidentes registrados, con un total de 5898 casos, seguido por el año 2019 con 5645 accidentes. Los años siguientes muestran una tendencia general a la disminución en la frecuencia de accidentes, siendo el año 2024 el que registra la menor cantidad con 741 casos (Esto puede darse porque aún el año no ha culminado). Este descenso en el número de accidentes podría reflejar cambios en las diferentes condiciones y factores en los accidentes.

Variable: Cantidad de accidentes ocurridos

table4 <- df %>%
  filter(!is.na(CANTIDAD_ACCIDENTES)) %>%  
  count(CANTIDAD_ACCIDENTES) %>%
  rename(`Cantidad de accidente` = CANTIDAD_ACCIDENTES, Frecuencia = n)

knitr::kable(table4, format = "pipe", align = "c", caption = "Frecuencia por Cantidad de Accidentes")
Frecuencia por Cantidad de Accidentes
Cantidad de accidente Frecuencia
1 25605
2 5

En la tabla presentada, se observa que la gran mayoría de los registros corresponden a incidentes donde ocurrió un solo accidente, con un total de 25,605 casos. Solo hay 5 registros en los que se reportan dos accidentes. Este resultado es esperado, ya que los accidentes están clasificados según variables específicas como la hora, dirección, fecha, y otros datos detallados. Estas clasificaciones precisas hacen poco común que se registren múltiples accidentes bajo las mismas condiciones exactas, lo que explica la baja frecuencia de casos con dos accidentes reportados.

Análisis descriptivo del precio de combustible en Colombia 2023.

Esta base de datos, ofrecida por el sitio oficial de SICOM (Sistema de Información de la Cadena de Distribución de Combustibles del Ministerio de Minas y Energía), contiene datos sobre los precios de los combustibles distribuidos en Colombia durante el año 2023.

Datos

d20231 = read_csv('20231.csv')
d20232 = read_csv('20232.csv')
d20233 = read_csv('20233.csv')
d20234 = read_csv('20234.csv')
df_2023 <- bind_rows(d20231,d20232,d20233,d20234)
datatable(
  df_2023[1:10, ],
  caption = "Base de datos: Combustibles en Colombia año 2023",
  options = list(
    scrollX = TRUE,
    scrollY = "450px",
    paging=FALSE
)
)

Contextualización de la base de datos

Características de la base de datos

dim(df_2023)
[1] 267943      7

Usando la función dim(), podemos determinar que la base de datos contiene 267,943 observaciones y 7 variables.

names(df_2023)
[1] "BANDERA"          "NOMBRE COMERCIAL" "PRODUCTO"         "FECHA REGISTRO"  
[5] "DEPARTAMENTO"     "MUNICIPIO"        "VALOR PRECIO"    

El conjunto de datos contiene una variable numérica (VALOR PRECIO) y seis variables categóricas.

Análisis de las variables

Variable: Producto

unique(df_2023$PRODUCTO)
[1] "DIESEL"         "GASOLINA MOTOR" "EXTRA"         
table(df_2023$PRODUCTO)

        DIESEL          EXTRA GASOLINA MOTOR 
        108205          32400         127338 
ggplot(df_2023, aes(x = PRODUCTO)) +
  geom_bar(fill = "#8FBC8F") +
  labs(x = "Tipo de combustible", y = "Frecuencia", title = "Distribución de los diferentes tipos de combustible") +
  theme_minimal()

El análisis presentado en la gráfica muestra la distribución de los diferentes tipos de combustibles registrados. Según los datos, la gasolina motor es el tipo de combustible más común, con 127.338 registros, seguida por el diésel, que cuenta con 108.285 registros. En menor cantidad se encuentra el combustible extra, con 32.400 registros.

Variable: Valor precio

options(digits = 2)
summary(df_2023$`VALOR PRECIO`)
    Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
       0     9350    10880    12023    13849 14750147 
ggplot(df_2023, aes(x = `VALOR PRECIO`, y = factor(1))) +
  geom_boxplot(fill = "#8FBC8F") +
  scale_x_continuous(limits = c(0, 30000), labels = scales::comma) +
  labs(x = "Precio", y = "Tipo de Producto", title = "Distribución del precio de combustible") +
  theme_classic()
Warning: Removed 33 rows containing non-finite outside the scale range
(`stat_boxplot()`).

El análisis estadístico de la variable VALOR PRECIO muestra que, de los 267.943 registros, el precio promedio es de 12,023.13 pesos, con una mediana de 10,880.00 pesos. La desviación estándar es alta, de 28,709.79 pesos, lo que sugiere una variabilidad significativa en los precios. El precio mínimo registrado es de 0.00 COP, lo que podría indicar un error en los datos, mientras que el precio máximo alcanza los 14,750,147.00 pesos, lo que también parece ser un valor atípico. Además, el 25% de los precios son inferiores a 9,350.00 pesos y el 75% son menores a 13,849.00 pesos. Este análisis refleja tanto la tendencia central como la dispersión en los precios, así como la presencia de valores atípicos en los datos.

Variable: Departamento

tabla <- df_2023 %>%
  count(DEPARTAMENTO) %>%
  arrange(desc(n)) %>%
  rename(Departamento = DEPARTAMENTO, Frecuencia = n)

knitr::kable(tabla, format = "pipe", align = "c", caption = "Frecuencia por Departamento")
Frecuencia por Departamento
Departamento Frecuencia
NARIÑO 31054
ANTIOQUIA 25009
NORTE DE SANTANDER 21752
VALLE DEL CAUCA 18754
CUNDINAMARCA 16631
BOGOTA D.C. 16031
CESAR 15703
LA GUAJIRA 13636
SANTANDER 10247
ATLANTICO 8595
TOLIMA 8014
BOYACA 7769
CORDOBA 7440
BOLIVAR 7027
PUTUMAYO 6610
HUILA 6355
META 6250
CAUCA 5617
RISARALDA 5435
MAGDALENA 5339
CALDAS 4517
SUCRE 4358
CASANARE 2744
CHOCO 2640
QUINDIO 2555
CAQUETA 2390
ARAUCA 2312
GUAVIARE 1043
VICHADA 922
AMAZONAS 513
ARCHIPIELAGO DE SAN ANDRES, SANTA CATALINA Y PROVIDENCIA 267
GUAINIA 261
VAUPES 153

Los resultados indican lo siguiente:

Entre los departamentos con mayor número de registros se encuentran Nariño con 31.054 registros, seguido de Antioquia con 25.009 registros y Norte de Santander con 21.752 registros. Estos departamentos parecen tener una mayor actividad o más estaciones de servicio reportando precios de combustible.

Por otro lado, los departamentos con menor número de registros son Guainía con 261 registros y Vaupés con solo 153 registros. Esto podría indicar una menor densidad de estaciones de servicio en estos departamentos o menos reportes de precios.

Identificación de valores NA

Primeramente, calculemos de manera matemática la cantidad de datos faltantes.

cantidad_na_23 <- sapply(df_2023, function(x) sum(is.na(x)))
for (columna in names(cantidad_na_23)) {
  cat(columna, ":", cantidad_na_23[[columna]], "NA's \n")
}
BANDERA : 0 NA's 
NOMBRE COMERCIAL : 0 NA's 
PRODUCTO : 0 NA's 
FECHA REGISTRO : 0 NA's 
DEPARTAMENTO : 0 NA's 
MUNICIPIO : 0 NA's 
VALOR PRECIO : 0 NA's 
gg_miss_var(df_2023, show_pct = TRUE)

Es decir que la base de datos no contiene ningún dato de tipo NA.

Datos atípicos

Para ver los datos atípicos y concentrarnos en la parte de la imputación de estos mismos, usaremos la variable de interés VALOR PRECIO. El primer paso para esto es observar el diagrama de cajas y bigotes el cual nos permitirá determinar que en efecto si hay datos atípicos.

ggplot(df_2023, aes(x = `VALOR PRECIO`, y = factor(1))) +
  geom_boxplot(fill = "#8FBC8F") +
  scale_x_continuous(limits = c(0, 30000), labels = scales::comma) +
  labs(x = "Precio", y = "Tipo de Producto", title = "Distribución del precio de combustible") +
  theme_classic()
Warning: Removed 33 rows containing non-finite outside the scale range
(`stat_boxplot()`).

En efecto, si hay presencia de datos atípicos en la variable `VALOR PRECIO`. Ahora veamos la cantidad exacta de datos atípicos existentes con el método de rango intercuartilico

# Calcular el primer y tercer cuartil
Q1 <- quantile(df_2023$`VALOR PRECIO`, 0.25)
Q3 <- quantile(df_2023$`VALOR PRECIO`, 0.75)

# Calcular el IQR
IQR <- Q3 - Q1

# Calcular los límites inferior y superior
lower_bound <- Q1 - 1.5 * IQR
upper_bound <- Q3 + 1.5 * IQR

# Filtrar los outliers
outliers <- df_2023[df_2023$`VALOR PRECIO` < lower_bound | df_2023$`VALOR PRECIO` > upper_bound, ]

# Mostrar el número de outliers detectados
cat("Número de outliers detectados:", nrow(outliers), "\n")
Número de outliers detectados: 2907 

Veamos ahora su distribución.

ks_test <- ks.test(df_2023$`VALOR PRECIO`, "pnorm", mean(df_2023$`VALOR PRECIO`), sd(df_2023$`VALOR PRECIO`))

# Evaluar el p-value y determinar si la distribución es normal
if (ks_test$p.value > 0.05) {
  cat("La variable 'VALOR PRECIO' sigue una distribución normal.\n")
} else {
  cat("La variable 'VALOR PRECIO' NO sigue una distribución normal.\n")
}
La variable 'VALOR PRECIO' NO sigue una distribución normal.

Dado que las observaciones de la variable VALOR PRECIO NO siguen una distribución normal se usará la mediana como método de imputación de los datos atípicos

# Calcular la mediana
mediana <- median(df_2023$`VALOR PRECIO`, na.rm = TRUE)

# Imputación de los datos atípicos con la mediana
df_2023$`VALOR PRECIO`[df_2023$`VALOR PRECIO` < lower_bound] <- mediana
df_2023$`VALOR PRECIO`[df_2023$`VALOR PRECIO` > upper_bound] <- mediana

Veamos si la distribución se mantuvo

ks_test <- ks.test(df_2023$`VALOR PRECIO`, "pnorm", mean(df_2023$`VALOR PRECIO`), sd(df_2023$`VALOR PRECIO`))

# Evaluar el p-value y determinar si la distribución es normal
if (ks_test$p.value > 0.05) {
  cat("La variable 'VALOR PRECIO' sigue una distribución normal.\n")
} else {
  cat("La variable 'VALOR PRECIO' NO sigue una distribución normal.\n")
}
La variable 'VALOR PRECIO' NO sigue una distribución normal.

En efecto, la imputación fue realizada de manera exitosa dada que la distribución de los datos correspondientes a la variable VALOR PRECIO mantuvo su distribución

Veamos ahora el diagrama de cajas y bigotes para confirmar la efectividad de la imputación.

ggplot(df_2023, aes(x = `VALOR PRECIO`, y = factor(1))) +
  geom_boxplot(fill = "#8FBC8F") +
  scale_x_continuous(limits = c(0, 30000), labels = scales::comma) +
  labs(x = "Precio", y = "Tipo de Producto", title = "Distribución del precio de combustible") +
  theme_classic()

Filtración de la base de datos

A continuación se filtra la base de datos por la variable PRODUCTO. En este caso se escogió el combustible de tipo “Gasolina Motor”. Dicha categoría es estudiada más adelante con un mapa interactivo.

df_motor <- df_2023[df_2023$PRODUCTO == 'GASOLINA MOTOR', ]
datatable(
  df_motor[1:10, ],
  caption = "Combustibles de Gasolina Motor en Colombia 2023",
  options = list(
    scrollX = TRUE,
    scrollY = "450px",
    paging=FALSE
)
)
dim(df_motor)
[1] 127338      7

Luego de haber filtrado podemos afirmar que en la base de datos original, hay 127338 observaciones registradas acerca del combustible de tipo Gasolina Motor

Mapa de geolocalización

En esta parte del código, se realiza una unión entre los dos DataFrames de intéres: mapa_col, que contiene la información geoespacial, y df_precios, que tiene datos sobre los precios de combustible por departamento. La fusión se hace utilizando como claves de unión la columna DPTO_CNMBR del DataFrame mapa_col y la columna DEPARTAMENTO del DataFrame df_precios.

df_precios <- df_motor %>%
  group_by(DEPARTAMENTO) %>%
  summarize(`VALOR PRECIO` = mean(`VALOR PRECIO`)) %>%
  ungroup()
# Leer el archivo shapefile
shapefile_path <- "C:/Users/kamac/OneDrive/Desktop/VisualizacionUN/COLOMBIA/COLOMBIA.shp"
mapa_col <- st_read(shapefile_path)
Reading layer `COLOMBIA' from data source 
  `C:\Users\kamac\OneDrive\Desktop\VisualizacionUN\COLOMBIA\COLOMBIA.shp' 
  using driver `ESRI Shapefile'
Simple feature collection with 33 features and 11 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -82 ymin: -4.2 xmax: -67 ymax: 13
Geodetic CRS:  WGS 84
# Ajustar nombres para coincidir con los del shapefile
df_precios$DEPARTAMENTO[df_precios$DEPARTAMENTO == "ARCHIPIELAGO DE SAN ANDRES, SANTA CATALINA Y PROVIDENCIA"] <- "ARCHIPIELAGO DE SAN ANDRES"
mapa_col$DPTO_CNMBR[mapa_col$DPTO_CNMBR == "NARI?O"] <- "NARIÑO"

# Unir el shapefile con el dataframe de precios
mapa_col <- merge(mapa_col, df_precios, by.x = "DPTO_CNMBR", by.y = "DEPARTAMENTO", all.x = TRUE)

Finalmente, graficamos el mapa donde se puede ver la variabilidad de precios del combustible gasolina motor dependiendo el departamento donde está ubicado.

pal <- colorNumeric(
  palette = "Reds",  
  domain = mapa_col$`VALOR PRECIO`  # Dominios basados en la columna de precios
)
map <- leaflet(mapa_col) %>% 
  addTiles() %>%
  addPolygons(
    popup = ~paste("Departamento: ", mapa_col$DPTO_CNMBR, "<br>",
                   "Precio promedio de gasolina motor: ", round(mapa_col$`VALOR PRECIO`, 0), "COP"),
    color = "black",
    fillColor = ~pal(mapa_col$`VALOR PRECIO`),
    weight = 1.1
  ) %>%
  addLegend(
    position = "topright",
    pal = pal,
    values = mapa_col$`VALOR PRECIO`,
    title = "Precio promedio de gasolina motor en Colombia en 2023"
  )

mapshot(map, file = "mapa.png", selfcontained = FALSE, vwidth = 1200, vheight = 900)

knitr::include_graphics("mapa.png")

La imagen muestra un mapa interactivo de Colombia, en el cual los departamentos están coloreados de acuerdo con el precio promedio de la gasolina motor en 2023. La leyenda en la parte superior derecha indica que los precios varían desde aproximadamente 11,000 pesos hasta 15,000 pesos. Los departamentos con precios más bajos están representados en tonos más claros, mientras que los que tienen precios más altos están en tonos más oscuros.

En general, se observa que los departamentos ubicados al sureste del país, como Amazonas, presentan los precios más altos de gasolina, mientras que los departamentos del centro y norte tienen precios más bajos. Esto podría indicar una posible correlación entre la ubicación geográfica y el costo del combustible.

ggplot(data = mapa_col) +
  geom_sf(aes(fill = `VALOR PRECIO`), color = "black") +  
  scale_fill_viridis_c(option = "Greens", na.value = "white") +  
  # Las etiquetas son repelidas para evitar superposición usando 'geom_text_repel'.
  geom_text_repel(aes(label = ifelse(DPTO_CNMBR %in% c("BOGOTA D.C.", "AMAZONAS", "GUAINIA", "ATLANTICO", "VAUPES"), DPTO_CNMBR, ""),
                      geometry = geometry),
                  stat = "sf_coordinates",
                  size = 2,  # Ajusta el tamaño del texto de las etiquetas
                  min.segment.length = 0) +  # Permite la longitud mínima de los segmentos que unen la etiqueta con el punto correspondiente  
  theme_minimal() + 
  theme(
    plot.title = element_text(size = 12, hjust = 0.3),  # Ajusta el tamaño del título y lo posiciona ligeramente hacia la izquierda
    legend.position = "right",  # Coloca la leyenda a la derecha del gráfico
    legend.title = element_text(size = 12),  # Ajusta el tamaño del título de la leyenda
    legend.text = element_text(size = 10),  # Ajusta el tamaño del texto dentro de la leyenda
    axis.text = element_text(size = 8)  # Ajusta el tamaño del texto en los ejes (x e y)
  ) + 
  labs(
    title = "Mapa de los Departamentos de Colombia con precios Promedios de Gasolina motor", 
    fill = "Precio Promedio (COP)"
  ) +
  coord_sf(xlim = c(-82, -66), ylim = c(-4.5, 13.5))  # Ajusta los límites del mapa para centrar mejor la visualización

El mapa muestra la distribución de los precios promedio de la gasolina motor en los diferentes departamentos de Colombia. Se observa que los departamentos del sur y sureste, como Amazonas y Vaupés, presentan los precios más altos, reflejados en colores amarillos, mientras que los departamentos del centro y norte del país, como Atlántico y Antioquia, muestran precios más bajos, representados en tonos más oscuros, como azul y verde. Esta variación sugiere que hay una diferencia significativa en los costos del combustible a lo largo del país, posiblemente influenciada por factores como la accesibilidad.