This is an R Markdown document. Markdown is a simple formatting syntax for authoring HTML, PDF, and MS Word documents. For more details on using R Markdown see http://rmarkdown.rstudio.com.
When you click the Knit button a document will be generated that includes both content as well as the output of any embedded R code chunks within the document. You can embed an R code chunk like this:
ruta <- "C:/Users/diego/Documents/Diego/Estudios/Data science/Visualización de datos/Practica/alquiler_serpavi.xlsx"
serpavi_raw <- read_excel(ruta, sheet = 3)
serpavi_sel_vc <- serpavi_raw %>%
select(
provincia = LITPRO,
matches("^ALQM2_LV_M_VC_[0-9]{2}$"),
matches("^SLVM2_M_VC_[0-9]{2}$")
)
serpavi_wide_vc <- serpavi_sel_vc %>%
pivot_longer(
cols = -provincia,
names_to = c("var", "anio2"),
names_pattern = "^(ALQM2_LV_M_VC|SLVM2_M_VC)_([0-9]{2})$",
values_to = "valor"
) %>%
mutate(anio = 2000 + as.integer(anio2)) %>%
select(-anio2) %>%
pivot_wider(names_from = var, values_from = valor) %>%
rename(
alquilerm2 = ALQM2_LV_M_VC,
superficie = SLVM2_M_VC
) %>%
mutate(alquiler_mensual_estimado = alquilerm2 * superficie) %>%
select(provincia, anio, alquilerm2, superficie, alquiler_mensual_estimado) %>%
pivot_wider(
names_from = anio,
values_from = c(alquilerm2, superficie, alquiler_mensual_estimado),
names_glue = "{.value}_{anio}"
) %>%
arrange(provincia)
serpavi_final <- serpavi_wide_vc %>%
select(
provincia,
matches("_(2015|2016|2017|2018|2019|2020|2021|2022|2023)$")
)
You can also embed plots, for example:
ruta_ine <- "C:/Users/diego/Documents/Diego/Estudios/Data science/Visualización de datos/Practica/ine_renta.csv"
ine_raw <- read_delim(
file = ruta_ine,
delim = ";",
locale = locale(encoding = "UTF-8"),
show_col_types = FALSE,
trim_ws = TRUE
)
ine_hogar <- ine_raw %>%
filter(
!is.na(Provincias),
`Indicadores de renta media` == "Renta neta media por hogar"
) %>%
mutate(
anio = as.integer(Periodo),
renta_hogar = parse_number(
as.character(Total),
locale = locale(decimal_mark = ",", grouping_mark = ".")
)
) %>%
select(
provincia = Provincias,
anio,
renta_hogar
)
## Warning: There was 1 warning in `mutate()`.
## ℹ In argument: `renta_hogar = parse_number(...)`.
## Caused by warning:
## ! 1 parsing failure.
## row col expected actual
## 531 -- a number ..
ine_final <- ine_hogar %>%
group_by(provincia, anio) %>%
summarise(renta_hogar = mean(renta_hogar, na.rm = TRUE), .groups = "drop") %>%
pivot_wider(
names_from = anio,
values_from = renta_hogar,
names_prefix = "renta_hogar_"
) %>%
arrange(provincia)
library(dplyr)
years <- 2015:2023
# Unimos manteniendo TODO lo que ya tienes en servavi_final
# y añadiendo las rentas del ine_final
dataset_final <- serpavi_final %>%
left_join(ine_final, by = "provincia")
# Asegurar que exista alquiler_mensual_estimado_YYYY
# (por si en algún punto no se generó o falta algún año)
for (y in years) {
col_m2 <- paste0("alquilerm2_", y)
col_su <- paste0("superficie_", y)
col_am <- paste0("alquiler_mensual_estimado_", y)
if (!(col_am %in% names(dataset_final)) &&
all(c(col_m2, col_su) %in% names(dataset_final))) {
dataset_final[[col_am]] <- dataset_final[[col_m2]] * dataset_final[[col_su]]
}
}
# Crear renta mensual por año (renta_hogar_YYYY / 12)
for (y in years) {
col_ra <- paste0("renta_hogar_", y)
col_rm <- paste0("renta_mensual_", y)
if (col_ra %in% names(dataset_final)) {
dataset_final[[col_rm]] <- dataset_final[[col_ra]] / 12
}
}
# Calcular esfuerzo (%) por año
# esfuerzo_YYYY = alquiler_mensual_estimado_YYYY / renta_mensual_YYYY * 100
for (y in years) {
col_am <- paste0("alquiler_mensual_estimado_", y)
col_rm <- paste0("renta_mensual_", y)
col_es <- paste0("esfuerzo_", y)
if (all(c(col_am, col_rm) %in% names(dataset_final))) {
dataset_final[[col_es]] <- (dataset_final[[col_am]] / dataset_final[[col_rm]]) * 100
}
}
# Resultado
names(dataset_final)
## [1] "provincia" "alquilerm2_2015"
## [3] "alquilerm2_2016" "alquilerm2_2017"
## [5] "alquilerm2_2018" "alquilerm2_2019"
## [7] "alquilerm2_2020" "alquilerm2_2021"
## [9] "alquilerm2_2022" "alquilerm2_2023"
## [11] "superficie_2015" "superficie_2016"
## [13] "superficie_2017" "superficie_2018"
## [15] "superficie_2019" "superficie_2020"
## [17] "superficie_2021" "superficie_2022"
## [19] "superficie_2023" "alquiler_mensual_estimado_2015"
## [21] "alquiler_mensual_estimado_2016" "alquiler_mensual_estimado_2017"
## [23] "alquiler_mensual_estimado_2018" "alquiler_mensual_estimado_2019"
## [25] "alquiler_mensual_estimado_2020" "alquiler_mensual_estimado_2021"
## [27] "alquiler_mensual_estimado_2022" "alquiler_mensual_estimado_2023"
## [29] "renta_hogar_2015" "renta_hogar_2016"
## [31] "renta_hogar_2017" "renta_hogar_2018"
## [33] "renta_hogar_2019" "renta_hogar_2020"
## [35] "renta_hogar_2021" "renta_hogar_2022"
## [37] "renta_hogar_2023" "renta_mensual_2015"
## [39] "renta_mensual_2016" "renta_mensual_2017"
## [41] "renta_mensual_2018" "renta_mensual_2019"
## [43] "renta_mensual_2020" "renta_mensual_2021"
## [45] "renta_mensual_2022" "renta_mensual_2023"
## [47] "esfuerzo_2015" "esfuerzo_2016"
## [49] "esfuerzo_2017" "esfuerzo_2018"
## [51] "esfuerzo_2019" "esfuerzo_2020"
## [53] "esfuerzo_2021" "esfuerzo_2022"
## [55] "esfuerzo_2023"
# ¿Dónde supone mayor esfuerzo el alquiler?
# Top provincias por esfuerzo en 2023 + evolución
# Tabla resumen
dataset_final %>%
select(provincia, esfuerzo_2015, esfuerzo_2023) %>%
mutate(
variacion = esfuerzo_2023 - esfuerzo_2015,
tendencia = if_else(variacion > 0, "↑ Empeora", "↓ Mejora")
) %>%
arrange(desc(esfuerzo_2023)) %>%
print(n = Inf)
## # A tibble: 52 × 5
## provincia esfuerzo_2015 esfuerzo_2023 variacion tendencia
## <chr> <dbl> <dbl> <dbl> <chr>
## 1 Málaga 24.5 24.8 0.216 ↑ Empeora
## 2 Sevilla 22.1 21.5 -0.668 ↓ Mejora
## 3 Barcelona 19.8 20.6 0.839 ↑ Empeora
## 4 Balears, Illes 19.9 20.2 0.297 ↑ Empeora
## 5 Madrid 19.2 19.9 0.663 ↑ Empeora
## 6 Cuenca 21.2 19.6 -1.56 ↓ Mejora
## 7 Cádiz 20.5 19.6 -0.928 ↓ Mejora
## 8 Córdoba 21.0 19.6 -1.46 ↓ Mejora
## 9 Granada 19.2 19.1 -0.105 ↓ Mejora
## 10 Santa Cruz de Tenerife 19.5 19.0 -0.517 ↓ Mejora
## 11 Gipuzkoa NA 18.9 NA <NA>
## 12 Huelva 19.7 18.6 -1.08 ↓ Mejora
## 13 Badajoz 19.3 18.3 -0.994 ↓ Mejora
## 14 Toledo 18.0 18.0 0.0666 ↑ Empeora
## 15 Guadalajara 17.3 18.0 0.627 ↑ Empeora
## 16 Ceuta 19.5 17.9 -1.53 ↓ Mejora
## 17 Albacete 19.0 17.9 -1.02 ↓ Mejora
## 18 Salamanca 18.9 17.8 -1.06 ↓ Mejora
## 19 Almería 18.8 17.8 -0.984 ↓ Mejora
## 20 Palmas, Las 18.6 17.7 -0.891 ↓ Mejora
## 21 Pontevedra 18.3 17.6 -0.696 ↓ Mejora
## 22 Cáceres 18.7 17.5 -1.25 ↓ Mejora
## 23 Cantabria 17.6 17.2 -0.459 ↓ Mejora
## 24 Jaén 18.7 17.1 -1.60 ↓ Mejora
## 25 Ávila 17.1 16.9 -0.206 ↓ Mejora
## 26 Valladolid 16.9 16.9 -0.0525 ↓ Mejora
## 27 Murcia 16.8 16.6 -0.182 ↓ Mejora
## 28 Girona 17.1 16.6 -0.533 ↓ Mejora
## 29 Asturias 16.8 16.5 -0.362 ↓ Mejora
## 30 Segovia 17.8 16.4 -1.38 ↓ Mejora
## 31 Burgos 16.7 16.4 -0.335 ↓ Mejora
## 32 Tarragona 16.5 16.4 -0.145 ↓ Mejora
## 33 Soria 16.4 16.4 -0.0598 ↓ Mejora
## 34 Rioja, La 16.6 16.4 -0.230 ↓ Mejora
## 35 Zaragoza 16.1 16.3 0.199 ↑ Empeora
## 36 Navarra NA 16.3 NA <NA>
## 37 Zamora 17.5 16.1 -1.41 ↓ Mejora
## 38 León 16.3 16.0 -0.344 ↓ Mejora
## 39 Melilla 17.5 16.0 -1.54 ↓ Mejora
## 40 Palencia 16.3 15.7 -0.617 ↓ Mejora
## 41 Ciudad Real 16.8 15.6 -1.22 ↓ Mejora
## 42 Ourense 16.9 15.5 -1.39 ↓ Mejora
## 43 Coruña, A 16.3 15.4 -0.898 ↓ Mejora
## 44 Castellón/Castelló 14.4 15.1 0.664 ↑ Empeora
## 45 Lleida 15.2 14.7 -0.539 ↓ Mejora
## 46 Huesca 14.5 14.6 0.110 ↑ Empeora
## 47 Teruel 14.4 14.6 0.209 ↑ Empeora
## 48 Lugo 14.9 14.2 -0.640 ↓ Mejora
## 49 Alicante NA NA NA <NA>
## 50 Araba/Álava NA NA NA <NA>
## 51 Bizkaia NA NA NA <NA>
## 52 Valencia/Valéncia NA NA NA <NA>
# Gráfico: ranking 2023 coloreado por variación
dataset_final %>%
select(provincia, esfuerzo_2015, esfuerzo_2023) %>%
drop_na(esfuerzo_2023) %>%
mutate(
variacion = esfuerzo_2023 - esfuerzo_2015,
provincia = fct_reorder(provincia, esfuerzo_2023)
) %>%
ggplot(aes(x = esfuerzo_2023, y = provincia, fill = variacion)) +
geom_col() +
geom_vline(xintercept = 30, linetype = "dashed", color = "red", linewidth = 0.8) +
annotate("text", x = 31, y = 1, label = "Umbral 30%", color = "red", hjust = 0, size = 3.5) +
scale_fill_gradient2(low = "#2ECC71", mid = "#F7DC6F", high = "#E74C3C", midpoint = 0) +
scale_x_continuous(labels = scales::percent_format(scale = 1)) +
labs(title = "Esfuerzo económico para el alquiler por provincia (2023)",
subtitle = "Color = variación respecto a 2015 | Línea roja = umbral crítico 30%",
x = "% renta destinado al alquiler", y = NULL,
fill = "Variación\n2015–2023 (pp)") +
theme_minimal(base_size = 13)
# ¿Relación renta vs precio de alquiler? ¿Hay desajustes?
# Correlación por año
años <- 2015:2023
correlaciones <- map_dfr(años, function(a) {
tibble(
año = a,
correlacion = cor(
dataset_final[[paste0("renta_mensual_", a)]],
dataset_final[[paste0("alquilerm2_", a)]],
use = "complete.obs"
)
)
})
print(correlaciones)
## # A tibble: 9 × 2
## año correlacion
## <int> <dbl>
## 1 2015 0.791
## 2 2016 0.783
## 3 2017 0.787
## 4 2018 0.795
## 5 2019 0.793
## 6 2020 0.767
## 7 2021 0.765
## 8 2022 0.811
## 9 2023 0.833
# Scatter renta vs alquiler con línea de tendencia y outliers etiquetados
dataset_final %>%
select(provincia, renta_mensual_2023, alquilerm2_2023, esfuerzo_2023) %>%
drop_na() %>%
mutate(
desajuste = as.vector(scale(alquilerm2_2023)) - as.vector(scale(renta_mensual_2023)),
es_outlier = abs(desajuste) > 1
) %>%
ggplot(aes(x = renta_mensual_2023, y = alquilerm2_2023)) +
geom_point(aes(color = esfuerzo_2023, size = esfuerzo_2023), alpha = 0.8) +
geom_smooth(method = "lm", se = TRUE, color = "gray40", linetype = "dashed") +
ggrepel::geom_text_repel(
aes(label = if_else(es_outlier, provincia, NA_character_)),
size = 3.2, max.overlaps = 20
) +
scale_color_gradient(low = "#2ECC71", high = "#E74C3C") +
scale_x_continuous(labels = scales::dollar_format(suffix = "€", prefix = "")) +
scale_y_continuous(labels = scales::dollar_format(suffix = "€/m²", prefix = "")) +
labs(title = "Relación entre renta mensual y precio de alquiler (2023)",
subtitle = "Provincias etiquetadas = desajuste significativo entre renta y alquiler",
x = "Renta mensual del hogar (€)", y = "Precio alquiler €/m²",
color = "Esfuerzo (%)", size = "Esfuerzo (%)") +
theme_minimal(base_size = 13)
## `geom_smooth()` using formula = 'y ~ x'
## Warning: Removed 45 rows containing missing values or values outside the scale range
## (`geom_text_repel()`).
# ¿Qué diferencias territoriales existen?
# Heatmap completo con clustering por similitud
library(tidyverse)
mat_esfuerzo <- dataset_final %>%
select(provincia, starts_with("esfuerzo_")) %>%
column_to_rownames("provincia")
# Ordenar provincias por esfuerzo medio (agrupa similares visualmente)
orden <- mat_esfuerzo %>%
rowMeans(na.rm = TRUE) %>%
sort(decreasing = TRUE) %>%
names()
dataset_final %>%
pivot_longer(starts_with("esfuerzo_"), names_to = "año",
values_to = "esfuerzo", names_prefix = "esfuerzo_") %>%
mutate(
año = as.integer(año),
provincia = factor(provincia, levels = orden)
) %>%
ggplot(aes(x = año, y = provincia, fill = esfuerzo)) +
geom_tile(color = "white", linewidth = 0.4) +
geom_text(aes(label = round(esfuerzo, 1)), size = 2.5, color = "black") +
scale_fill_gradient2(low = "#2ECC71", mid = "#F7DC6F", high = "#E74C3C",
midpoint = 30) +
scale_x_continuous(breaks = 2015:2023) +
labs(title = "Esfuerzo de alquiler por provincia y año (%)",
subtitle = "Ordenado por esfuerzo medio 2015–2023",
x = NULL, y = NULL, fill = "Esfuerzo (%)") +
theme_minimal(base_size = 12) +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
## Warning: Removed 49 rows containing missing values or values outside the scale range
## (`geom_text()`).
# Boxplot de dispersión entre provincias por año
dataset_final %>%
pivot_longer(starts_with("esfuerzo_"), names_to = "año",
values_to = "esfuerzo", names_prefix = "esfuerzo_") %>%
mutate(año = as.integer(año)) %>%
ggplot(aes(x = factor(año), y = esfuerzo)) +
geom_boxplot(fill = "#3498DB", alpha = 0.5, outlier.shape = NA) +
geom_jitter(aes(color = provincia), width = 0.15, size = 2, alpha = 0.7) +
scale_y_continuous(labels = scales::percent_format(scale = 1)) +
labs(title = "Dispersión territorial del esfuerzo por año",
subtitle = "Cada punto es una provincia — la caja muestra la distribución",
x = NULL, y = "Esfuerzo (%)", color = "Provincia") +
theme_minimal(base_size = 13) +
theme(legend.position = "right")
## Warning: Removed 49 rows containing non-finite outside the scale range
## (`stat_boxplot()`).
## Warning: Removed 49 rows containing missing values or values outside the scale range
## (`geom_point()`).
# ¿Cómo ha evolucionado la accesibilidad?
# Evolución del esfuerzo medio nacional + rango entre provincias
dataset_final %>%
pivot_longer(starts_with("esfuerzo_"), names_to = "año",
values_to = "esfuerzo", names_prefix = "esfuerzo_") %>%
mutate(año = as.integer(año)) %>%
group_by(año) %>%
summarise(
media = mean(esfuerzo, na.rm = TRUE),
minimo = min(esfuerzo, na.rm = TRUE),
maximo = max(esfuerzo, na.rm = TRUE),
p25 = quantile(esfuerzo, 0.25, na.rm = TRUE),
p75 = quantile(esfuerzo, 0.75, na.rm = TRUE)
) %>%
ggplot(aes(x = año)) +
geom_ribbon(aes(ymin = minimo, ymax = maximo), fill = "#E74C3C", alpha = 0.15) +
geom_ribbon(aes(ymin = p25, ymax = p75), fill = "#E74C3C", alpha = 0.30) +
geom_line(aes(y = media), color = "#E74C3C", linewidth = 1.4) +
geom_point(aes(y = media), color = "#E74C3C", size = 3) +
geom_hline(yintercept = 30, linetype = "dashed", color = "gray40") +
annotate("text", x = 2015, y = 31, label = "Umbral crítico 30%",
hjust = 0, size = 3.5, color = "gray40") +
scale_x_continuous(breaks = 2015:2023) +
scale_y_continuous(labels = scales::percent_format(scale = 1)) +
labs(title = "Evolución de la accesibilidad al alquiler (2015–2023)",
subtitle = "Línea = media nacional | Banda oscura = P25–P75 | Banda clara = rango total",
x = NULL, y = "Esfuerzo (%)") +
theme_minimal(base_size = 13)
# Índice encadenado base 2015=100 para comparar alquiler vs renta
dataset_final %>%
summarise(across(starts_with("alquilerm2_") | starts_with("renta_mensual_"),
~ mean(.x, na.rm = TRUE))) %>%
pivot_longer(everything(), names_to = "variable", values_to = "valor") %>%
mutate(
año = as.integer(str_extract(variable, "\\d{4}")),
tipo = case_when(
str_detect(variable, "alquilerm2") ~ "Precio alquiler €/m²",
str_detect(variable, "renta_mensual") ~ "Renta mensual"
)
) %>%
group_by(tipo) %>%
mutate(indice = valor / first(valor) * 100) %>%
ggplot(aes(x = año, y = indice, color = tipo, group = tipo)) +
geom_line(linewidth = 1.4) +
geom_point(size = 3) +
geom_hline(yintercept = 100, linetype = "dashed", color = "gray60") +
scale_x_continuous(breaks = 2015:2023) +
scale_color_manual(values = c("#E74C3C", "#3498DB")) +
labs(title = "Alquiler vs Renta — Índice base 2015 = 100",
subtitle = "Si el alquiler crece más rápido que la renta, la accesibilidad empeora",
x = NULL, y = "Índice (2015 = 100)", color = NULL) +
theme_minimal(base_size = 13) +
theme(legend.position = "top")