---
title: "El gran problema del desempleo"
description: |
Análisis de la evolución reciente de las tasas de desempleo en Europa utilizando datos oficiales de Eurostat.
author:
- name: Marian Delfa Baena
affiliation: Universitat de València
affiliation-url: https://www.uv.es
date: 2025-12-22
categories: [trabajo BigData, desempleo, eurostat]
image: "./imagenes/imagen_01.png"
theme: zephyr
title-block-banner: "#2563EB"
title-block-banner-color: "white"
toc: true
toc-depth: 3
smooth-scroll: true
format:
html:
mainfont: 'Inter, sans-serif'
link-external-newwindow: true
code-tools: true
code-link: true
df-print: paged
---
## Intro
Vamos a utilizar datos del paquete `eurostat`.
Los datos se pueden cargar en la memoria de de R/RStudio de esta forma:
```{r}
#| label: carga-datos-eurostat
#| warning: false
#| message: false
#| echo: false
#| output: false
library(ggplot2)
library(knitr)
library(dplyr)
library(eurostat)
library(lubridate)
library(DT)
library(plotly)
# Busco datasets relacionados con el desempleo, he elegido el tema de la desempleo porque es un tema actualmente muy relevante
my_tema <- "unemployment"
aa <- eurostat::search_eurostat(pattern = my_tema, type = "all")
# Específicamente elijo la tabla "tps00203" (Total unemployment rate)
my_table <- "tps00203"
# Información sobre la base de datos seleccionada
eurostat::label_eurostat_tables(my_table)
# Importo los datos desde Eurostat
df_desempleo <- eurostat::get_eurostat(my_table, time_format = "raw", keepFlags = TRUE)
# Pido los descriptores/labels de las series para entender mejor las variables
df_desempleo_l <- eurostat::label_eurostat(df_desempleo)
# como me daba problemas el ratio al estar en porcentaje o no, lo he pasado a porcentaje real apañándolo como podía para que todos estén en la misma medida y todos tengan un porcentaje adecuado
df_desempleo_l <- df_desempleo_l %>%
mutate(
Unemployment_rate = case_when(
values > 1000 ~ values / 1000,
values > 100 ~ values / 10,
TRUE ~ values
)
)
# filtrar también los valores extremos que no sean lógicos para tasa de desempleo
df_desempleo_l <- df_desempleo_l %>%
filter(
Unemployment_rate >= 0,
Unemployment_rate <= 100 # Una tasa de desempleo no puede superar 100%
)
# tipificar por si acaso
df_desempleo_tip <- df_desempleo_l %>%
group_by(geo) %>%
mutate(
Unemployment_z = scale(Unemployment_rate)
) %>%
ungroup()
```
El dataset contiene observaciones sobre un conjunto de `r nrow(df_desempleo_l)` ratios de desempleo.
------------------------------------------------------------------------
## 1- Tabla interactiva
Vamos a crear una tabla donde se puede buscar por país y año, permitiendo exportar los datos en formato PDF, CSV o Excel (datos filtrados a partir de 2018).
```{r}
#| warning: false
#| message: false
tabla_desempleo <- df_desempleo_tip %>%
mutate(
year = as.numeric(TIME_PERIOD),
) %>%
filter(
age == "From 15 to 74 years",
sex == "Total",
year >= 2018
) %>%
# para evitar datos duplicados, vacíos y que como mucho hayan 2 decimales
group_by(geo, year) %>%
summarise(
Unemployment_rate = mean(Unemployment_rate, na.rm = TRUE),
.groups = "drop"
) %>%
filter(!is.na(Unemployment_rate)) %>%
mutate(Unemployment_rate = round(Unemployment_rate, 2)) %>%
select(
Country = geo,
Year = year,
Unemployment_rate
)
# tabla interactiva
datatable(
tabla_desempleo,
class = 'cell-border stripe hover', # Estilo rayado moderno
extensions = "Buttons",
options = list(
pageLength = 10,
autoWidth = TRUE,
dom = "Bfrtip",
buttons = list(
list(extend = "csv", text = "CSV"),
list(extend = "excel", text = "Excel"),
list(extend = "pdf", text = "PDF")
),
language = list(
url = "//cdn.datatables.net/plug-ins/1.13.6/i18n/es-ES.json"
)
),
caption = "Tasa de desempleo (%)"
)
```
------------------------------------------------------------------------
## 2- Gráfico interactivo de lineas
Bueno pues vamos a hacer algún gráfico, en este caso será un gráfico desde 2020 a 2024 que recogerá la evolución del desempleo que permite comparar interactivamente cualquier país con la media europea.
Arreglo los datos
```{r}
# cojo datos de la tabla anterior que ya están clasificados por país y año y arreglo para que sea esta vez de 2020 a 2024
datos_filtrados <- tabla_desempleo %>%
filter(Year >= 2020 & Year <= 2024) %>%
mutate(
Unemployment_rate = as.numeric(Unemployment_rate),
Unemployment_rate = ifelse(Unemployment_rate > 100, NA, Unemployment_rate)
) %>%
filter(!is.na(Unemployment_rate)) %>%
group_by(Country, Year) %>%
summarise(
Unemployment_rate = mean(Unemployment_rate, na.rm = TRUE),
.groups = "drop"
) %>%
group_by(Country) %>%
filter(n() >= 3) %>%
ungroup() %>%
arrange(Country, Year)
```
Muestro finalmente el gráfico
```{r}
#| warning: false
#| message: false
#| fig-height: 3.5
#| out-width: "100%"
# primero quiero calcular la media de ratio de desempleo europeo para que se puedan hacer comparativas
media_europa <- datos_filtrados %>%
group_by(Year) %>%
summarise(
Country = "Media Europa",
Unemployment_rate = mean(Unemployment_rate, na.rm = TRUE),
.groups = "drop"
) %>%
mutate(Unemployment_rate = round(Unemployment_rate, 2))
datos_finales <- bind_rows(datos_filtrados, media_europa)
paises <- sort(unique(datos_filtrados$Country))
# ordenar países por tasa de desempleo
paises_ordenados <- datos_filtrados %>%
group_by(Country) %>%
summarise(media = mean(Unemployment_rate, na.rm = TRUE)) %>%
arrange(media) %>%
pull(Country)
paises <- paises_ordenados
# ajustar los margenes de los ejes a las búsquedas porque me quedaba muy grande
rango_min <- min(datos_finales$Unemployment_rate, na.rm = TRUE)
rango_max <- max(datos_finales$Unemployment_rate, na.rm = TRUE)
margen <- (rango_max - rango_min) * 0.15
if(margen < 0.5) margen <- 0.5
y_min_inicial <- max(0, rango_min - margen)
y_max_inicial <- rango_max + margen
# gráfico base
fig <- plot_ly()
# añadir trazas para cada país
for (pais in paises) {
df_pais <- datos_finales %>% filter(Country == pais)
if(nrow(df_pais) > 0) {
fig <- fig %>%
add_trace(
data = df_pais,
x = ~Year,
y = ~Unemployment_rate,
type = 'scatter',
mode = 'lines+markers',
name = pais,
visible = FALSE,
line = list(shape = "linear", width = 2),
marker = list(size = 6),
hovertemplate = paste0(
"<b>País:</b> ", pais,
"<br><b>Año:</b> %{x}",
"<br><b>Desempleo:</b> %{y:.1f}%<extra></extra>"
)
)
}
}
# ahora añado la media europea para que aparezca siempre para poder hacer comparativas
fig <- fig %>%
add_trace(
data = media_europa,
x = ~Year,
y = ~Unemployment_rate,
type = 'scatter',
mode = 'lines+markers',
name = "Media Europa",
line = list(dash = "dash", width = 4, color = "#E74C3C"),
marker = list(size = 10, color = "#E74C3C"),
visible = TRUE,
hovertemplate = paste0(
"<b>Media Europa</b><br>",
"Año: %{x}<br>",
"Tasa: %{y:.1f}%<extra></extra>"
)
)
# añadir funciones de interactividad
botones <- list(
list(
method = "update",
args = list(
list(visible = c(rep(FALSE, length(paises)), TRUE)),
list(
title = "Evolución del desempleo (solo Media Europa)",
yaxis = list(range = c(y_min_inicial, y_max_inicial))
)
),
label = "Solo Media Europa"
),
list(
method = "update",
args = list(
list(visible = c(rep(TRUE, length(paises)), TRUE)),
list(
title = "Evolución del desempleo - Todos los países",
yaxis = list(range = c(y_min_inicial, y_max_inicial))
)
),
label = "Todos los países"
)
)
for (i in seq_along(paises)) {
visible <- rep(FALSE, length(paises) + 1)
visible[i] <- TRUE
visible[length(visible)] <- TRUE
# para cada país, calcular el rango específico por lo que he dicho antes para que no se qude muy grande
df_pais_especifico <- datos_finales %>%
filter(Country %in% c(paises[i], "Media Europa"))
rango_min_pais <- min(df_pais_especifico$Unemployment_rate, na.rm = TRUE)
rango_max_pais <- max(df_pais_especifico$Unemployment_rate, na.rm = TRUE)
margen_pais <- (rango_max_pais - rango_min_pais) * 0.2
if(margen_pais < 0.5) margen_pais <- 0.5
y_min_pais <- max(0, rango_min_pais - margen_pais)
y_max_pais <- rango_max_pais + margen_pais
botones[[length(botones) + 1]] <- list(
method = "update",
args = list(
list(visible = visible),
list(
title = paste("Evolución del desempleo:", paises[i], "vs Media Europa"),
yaxis = list(range = c(y_min_pais, y_max_pais))
)
),
label = paises[i]
)
}
# gráfico final
fig <- fig %>%
layout(
title = list(
text = "Evolución del desempleo en Europa (2020–2024)",
font = list(size = 16)
),
xaxis = list(
title = "Año",
tickmode = "array",
tickvals = 2020:2024,
ticktext = as.character(2020:2024),
gridcolor = "#f0f0f0"
),
yaxis = list(
title = "Tasa de desempleo (%)",
range = c(y_min_inicial, y_max_inicial), # USAR RANGO INICIAL
gridcolor = "#f0f0f0",
zerolinecolor = "#cccccc",
tickformat = ".1f"
),
updatemenus = list(
list(
type = "dropdown",
active = 0,
buttons = botones,
x = 1.15,
y = 0.9,
bgcolor = "#f0f0f0",
bordercolor = "#333333",
font = list(size = 12)
)
),
legend = list(
title = list(text = "<b>País</b>"),
font = list(size = 10),
orientation = "h",
y = -0.3,
x = 0.5,
xanchor = "center"
),
hovermode = "closest",
showlegend = TRUE,
plot_bgcolor = "white",
paper_bgcolor = "white",
margin = list(
t = 50,
b = 100,
l = 60,
r = 100
)
)
fig <- fig %>%
layout(
annotations = list(
list(
x = 0.5,
y = -0.4,
xref = "paper",
yref = "paper",
text = "Selecciona un país en el menú desplegable para ver su evolución comparada con la media europea",
showarrow = FALSE,
font = list(size = 11, color = "gray"),
align = "center"
)
)
)
fig
```
------------------------------------------------------------------------
## 3- Mapa de densidad animado
Creamos un mapa de coropletas con diferentes colores para identificar la intensidad de desemplo en Europa. El mapa es animado y puede ir desde el año 2020 a 2024.
```{r}
#| label: mapa-animado-desempleo-europa
#| warning: false
#| message: false
#| fig-height: 2.5
#| out-width: "100%"
library(plotly)
library(dplyr)
library(viridis)
# filtrar los últimos años (2020-2024)
datos_mapa_animado <- tabla_desempleo %>%
filter(Year >= 2020 & Year <= 2024) %>%
mutate(
# limpiar nombres para leerlos claramente
iso_code = case_when(
Country == "Germany (until 1990 former territory of the FRG)" ~ "DEU",
Country == "Czechia" ~ "CZE",
Country == "Greece" ~ "GRC",
Country == "France" ~ "FRA",
Country == "Spain" ~ "ESP",
Country == "Italy" ~ "ITA",
Country == "United Kingdom" ~ "GBR",
Country == "Netherlands" ~ "NLD",
Country == "Belgium" ~ "BEL",
Country == "Portugal" ~ "PRT",
Country == "Sweden" ~ "SWE",
Country == "Poland" ~ "POL",
Country == "Austria" ~ "AUT",
Country == "Switzerland" ~ "CHE",
Country == "Denmark" ~ "DNK",
Country == "Norway" ~ "NOR",
Country == "Finland" ~ "FIN",
Country == "Ireland" ~ "IRL",
Country == "Hungary" ~ "HUN",
Country == "Slovakia" ~ "SVK",
Country == "Croatia" ~ "HRV",
Country == "Romania" ~ "ROU",
Country == "Bulgaria" ~ "BGR",
Country == "Lithuania" ~ "LTU",
Country == "Latvia" ~ "LVA",
Country == "Slovenia" ~ "SVN",
Country == "Estonia" ~ "EST",
Country == "Luxembourg" ~ "LUX",
Country == "Malta" ~ "MLT",
Country == "Cyprus" ~ "CYP",
Country == "Iceland" ~ "ISL",
Country == "Serbia" ~ "SRB",
Country == "North Macedonia" ~ "MKD",
Country == "Montenegro" ~ "MNE",
Country == "Bosnia and Herzegovina" ~ "BIH",
Country == "Albania" ~ "ALB",
Country == "Turkey" ~ "TUR",
TRUE ~ NA_character_
),
# mantener nombre legibles
nombre_pais = gsub(" \\(.*\\)", "", Country),
nombre_pais = gsub(" -.*", "", nombre_pais)
) %>%
filter(!is.na(iso_code) & !is.na(Unemployment_rate)) %>%
select(iso_code, nombre_pais, Year, Unemployment_rate) %>%
arrange(Year, iso_code)
# el mapa
mapa_animado <- plot_ly() %>%
add_trace(
type = "choropleth",
locations = datos_mapa_animado$iso_code,
z = datos_mapa_animado$Unemployment_rate,
frame = datos_mapa_animado$Year,
text = ~paste(
"<b>País:</b> ", datos_mapa_animado$nombre_pais,
"<br><b>Tasa de desempleo:</b> ", round(datos_mapa_animado$Unemployment_rate, 1), "%",
"<br><b>Año:</b> ", datos_mapa_animado$Year,
"<br><b>Código:</b> ", datos_mapa_animado$iso_code
),
hoverinfo = "text",
colorscale = "Blues",
reversescale = FALSE, # Azul oscuro = Más desempleo
marker = list(
line = list(color = "white", width = 0.5)
),
colorbar = list(
title = "Desempleo %",
thickness = 15,
len = 0.7,
x = 0.95,
y = 0.5
),
zmin = 0,
zmax = ceiling(max(datos_mapa_animado$Unemployment_rate, na.rm = TRUE) / 5) * 5,
showscale = TRUE
) %>%
layout(
title = list(
text = "EVOLUCIÓN DEL DESEMPLEO EN EUROPA (2020-2024)",
font = list(size = 20, family = "Arial, sans-serif"),
x = 0.05,
y = 0.98
),
geo = list(
scope = "europe",
projection = list(type = "mercator"),
showframe = FALSE,
showcoastlines = TRUE,
coastlinecolor = "#AAAAAA",
coastlinewidth = 0.5,
showcountries = TRUE,
countrycolor = "#FFFFFF",
countrywidth = 0.7,
showocean = TRUE,
oceancolor = "#E8F4F8",
showlakes = TRUE,
lakecolor = "#E8F4F8",
showland = TRUE,
landcolor = "#F5F5F5",
subunitwidth = 1,
center = list(lon = 10, lat = 52),
lonaxis = list(range = c(-20, 40)),
lataxis = list(range = c(34, 70))
),
margin = list(
t = 50,
b = 0,
l = 0,
r = 0
),
# las funciones interactivas
annotations = list(
list(
x = 0.5,
y = 0.05,
text = " Haz clic en PLAY para ver la evolución anual",
showarrow = FALSE,
xref = "paper",
yref = "paper",
font = list(size = 12, color = "#666666")
)
)
) %>%
animation_opts(
frame = 1000,
transition = 500,
redraw = TRUE,
easing = "cubic-in-out",
mode = "immediate"
) %>%
animation_button(
x = 0.1, y = 0, xanchor = "right", yanchor = "bottom"
)
mapa_animado
```
------------------------------------------------------------------------
## Easter Egg si has llegado hasta aquí
```{r}
#| label: easter-egg-sepe
#| echo: false
#| warning: false
#| message: false
htmltools::tagList(
htmltools::tags$details(
style = "
max-width:600px;
margin:40px auto;
padding:0;
border-radius:12px;
border: 2px solid #2563EB;
background:#F0F9FF;
box-shadow: 0 10px 25px rgba(37, 99, 235, 0.15);
cursor:pointer;
overflow: hidden;
",
htmltools::tags$summary(
style = "
padding: 20px;
font-size:16px;
font-weight:bold;
color:#2563EB;
background: white;
",
"Haz click aquí para ver una sorpresa"
),
htmltools::tags$div(
style = "
padding:20px;
background:white;
border-top: 1px solid #e5e7eb;
",
htmltools::tags$img(
src = "imagenes/imagen_01.jpg",
# Borde naranja vibrante
style = "width:100%; border-radius:10px; border:3px solid #FF5722;",
alt = "Oficina del SEPE"
),
htmltools::tags$p(
style = "
margin-top:15px;
font-style:italic;
color:#E65100;
background:#FFF3E0;
padding:12px;
border-left:4px solid #FF5722;
font-size:15px;
border-radius: 4px;
",
htmltools::tags$b(
"Donde voy a acabar yo también al terminar la carrera..."
)
)
)
)
)
```
------------------------------------------------------------------------
<br>
¡Con esto finalizo mi trabajo para la asignatura!
<br>
------------------------------------------------------------------------
<br>
### Información sobre la sesión
A continuación muestro mi entorno de trabajo y paquetes utilizados para garantizar la reproducibilidad.
```{r}
#| echo: false
sessioninfo::session_info() %>%
details::details(summary = 'current session info')
```