Barcelona ciutat morta? (2015-2025)

Author

Jordi Muñoz and dades de l’Ajuntament de Barcelona

Published

April 17, 2026

Code
library(tidyverse)
library(lubridate)
grafiques_dir <- "../output/grafiques"
dir.create(grafiques_dir, showWarnings = FALSE, recursive = TRUE)

Importació i fusió de dades

Importem tots els fitxers CSV i els fusionem en un sol dataframe. També gestionem els valors “..” que representen recomptes de població petits (emmascarament per privadesa), tractant-los com a 2.

Code
# Llista de tots els fitxers CSV
files <- list.files(path = "../data", pattern = "20.*_pad_mdbas_edat-1.csv", full.names = TRUE)

# Lectura i fusió
data_raw <- files %>%
  map_df(function(f) {
    read_csv(f, col_types = cols(.default = "c")) %>%
      mutate(Source_File = f)
  })

# Neteja de dades
data <- data_raw %>%
  mutate(
    # Convertim Valor a numèric, tractant ".." com a 2 (comú en els datasets de BCN)
    Valor = as.numeric(ifelse(Valor == "..", "2", Valor)),
    EDAT_1 = as.numeric(EDAT_1),
    Year = year(as.Date(Data_Referencia)),
    Nom_Districte = factor(Nom_Districte),
    Codi_Districte = as.numeric(Codi_Districte)
  ) %>%
  filter(!is.na(Valor))

# Població total per any i districte per a normalització
poblacio_total <- data %>%
  group_by(Year, Codi_Districte, Nom_Districte) %>%
  summarize(Poblacio_Total = sum(Valor), .groups = "drop")

# Resum de la població total per any
poblacio_total %>%
  group_by(Year) %>%
  summarize(Poblacio_Barcelona = sum(Poblacio_Total)) %>%
  knitr::kable()
Year Poblacio_Barcelona
2015 1601223
2016 1606819
2017 1621571
2018 1625427
2019 1646810
2020 1662668
2021 1656181
2022 1635478
2023 1655903
2024 1698181
2025 1727268

Distribució de la població per edat i districte

Code
pyramid_data <- data %>%
  group_by(Year, Nom_Districte, EDAT_1) %>%
  summarize(Total = sum(Valor), .groups = "drop")

selected_years <- c(2015, 2020, 2022, 2025)

p <- ggplot(pyramid_data %>% filter(Year %in% selected_years),
       aes(x = EDAT_1, y = Total)) +
  geom_bar(stat = "identity", fill = "steelblue", alpha = 0.8) +
  facet_grid(Nom_Districte ~ Year, scales = "free_x") +
  theme_minimal(base_size = 9) +
  labs(title = "Distribució de la població per edat i districte",
       x = "Edat",
       y = "Nombre de persones",
       caption = "Font: https://opendata-ajuntament.barcelona.cat") +
  theme(strip.text.y = element_text(angle = 0),
        plot.caption = element_text(size = 7, color = "grey50"))
print(p)

Code
ggsave(file.path(grafiques_dir, "piramides_districtes.png"), p, width = 12, height = 15)

Tendències demogràfiques al llarg del temps (12 anys o menys i majors de 65)

Code
trends_data <- data %>%
  mutate(Grup_Edat = case_when(
    EDAT_1 <= 12 ~ "12 anys o menys",
    EDAT_1 >= 65 ~ "Majors de 65",
    TRUE ~ "13-64"
  )) %>%
  group_by(Year, Nom_Districte, Grup_Edat) %>%
  summarize(Count = sum(Valor), .groups = "drop") %>%
  group_by(Year, Nom_Districte) %>%
  mutate(Percentatge = (Count / sum(Count)) * 100) %>%
  filter(Grup_Edat %in% c("12 anys o menys", "Majors de 65"))

p <- ggplot(trends_data, aes(x = Year, y = Percentatge, color = Grup_Edat)) +
  geom_line(size = 1) +
  geom_point() +
  facet_wrap(~Nom_Districte, ncol = 2) +
  scale_x_continuous(breaks = seq(2015, 2025, 2)) +
  theme_minimal() +
  labs(title = "Percentatge de població de 12 anys o menys i major de 65 per districte",
       x = "Any",
       y = "Percentatge (%)",
       color = "Grup d'edat",
       caption = "Font: https://opendata-ajuntament.barcelona.cat") +
  theme(legend.position = "bottom",
        plot.caption = element_text(size = 7, color = "grey50"))
print(p)

Code
ggsave(file.path(grafiques_dir, "tendencies_demografiques.png"), p, width = 10, height = 10)

Comparativa Per Càpita: Animals de companyia vs. Infants (fins a 5 anys)

En aquesta secció comparem el nombre d’animals de companyia i d’infants de 5 anys o menys per cada 1000 habitants.

Code
pet_files <- list.files(path = "../data/animals2", pattern = "*.csv", full.names = TRUE)

pets_raw <- pet_files %>%
  map_df(function(f) {
    any_fitxer <- as.numeric(str_extract(basename(f), "^[0-9]{4}"))
    df <- read_csv(f, col_types = cols(.default = "c")) %>% mutate(Year = any_fitxer)
    if ("DISTRICTE" %in% names(df)) df <- rename(df, Nom_Districte = DISTRICTE)
    if ("NOM_DISTRICTE" %in% names(df)) df <- rename(df, Nom_Districte = NOM_DISTRICTE)
    if ("NUM_ANIMALS" %in% names(df)) df <- rename(df, Num_Animals = NUM_ANIMALS)
    if ("NUM_ANIMAL" %in% names(df)) df <- rename(df, Num_Animals = NUM_ANIMAL)
    df %>% select(Year, Codi_Districte = CODI_DISTRICTE, Num_Animals) %>% mutate(Num_Animals = as.numeric(Num_Animals))
  })

pets_yearly <- pets_raw %>%
  group_by(Year, Codi_Districte) %>%
  summarize(Animals_Companyia = mean(Num_Animals, na.rm = TRUE), .groups = "drop") %>%
  mutate(Codi_Districte = as.numeric(Codi_Districte))

kids_yearly <- data %>%
  filter(EDAT_1 <= 5) %>%
  group_by(Year, Codi_Districte) %>%
  summarize(Infants_5 = sum(Valor), .groups = "drop")

comparison_data <- poblacio_total %>%
  left_join(kids_yearly, by = c("Year", "Codi_Districte")) %>%
  left_join(pets_yearly, by = c("Year", "Codi_Districte")) %>%
  filter(Year >= 2018) %>%
  mutate(
    Infants_per_1000 = (Infants_5 / Poblacio_Total) * 1000,
    Animals_per_1000 = (Animals_Companyia / Poblacio_Total) * 1000
  ) %>%
  pivot_longer(cols = c(Infants_per_1000, Animals_per_1000),
               names_to = "Categoria",
               values_to = "Ràtio") %>%
  mutate(Categoria = recode(Categoria,
                            "Infants_per_1000" = "Infants (≤5 anys)",
                            "Animals_per_1000" = "Animals de companyia"))

Ràtio a nivell de ciutat (per 1000 hab.)

Code
comparison_city <- comparison_data %>%
  group_by(Year, Categoria) %>%
  summarize(Ràtio = sum(Ràtio * Poblacio_Total) / sum(Poblacio_Total), .groups = "drop")

p <- ggplot(comparison_city, aes(x = Year, y = Ràtio, color = Categoria)) +
  geom_line(size = 1.2) +
  geom_point(size = 3) +
  scale_x_continuous(breaks = seq(2018, 2025, 1)) +
  theme_minimal() +
  labs(title = "Evolució Animals de companyia vs. Infants a Barcelona (Per 1000 hab.)",
       x = "Any",
       y = "Nombre per cada 1000 habitants",
       color = "Categoria",
       caption = "Font: https://opendata-ajuntament.barcelona.cat") +
  theme(legend.position = "bottom",
        plot.caption = element_text(size = 7, color = "grey50"))
print(p)

Code
ggsave(file.path(grafiques_dir, "animals_infants_ciutat.png"), p, width = 10, height = 6)

Ràtio per Districtes (per 1000 hab.)

Code
p <- ggplot(comparison_data, aes(x = Year, y = Ràtio, color = Categoria)) +
  geom_line() +
  geom_point(size = 1) +
  facet_wrap(~Nom_Districte, scales = "free_y", ncol = 2) +
  scale_x_continuous(breaks = seq(2018, 2025, 2)) +
  theme_minimal() +
  labs(title = "Animals de companyia vs. Infants per districte (Per 1000 hab.)",
       x = "Any",
       y = "Nombre per cada 1000 habitants",
       color = "Categoria",
       caption = "Font: https://opendata-ajuntament.barcelona.cat") +
  theme(legend.position = "bottom",
        plot.caption = element_text(size = 7, color = "grey50"))
print(p)

Code
ggsave(file.path(grafiques_dir, "animals_infants_districtes.png"), p, width = 10, height = 12)

Evolució de la població de 3 i 12 anys per districte i per la ciutat

Per districte

Code
edats_clau_districtes <- data %>%
  filter(EDAT_1 %in% c(3, 12)) %>%
  group_by(Year, Nom_Districte, EDAT_1) %>%
  summarize(Total = sum(Valor), .groups = "drop") %>%
  mutate(Edat = factor(paste(EDAT_1, "anys")))

p <- ggplot(edats_clau_districtes, aes(x = Year, y = Total, color = Edat, group = Edat)) +
  geom_line(size = 1) +
  geom_point(size = 2) +
  facet_wrap(~Nom_Districte, ncol = 2, scales = "free_y") +
  scale_x_continuous(breaks = seq(2015, 2025, 2)) +
  theme_minimal() +
  labs(title = "Evolució de la població de 3 i 12 anys per districte",
       x = "Any",
       y = "Nombre d'habitants",
       color = "Edat",
       caption = "Font: https://opendata-ajuntament.barcelona.cat") +
  theme(legend.position = "bottom",
        plot.caption = element_text(size = 7, color = "grey50"))
print(p)

Code
ggsave(file.path(grafiques_dir, "edats_clau_districtes.png"), p, width = 12, height = 14)

Per tota la ciutat

Code
edats_clau_ciutat <- data %>%
  filter(EDAT_1 %in% c(3, 12)) %>%
  group_by(Year, EDAT_1) %>%
  summarize(Total = sum(Valor), .groups = "drop") %>%
  mutate(Edat = factor(paste(EDAT_1, "anys")))

p <- ggplot(edats_clau_ciutat, aes(x = Year, y = Total, color = Edat, group = Edat)) +
  geom_line(size = 1) +
  geom_point(size = 2) +
  geom_text(aes(label = Total), vjust = -1, size = 3, show.legend = FALSE) +
  scale_x_continuous(breaks = seq(2015, 2025, 1)) +
  theme_minimal() +
  labs(title = "Evolució de la població de 3 i 12 anys a Barcelona",
       x = "Any",
       y = "Nombre d'habitants",
       color = "Edat",
       caption = "Font: https://opendata-ajuntament.barcelona.cat") +
  theme(legend.position = "bottom",
        plot.caption = element_text(size = 7, color = "grey50"))
print(p)

Code
ggsave(file.path(grafiques_dir, "edats_clau_ciutat.png"), p, width = 10, height = 5)

Renda de les llars i proporció d’infants per barri

Analitzem la relació entre la renda disponible de les llars per persona (2022, agregada per barri com a mitjana de les seccions censals) i la proporció d’infants de 12 anys o menys i de 5 anys o menys sobre la població total del barri (2025). Cada punt representa un barri, i el color indica el districte.

Code
income_raw <- read_csv("../data/2022_renda_disponible_llars_per_persona.csv",
                       col_types = cols(.default = "c")) %>%
  mutate(Import_Euros = as.numeric(Import_Euros),
         Codi_Barri   = as.numeric(Codi_Barri))

income_barri <- income_raw %>%
  group_by(Codi_Barri, Nom_Barri, Codi_Districte, Nom_Districte) %>%
  summarize(Renda_Mitjana = mean(Import_Euros, na.rm = TRUE), .groups = "drop") %>%
  mutate(Codi_Districte = as.numeric(Codi_Districte))

pop_barri_2025 <- read_csv("../data/2025_pad_mdbas_edat-1.csv",
                           col_types = cols(.default = "c")) %>%
  mutate(Valor    = as.numeric(ifelse(Valor == "..", "2", Valor)),
         EDAT_1   = as.numeric(EDAT_1),
         Codi_Barri = as.numeric(Codi_Barri)) %>%
  filter(!is.na(Valor)) %>%
  group_by(Codi_Barri, Nom_Barri, Codi_Districte, Nom_Districte) %>%
  summarize(
    Poblacio_Total = sum(Valor),
    Infants_12     = sum(Valor[EDAT_1 <= 12]),
    Infants_5      = sum(Valor[EDAT_1 <= 5]),
    .groups = "drop"
  ) %>%
  mutate(
    Prop_Infants_12 = Infants_12 / Poblacio_Total * 100,
    Prop_Infants_5  = Infants_5  / Poblacio_Total * 100,
    Codi_Districte  = as.numeric(Codi_Districte)
  )

renda_infants <- income_barri %>%
  inner_join(pop_barri_2025, by = c("Codi_Barri", "Codi_Districte")) %>%
  select(Codi_Barri, Nom_Barri = Nom_Barri.x, Nom_Districte = Nom_Districte.x,
         Renda_Mitjana, Prop_Infants_12, Prop_Infants_5) %>%
  pivot_longer(cols = c(Prop_Infants_12, Prop_Infants_5),
               names_to = "Grup",
               values_to = "Proporcio") %>%
  mutate(Grup = recode(Grup,
                       "Prop_Infants_12" = "Infants ≤12 anys",
                       "Prop_Infants_5"  = "Infants ≤5 anys"))
Code
p <- ggplot(renda_infants, aes(x = Renda_Mitjana, y = Proporcio, color = Nom_Districte)) +
  geom_point(alpha = 0.7, size = 2) +
  geom_smooth(method = "lm", formula = y ~ poly(x, 2), se = TRUE, color = "grey30", linewidth = 0.8) +
  facet_wrap(~Grup, scales = "free_y") +
  scale_x_continuous(labels = scales::comma) +
  theme_minimal() +
  labs(title = "Renda de les llars i proporció d'infants per barri (Barcelona)",
       subtitle = "Renda: 2022 (mitjana per seccions censals) · Població: 2025",
       x = "Renda disponible mitjana per persona (€)",
       y = "Proporció sobre la població del barri (%)",
       color = "Districte",
       caption = "Font: https://opendata-ajuntament.barcelona.cat") +
  theme(legend.position = "bottom",
        legend.text = element_text(size = 7),
        plot.caption = element_text(size = 7, color = "grey50"))
print(p)

Code
ggsave(file.path(grafiques_dir, "renda_infants_barri.png"), p, width = 12, height = 8)

Composició de les llars per presència de menors

Les dades classifiquen les llars per tipologia de convivència, no pel nombre exacte de fills. Usem la classificació documentada (codis 1–12) agrupada en quatre categories: llars sense cap menor de 18 anys, llars monoparentals amb menors, llars biparentals amb menors, i altres llars amb menors. Els codis addicionals (>12) no estan documentats i representen un volum marginal, per la qual cosa s’exclouen.

Code
dom_raw <- read_csv("../data/2025_pad_dom_mdbas_tipus-domicili.csv",
                    col_types = cols(.default = "c")) %>%
  mutate(Valor          = as.numeric(Valor),
         TIPUS_DOMICILI = as.integer(TIPUS_DOMICILI),
         Codi_Barri     = as.numeric(Codi_Barri),
         Codi_Districte = as.numeric(Codi_Districte)) %>%
  filter(!is.na(Valor), TIPUS_DOMICILI %in% 1:12) %>%
  mutate(Grup_Llar = case_when(
    TIPUS_DOMICILI %in% 1:8  ~ "Sense menors",
    TIPUS_DOMICILI %in% 9:10 ~ "Monoparental amb menors",
    TIPUS_DOMICILI == 11     ~ "Biparental amb menors",
    TIPUS_DOMICILI == 12     ~ "Altres amb menors"
  ))

llars_barri <- dom_raw %>%
  group_by(Codi_Barri, Nom_Barri, Codi_Districte, Nom_Districte, Grup_Llar) %>%
  summarize(Llars = sum(Valor), .groups = "drop") %>%
  group_by(Codi_Barri) %>%
  mutate(Total_Llars = sum(Llars),
         Proporcio   = Llars / Total_Llars * 100) %>%
  ungroup() %>%
  mutate(Grup_Llar = factor(Grup_Llar, levels = c(
    "Sense menors", "Monoparental amb menors",
    "Biparental amb menors", "Altres amb menors")))

Proporció per barri

Code
barri_order <- llars_barri %>%
  filter(Grup_Llar == "Sense menors") %>%
  arrange(Proporcio) %>%
  pull(Nom_Barri)

p <- llars_barri %>%
  mutate(Nom_Barri = factor(Nom_Barri, levels = barri_order)) %>%
  ggplot(aes(x = Proporcio, y = Nom_Barri, fill = Grup_Llar)) +
  geom_col(alpha = 0.9) +
  scale_fill_brewer(palette = "Set2") +
  theme_minimal(base_size = 9) +
  labs(title = "Composició de les llars per barri (Barcelona, 2025)",
       x = "% sobre el total de llars del barri",
       y = NULL,
       fill = "Tipologia",
       caption = "Font: https://opendata-ajuntament.barcelona.cat") +
  theme(legend.position = "bottom",
        legend.text = element_text(size = 8),
        plot.caption = element_text(size = 7, color = "grey50"))
print(p)

Code
ggsave(file.path(grafiques_dir, "llars_tipus_barri.png"), p, width = 10, height = 18)

Relació amb la renda per barri

Code
llars_renda <- llars_barri %>%
  inner_join(income_barri %>% select(Codi_Barri, Renda_Mitjana),
             by = "Codi_Barri")

p <- ggplot(llars_renda, aes(x = Renda_Mitjana, y = Proporcio, color = Nom_Districte)) +
  geom_point(alpha = 0.7, size = 2) +
  geom_smooth(method = "lm", formula = y ~ poly(x, 2), se = TRUE,
              color = "grey30", linewidth = 0.8) +
  facet_wrap(~Grup_Llar, scales = "free_y", ncol = 2) +
  scale_x_continuous(labels = scales::comma) +
  theme_minimal() +
  labs(title = "Renda de les llars i tipologia de llar per barri (Barcelona)",
       subtitle = "Renda: 2022 · Llars: 2025",
       x = "Renda disponible mitjana per persona (€)",
       y = "% sobre el total de llars del barri",
       color = "Districte",
       caption = "Font: https://opendata-ajuntament.barcelona.cat") +
  theme(legend.position = "bottom",
        legend.text = element_text(size = 7),
        strip.text = element_text(face = "bold"),
        plot.caption = element_text(size = 7, color = "grey50"))
print(p)

Code
ggsave(file.path(grafiques_dir, "llars_renda_barri.png"), p, width = 12, height = 9)

Anàlisi específica del districte de Gràcia

Evolució de la composició per edats (Piràmides)

Code
gracia_data <- data %>% filter(Nom_Districte == "Gràcia")
anys_interes <- c(2015, 2018, 2021, 2025)

p <- ggplot(gracia_data %>% filter(Year %in% anys_interes),
       aes(x = EDAT_1, y = Valor)) +
  geom_bar(stat = "identity", fill = "darkorange", alpha = 0.7) +
  coord_flip() +
  facet_wrap(~Year, ncol = 2) +
  theme_minimal() +
  labs(title = "Piràmides d'edat al districte de Gràcia",
       x = "Edat",
       y = "Població",
       caption = "Font: https://opendata-ajuntament.barcelona.cat") +
  theme(plot.caption = element_text(size = 7, color = "grey50"))
print(p)

Code
ggsave(file.path(grafiques_dir, "piramides_gracia.png"), p, width = 12, height = 10)

Població de 3 i 12 anys a Gràcia

Code
gracia_clau <- gracia_data %>%
  filter(EDAT_1 %in% c(3, 12)) %>%
  group_by(Year, EDAT_1) %>%
  summarize(Total = sum(Valor), .groups = "drop") %>%
  mutate(Edat = factor(paste(EDAT_1, "anys")))

p <- ggplot(gracia_clau, aes(x = Year, y = Total, color = Edat, group = Edat)) +
  geom_line(size = 1) +
  geom_point(size = 2) +
  geom_text(aes(label = Total), vjust = -1, size = 3, show.legend = FALSE) +
  scale_x_continuous(breaks = seq(2015, 2025, 1)) +
  theme_minimal() +
  labs(title = "Evolució de la població de 3 i 12 anys a Gràcia",
       x = "Any",
       y = "Nombre d'infants",
       caption = "Font: https://opendata-ajuntament.barcelona.cat") +
  theme(plot.caption = element_text(size = 7, color = "grey50"))
print(p)

Code
ggsave(file.path(grafiques_dir, "edats_clau_gracia.png"), p, width = 10, height = 6)

Animals de companyia vs. Infants (≤5 anys) a Gràcia (Per 1000 hab.)

Code
kids5_gracia <- gracia_data %>%
  filter(EDAT_1 <= 5) %>%
  group_by(Year) %>%
  summarize(Infants_5 = sum(Valor), .groups = "drop")

poblacio_gracia <- poblacio_total %>% filter(Nom_Districte == "Gràcia")

gracia_comp <- poblacio_gracia %>%
  left_join(kids5_gracia, by = "Year") %>%
  left_join(pets_yearly %>% filter(Codi_Districte == 6), by = "Year") %>%
  filter(Year >= 2018) %>%
  mutate(
    Infants_per_1000 = (Infants_5 / Poblacio_Total) * 1000,
    Animals_per_1000 = (Animals_Companyia / Poblacio_Total) * 1000
  ) %>%
  pivot_longer(cols = c(Infants_per_1000, Animals_per_1000), names_to = "Categoria", values_to = "Ràtio") %>%
  mutate(Categoria = recode(Categoria,
                            "Infants_per_1000" = "Infants (≤5 anys)",
                            "Animals_per_1000" = "Animals de companyia"))

p <- ggplot(gracia_comp, aes(x = Year, y = Ràtio, color = Categoria)) +
  geom_line(size = 1.2) +
  geom_point() +
  scale_x_continuous(breaks = seq(2018, 2025, 1)) +
  theme_minimal() +
  labs(title = "Gràcia: Animals de companyia vs. Infants (≤5 anys) per 1000 hab.",
       x = "Any",
       y = "Ràtio per 1000 habitants",
       color = "Categoria",
       caption = "Font: https://opendata-ajuntament.barcelona.cat") +
  theme(legend.position = "bottom",
        plot.caption = element_text(size = 7, color = "grey50"))
print(p)

Code
ggsave(file.path(grafiques_dir, "animals_infants_gracia.png"), p, width = 10, height = 6)