Nightlife of Barcelona Neighborhoods

Jose M Sallan 2025-12-07 11 min read

In a previous post, I presented an analysis of the Barcelona nightlife at the district level. In this post, I will make an analysis at the neighbourhood level, including:

  • An assessment of which venues are inside each neighbourhood using spatial analysis.
  • Evaluate the relevance of nightlife at each neighbourhood using two metrics: number of venues and venue density.
  • Present choropleth maps of these metrics to examine them spatially.

I have used sf for spatial analysis. I will pick the Barcelona map through BAdatasetsSpatial. The tidyverse and kableExtra are for wrangling data, making plots and presenting nice tables.

library(tidyverse)
library(sf)
library(BAdatasetsSpatial)
library(kableExtra)

Barcelona Neighborhoods

I have retrieved the geographical information of Barcelona neighbourhoods from the map stored in BAdatasetsSPatial. I will be using the bcn_neigh map.

bcn_neigh <- BCNNeigh |>
  select(c_barri, n_barri, area, perim, coord_x, coord_y)

Barcelona is administratively divided into ten districts and seventy-three neighbourhoods. In this map, i am presenting each neighbourhood with its code.

ggplot(bcn_neigh) +
  geom_sf(fill = "white") +
  geom_text(aes(coord_x, coord_y, label = c_barri), size = 3) +
  theme_void() +
  ggtitle(label = "Barcelona Neighborhoods")

And here is the official list of neighbourhoods in Barcelona.

bcn_neigh |>
  st_drop_geometry() |>
  select(c_barri, n_barri) |>
  rename(code = c_barri, name = n_barri) |>
  arrange(code) |>
  kbl() |>
  kable_minimal(full_width = FALSE)
code name
1 el Raval
2 el Barri Gòtic
3 la Barceloneta
4 Sant Pere, Santa Caterina i la Ribera
5 el Fort Pienc
6 la Sagrada Família
7 la Dreta de l’Eixample
8 l’Antiga Esquerra de l’Eixample
9 la Nova Esquerra de l’Eixample
10 Sant Antoni
11 el Poble Sec
12 la Marina del Prat Vermell
13 la Marina de Port
14 la Font de la Guatlla
15 Hostafrancs
16 la Bordeta
17 Sants - Badal
18 Sants
19 les Corts
20 la Maternitat i Sant Ramon
21 Pedralbes
22 Vallvidrera, el Tibidabo i les Planes
23 Sarrià
24 les Tres Torres
25 Sant Gervasi - la Bonanova
26 Sant Gervasi - Galvany
27 el Putxet i el Farró
28 Vallcarca i els Penitents
29 el Coll
30 la Salut
31 la Vila de Gràcia
32 el Camp d’en Grassot i Gràcia Nova
33 el Baix Guinardó
34 Can Baró
35 el Guinardó
36 la Font d’en Fargues
37 el Carmel
38 la Teixonera
39 Sant Genís dels Agudells
40 Montbau
41 la Vall d’Hebron
42 la Clota
43 Horta
44 Vilapicina i la Torre Llobeta
45 Porta
46 el Turó de la Peira
47 Can Peguera
48 la Guineueta
49 Canyelles
50 les Roquetes
51 Verdun
52 la Prosperitat
53 la Trinitat Nova
54 Torre Baró
55 Ciutat Meridiana
56 Vallbona
57 la Trinitat Vella
58 Baró de Viver
59 el Bon Pastor
60 Sant Andreu
61 la Sagrera
62 el Congrés i els Indians
63 Navas
64 el Camp de l’Arpa del Clot
65 el Clot
66 el Parc i la Llacuna del Poblenou
67 la Vila Olímpica del Poblenou
68 el Poblenou
69 Diagonal Mar i el Front Marítim del Poblenou
70 el Besòs i el Maresme
71 Provençals del Poblenou
72 Sant Martí de Provençals
73 la Verneda i la Pau

Nightlife Data

The information abouth nightlife has been retrieved from the 2024 census of ground floor premises published in Open Data BCN. As the dataset includes latitude and longitude, it can be turned into a spatial object.

nightlife_2024 <- data_2024 |>
  select(nom_local, latitud, longitud, nom_barri, codi_barri, nom_via, porta)

nightlife_2024_sf <- nightlife_2024 |>
  st_as_sf(coords = c("longitud", "latitud"), crs = 4326, remove = FALSE)

nightlife_2024_sf
## Simple feature collection with 242 features and 7 fields
## Geometry type: POINT
## Dimension:     XY
## Bounding box:  xmin: 2.11062 ymin: 41.37241 xmax: 2.200073 ymax: 41.43286
## Geodetic CRS:  WGS 84
## # A tibble: 242 × 8
##    nom_local                 latitud longitud nom_barri codi_barri nom_via porta
##  * <chr>                       <dbl>    <dbl> <chr>          <dbl> <chr>   <dbl>
##  1 LA MOLE BAR MUSICAL          41.4     2.12 Sants - …         17 TORNS      10
##  2 LA TRAVIESA TUSET            41.4     2.15 Sant Ger…         26 TUSET      10
##  3 LA CLAVE                     41.4     2.13 Sants - …         17 SUGRAN…    20
##  4 PEREJIL                      41.4     2.16 el Poble…         11 BLAI       10
##  5 AFTERLIFE ESPORTS GAMER …    41.4     2.14 Sant Ger…         26 MARIÀ …    10
##  6 APOLO                        41.4     2.17 el Poble…         11 NOU DE…    10
##  7 APOLO BAILE                  41.4     2.17 el Poble…         11 NOU DE…    20
##  8 EL GIARDINETTO               41.4     2.15 Sant Ger…         26 GRANAD…    10
##  9 APOLO CASINO                 41.4     2.17 el Poble…         11 NOU DE…    50
## 10 BAR SCHULTZ                  41.4     2.17 la Dreta…          7 BAILÈN     30
## # ℹ 232 more rows
## # ℹ 1 more variable: geometry <POINT [°]>

Checking Data Quality

The nightlife_2024_sf includes information about the neighbourhood in the nom_barri and codi_barri columns. Let’s check if the Raval venues are located into the Raval neighbourhood. To do so, I create:

  • The nl_raval table of venues encoded as located at El Raval with codi_barri == 1.
  • The sf_raval table, corresponding with the polygon of El Raval.
nl_raval <- nightlife_2024_sf |>
  filter(codi_barri == 1)

sf_raval <- bcn_neigh |>
  filter(c_barri == 1) 

Let’s plot both spatial objects together.

ggplot(sf_raval) +
  geom_sf() +
  geom_sf(data = nl_raval)

Some of the points fall outside the polygon (and maybe there are points inside the polygon incorrectly encoded too). We need to assign points to neighborhoods spatially using sf::st_join(). The result is stored in nightlife_2024_sf2.

nightlife_2024_sf2 <- st_join(nightlife_2024_sf, 
                              bcn_neigh |> select(c_barri, n_barri),
                              join = st_intersects)

In one case the spatial join has not found the neighborhood, so we keep it in the original neighborhood.

nightlife_2024_sf2 <- nightlife_2024_sf2 |>
  mutate(c_barri = ifelse(is.na(c_barri), codi_barri, c_barri),
         n_barri = ifelse(is.na(c_barri), nom_barri, n_barri))

The neighbourhood code assigned spatially is in c_barri and the code included in the nightlife dataset is in codi_barri. We can appreciate some differences:

nightlife_2024_sf2 |>
  filter(c_barri != codi_barri)
## Simple feature collection with 43 features and 9 fields
## Geometry type: POINT
## Dimension:     XY
## Bounding box:  xmin: 2.127625 ymin: 41.37542 xmax: 2.195337 ymax: 41.43224
## Geodetic CRS:  WGS 84
## # A tibble: 43 × 10
##    nom_local               latitud longitud nom_barri   codi_barri nom_via porta
##  * <chr>                     <dbl>    <dbl> <chr>            <dbl> <chr>   <dbl>
##  1 ALIMENTACIÓ MONK           41.4     2.18 Sant Pere,…          4 ABAIXA…    10
##  2 BLING BLING                41.4     2.15 Sant Gerva…         26 TUSET      20
##  3 RATAFIA                    41.4     2.13 les Tres T…         24 RAFAEL…    20
##  4 ARENA XPERIENCE            41.4     2.17 la Dreta d…          7 RDA SA…    40
##  5 EL PATRÓN SHISHA LOUNGE    41.4     2.14 el Putxet …         27 MANUEL…    10
##  6 ARENA CHICAS               41.4     2.16 la Dreta d…          7 DIPUTA…    20
##  7 IMPERATOR                  41.4     2.16 la Vila de…         31 CÒRSEGA    10
##  8 SALA VIVALDI               41.4     2.15 Sant Antoni         10 LLANÇA     10
##  9 BEACH CLUB                 41.4     2.15 l'Antiga E…          8 VALÈNC…    10
## 10 SCOBIE'S IRISH PUB         41.4     2.17 la Dreta d…          7 RDA UN…    20
## # ℹ 33 more rows
## # ℹ 3 more variables: geometry <POINT [°]>, c_barri <dbl>, n_barri <chr>

Let’s check where are the points assigned to c_barri == 1.

nl2_raval <- nightlife_2024_sf2 |>
  filter(c_barri == 1)

ggplot(sf_raval) +
  geom_sf() +
  geom_sf(data = nl2_raval)

Seeing the results, I will be using the c_barri column to assign venues to neighbourhoods.

Metrics of Neighborhood Nightlife

I will be using two metrics to evaluate the intensity of nightlife in a neighbourhood:

  • Number of venues.
  • Venue density, measured in number of venues per square kilometer.

These metrics will be stored in the count_nl table. I am grouping by c_barri and count to obtain venues per neighbourhood.

count_nl <- nightlife_2024_sf2 |>
  st_drop_geometry() |>
  group_by(c_barri) |>
  summarise(venues = n(), .groups = "drop")
count_nl
## # A tibble: 35 × 2
##    c_barri venues
##      <dbl>  <int>
##  1       1     16
##  2       2     23
##  3       3      3
##  4       4     16
##  5       5      3
##  6       6      1
##  7       7     14
##  8       8     11
##  9       9      8
## 10      10      7
## # ℹ 25 more rows

Seeing the number of rows of count_nl, not all neighbourhoods have nightlife. I will pick the area of the incumbent neighbourhoods from bcn_neigh making a left_join() with count_nl as first table.

count_nl <- left_join(count_nl,
                      bcn_neigh |> 
                        st_drop_geometry() |>
                        select(c_barri, n_barri, area),
                      by = "c_barri")  

As the area is in square meters, I transform it in square kilometers and then compute density.

count_nl <- count_nl |>
  mutate(area = area/1e+06,
         density = venues/area)

The ten neighbourhoods with more nightlife venues are:

count_nl |>
  arrange(-venues) |>
  select(n_barri, venues, density) |>
  slice(1:10) |>
  kbl() |>
  kable_styling(full_width = FALSE)
n_barri venues density
Sant Gervasi - Galvany 32 19.289704
el Barri Gòtic 23 27.318994
el Poble Sec 23 4.995069
el Raval 16 14.566736
Sant Pere, Santa Caterina i la Ribera 16 14.358806
la Vila de Gràcia 15 11.312043
la Dreta de l’Eixample 14 6.593185
l’Antiga Esquerra de l’Eixample 11 8.910827
les Corts 11 7.787210
Sants 9 8.198509

And the top ten neighbourhoods by venue density of venues:

count_nl |>
  arrange(-density) |>
  select(n_barri, venues, density) |>
  slice(1:10) |>
  kbl() |>
  kable_styling(full_width = FALSE)
n_barri venues density
el Barri Gòtic 23 27.318994
Sant Gervasi - Galvany 32 19.289704
el Raval 16 14.566736
Sant Pere, Santa Caterina i la Ribera 16 14.358806
la Vila de Gràcia 15 11.312043
l’Antiga Esquerra de l’Eixample 11 8.910827
Sant Antoni 7 8.739239
Sants 9 8.198509
les Corts 11 7.787210
Sants - Badal 3 7.307104

Choropleth Maps

I will present the results of above as choroplet maps. I need to add the values of count_nl to bcn_neigh. As we need to keep all the regions of the map, I use a left_join() with bcn_neigh as first table.

bcn_neigh <- left_join(bcn_neigh,
                       count_nl |> 
                         select(c_barri, venues, density),
                       by = "c_barri") 

Some neighborhoods have no venues, so I replace NA by zeros in venues and density.

bcn_neigh <- bcn_neigh |>
  mutate(venues= ifelse(is.na(venues), 0, venues),
         density = ifelse(is.na(density), 0, density))

Now I can do the choropleth of the number of venues. I have chosen a gradient fill color scale with low value white (for neighbourhoods with no venues) to red (maximum value of venues).

ggplot(bcn_neigh) +
  geom_sf(aes(fill = venues)) +
  scale_fill_gradient(low = "white", high = "red") +
  theme_void() +
  theme(legend.position = "none") +
  labs(title = "Number of Venues by Neighborhood (2024)",
          caption = "Source: Open Data BCN")

The venue density choropleth is built with the same principle, using orange instead of red.

ggplot(bcn_neigh) +
  geom_sf(aes(fill = density)) +
  scale_fill_gradient(low = "white", high = "orange") +
  theme_void() +
  theme(legend.position = "none") +
  labs(title = "Venue Density by Neighborhood (2024)",
          caption = "Source: Open Data BCN")

Conclusions

In this post, I have used a Barcelona neighbourhood map to analyze the intensity of nightlife across the city. I have used a spatial join to assign a neighbourhood to each venue, and I have calculated two metrics: the number of venues and density of venues for each neighbourhood. I have used this information to present a top ten ranking of neighbourhoods and a choropleth for each metric.

Results show two distinct geographical zones: the residential neighbourhood of Sant Gervasi-Galvany and downtown neighbourhoods. Public of Sant Gervasi–Galvany is predominantly middle- to upper-middle-class residents. Many patrons are locals from the district or nearby upscale areas (Sarrià, Tres Torres, Eixample). Nightlife here tends to be more oriented toward cocktail bars, lounges, and premium venues. The public of downtonw neighbourhoods like Barri Gòtic, El Raval, Poble Sec and Sant Pere, Santa Caterina i la Ribera is more socially mixed but heavily influenced by tourists, visitors, and young people from across the city. There is presence of international residents, students, and creative communities. In the Barri Gòtic especially, the nightlife public is tourism-driven, with a high proportion of short-term visitors.

References

Session Info

## R version 4.5.2 (2025-10-31)
## Platform: x86_64-pc-linux-gnu
## Running under: Linux Mint 21.1
## 
## Matrix products: default
## BLAS:   /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.10.0 
## LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.10.0  LAPACK version 3.10.0
## 
## locale:
##  [1] LC_CTYPE=es_ES.UTF-8       LC_NUMERIC=C              
##  [3] LC_TIME=es_ES.UTF-8        LC_COLLATE=es_ES.UTF-8    
##  [5] LC_MONETARY=es_ES.UTF-8    LC_MESSAGES=es_ES.UTF-8   
##  [7] LC_PAPER=es_ES.UTF-8       LC_NAME=C                 
##  [9] LC_ADDRESS=C               LC_TELEPHONE=C            
## [11] LC_MEASUREMENT=es_ES.UTF-8 LC_IDENTIFICATION=C       
## 
## time zone: Europe/Madrid
## tzcode source: system (glibc)
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
##  [1] kableExtra_1.4.0        BAdatasetsSpatial_0.1.0 sf_1.0-20              
##  [4] lubridate_1.9.4         forcats_1.0.1           stringr_1.6.0          
##  [7] dplyr_1.1.4             purrr_1.2.0             readr_2.1.5            
## [10] tidyr_1.3.1             tibble_3.3.0            ggplot2_4.0.0          
## [13] tidyverse_2.0.0        
## 
## loaded via a namespace (and not attached):
##  [1] s2_1.1.7           utf8_1.2.4         sass_0.4.10        generics_0.1.3    
##  [5] xml2_1.4.1         class_7.3-23       KernSmooth_2.23-26 blogdown_1.21     
##  [9] stringi_1.8.7      hms_1.1.4          digest_0.6.37      magrittr_2.0.4    
## [13] evaluate_1.0.3     grid_4.5.2         timechange_0.3.0   RColorBrewer_1.1-3
## [17] bookdown_0.43      fastmap_1.2.0      jsonlite_2.0.0     e1071_1.7-16      
## [21] DBI_1.2.3          viridisLite_0.4.2  scales_1.4.0       textshaping_1.0.0 
## [25] jquerylib_0.1.4    cli_3.6.4          rlang_1.1.6        units_0.8-7       
## [29] withr_3.0.2        cachem_1.1.0       yaml_2.3.10        tools_4.5.2       
## [33] tzdb_0.5.0         vctrs_0.6.5        R6_2.6.1           proxy_0.4-27      
## [37] lifecycle_1.0.4    classInt_0.4-11    pkgconfig_2.0.3    pillar_1.11.1     
## [41] bslib_0.9.0        gtable_0.3.6       Rcpp_1.1.0         glue_1.8.0        
## [45] systemfonts_1.2.3  xfun_0.52          tidyselect_1.2.1   rstudioapi_0.17.1 
## [49] knitr_1.50         farver_2.1.2       htmltools_0.5.8.1  labeling_0.4.3    
## [53] svglite_2.2.0      rmarkdown_2.29     wk_0.9.4           compiler_4.5.2    
## [57] S7_0.2.0