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)Ejercicios - Visualización de datos con R
Paquetes y librerias necesarias.
Ejercicio 3
Instrucciones
Importe la base de datos.
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.
Analice cada una de las variables según su tipo: numéricas y categóricas.
Filtre la base de datos para entender mejor su estructura.
Explore la ayuda de la función
tabledel paquetebasey utilícela para explorar la base de datos.Identifique los valores NA (Not Available) en la base de datos.
Analice la presencia de posibles valores atípicos.
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 intervalosañ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):
Edite y explore reglas para verificar que la base de datos no contenga posibles registros erróneos.
Filtre los datos de Colombia y los de EEUU, generando dos bases de datos, llamadas
master_colymaster_eu.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.
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.
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:
"age > 0": La edad debe ser mayor que 0."suicides_no >= 0": El número de suicidios debe ser mayor o igual a 0."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
- 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. - 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")| 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")| 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")| 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")| 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")| 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] <- medianaVeamos 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ónEl 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.