Creating a London Population Map with D3po
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
If this post is useful to you I kindly ask a minimal donation on Buy Me a Coffee. It shall be used to continue my Open Source efforts.
You can send me questions for the blog using this form and subscribe to receive an email when there is a new post.
I got this badly worded question for the blog: “Please clarify. This two are not working. From which package is po_tooltip. Thanks.”
I appreciate the thanks, but remember the help me to help you rule: “Please provide a minimal, reproducible example when asking for help.” Or at least explain what your question is about.
Based on the question, it is from the Gini Index post.
I will try to explain by creating a population map of London using the D3po package.
All the po_*() functions are part of the D3po package, including po_tooltip().
Load these R packages to import and manipulate the data:
library(d3po) library(dplyr) library(sf) library(rvest) library(janitor)
There is a better resolution map of London boroughs provided by TfL. This map looks better compared to D3po provideds subnational map (low resolution).
Download the GeoJSON file to show that D3po can work with any spatial data in sf format.
url <- "https://hub.arcgis.com/api/v3/datasets/0a92a355a8094e0eb20a7a66cf4ca7cf_10/downloads/data?format=geojson&spatialRefId=4326&where=1%3D1"
finp <- "~/Documents/blog-materials/2025/11/14/london-population/london_boroughs.geojson"
if (!file.exists(finp)) {
download.file(url, destfile = finp, mode = "wb")
}
Read the GeoJSON file using st_read() from the sf package and clean the column names with clean_names() from the janitor package:
boroughs <- st_read(finp) %>% clean_names()
Reading layer `London_Boroughs' from data source `/home/pacha/Documents/blog-materials/2025/11/14/london-population/london_boroughs.geojson' using driver `GeoJSON' Simple feature collection with 33 features and 13 fields Geometry type: POLYGON Dimension: XY Bounding box: xmin: -0.5103558 ymin: 51.28676 xmax: 0.3340441 ymax: 51.69188 Geodetic CRS: WGS 84
boroughs
Simple feature collection with 33 features and 13 fields
Geometry type: POLYGON
Dimension: XY
Bounding box: xmin: -0.5103558 ymin: 51.28676 xmax: 0.3340441 ymax: 51.69188
Geodetic CRS: WGS 84
First 10 features:
borough number code hectares descript0 x
1 Bromley 19 00AF 15014.5152 CIVIL ADMINISTRATION AREA 542896
2 Lewisham 07 00AZ 3532.3405 CIVIL ADMINISTRATION AREA 537667
3 Wandsworth 10 00BJ 3522.0032 CIVIL ADMINISTRATION AREA 526129
4 Merton 22 00BA 3760.9196 CIVIL ADMINISTRATION AREA 525475
5 Redbridge 14 00BC 5645.0083 CIVIL ADMINISTRATION AREA 543914
6 Barnet 30 00AC 8673.7261 CIVIL ADMINISTRATION AREA 524028
7 City of London 00 00AA 315.2813 CIVIL ADMINISTRATION AREA 532464
8 Sutton 21 00BF 4385.0950 CIVIL ADMINISTRATION AREA 526976
9 Southwark 08 00BE 2989.7206 CIVIL ADMINISTRATION AREA 533855
10 Ealing 27 00AJ 5552.7807 CIVIL ADMINISTRATION AREA 515888
y area objectid file_name shape_area shape_length
1 165656 0 1 GREATER_LONDON_AUTHORITY 150145152 75909.143
2 174002 0 2 GREATER_LONDON_AUTHORITY 35323405 40992.749
3 174114 0 3 GREATER_LONDON_AUTHORITY 35220032 37353.847
4 169422 0 4 GREATER_LONDON_AUTHORITY 37609196 32293.920
5 189463 0 5 GREATER_LONDON_AUTHORITY 56450083 45688.184
6 192316 0 6 GREATER_LONDON_AUTHORITY 86737261 50866.472
7 181220 0 7 GREATER_LONDON_AUTHORITY 3152813 9651.891
8 164132 0 8 GREATER_LONDON_AUTHORITY 43850950 39753.841
9 176787 0 9 GREATER_LONDON_AUTHORITY 29897206 33664.416
10 181715 0 10 GREATER_LONDON_AUTHORITY 55527807 47268.052
global_id geometry
1 86b54395-dc20-4e52-bc8c-7b79188f035f POLYGON ((0.01215857 51.299...
2 84f88d72-30c1-47b6-bc71-163246413f0d POLYGON ((-0.04613459 51.45...
3 617038fb-5459-4133-96ae-1743ba48321c POLYGON ((-0.2540111 51.437...
4 6362d58b-7052-4655-a812-3487752e770e POLYGON ((-0.2181117 51.380...
5 3d6ac4f8-be2c-4a89-a684-e50e11c602e9 POLYGON ((0.02036894 51.556...
6 e424d66c-44fe-4e0e-b58b-d1851cfe71b1 POLYGON ((-0.1998716 51.670...
7 417f3ab6-bfb7-4770-827f-80526d360a25 POLYGON ((-0.1115267 51.515...
8 6dfcbe71-9609-488c-8852-df930b685ada POLYGON ((-0.1442461 51.339...
9 161208ac-2edc-471f-81dc-ec7e494032e5 POLYGON ((-0.07829781 51.42...
10 4d06499b-5d29-43e8-a4fa-c32be7b23119 POLYGON ((-0.3354244 51.496...
Extract the borough names:
names1 <- pull(boroughs, borough) names1
[1] "Bromley" "Lewisham" "Wandsworth" [4] "Merton" "Redbridge" "Barnet" [7] "City of London" "Sutton" "Southwark" [10] "Ealing" "Brent" "Croydon" [13] "Richmond upon Thames" "Hillingdon" "Haringey" [16] "Kensington & Chelsea" "Kingston upon Thames" "Waltham Forest" [19] "Barking & Dagenham" "Newham" "Enfield" [22] "Hammersmith & Fulham" "Havering" "Greenwich" [25] "Hackney" "Westminster" "Camden" [28] "Tower Hamlets" "Hounslow" "Harrow" [31] "Bexley" "Islington" "Lambeth"
Now we need the population for the 33 boroughs. CityPopulation.DE has a table we can read using rvest:
url_pop <- "https://www.citypopulation.de/en/uk/greaterlondon/"
finp2 <- "~/Documents/blog-materials/2025/11/14/london-population/london_population.rds"
if (file.exists(finp2)) {
pop_table <- readRDS(finp2)
} else {
page <- read_html(url_pop)
tables <- page %>% html_nodes("table")
pop_table <- tables[[1]] %>%
html_table() %>%
clean_names()
pop_table <- pop_table %>%
select(name, pop = population_estimate2024_06_30)
pop_table <- pop_table %>%
mutate(pop = as.numeric(gsub(",", "", pop)))
names2 <- pull(pop_table, name)
names2
# names that do not match
setdiff(names1, names2)
setdiff(names2, names1)
# replace the " and " with " & " in boroughs
# replace "City of Westminster" with "Westminster"
pop_table <- pop_table %>%
mutate(borough = case_when(
name == "City of Westminster" ~ "Westminster",
grepl(" and ", name) ~ gsub(" and ", " & ", name),
TRUE ~ name
)) %>%
select(-name)
saveRDS(pop_table, finp2)
}
Up to this point we can show two maps:
- Inhabitants per borough
- Inhabitants per square km
boroughs <- boroughs %>%
left_join(pop_table, by = "borough") %>%
mutate(
area_km2 = hectares / 100,
pop_per_km2 = pop / area_km2
)
Define a color gradient for the maps a and create the maps using d3po:
my_gradient <- c("#b2d8d8", "#66b2b2", "#008080", "#006666", "#004c4c")
d3po(boroughs, width = 800, height = 600) %>%
po_geomap(daes(group = borough, size = pop, color = my_gradient, gradient = T, tooltip = borough)) %>%
po_labels(
title = "Population in London Boroughs (2024)",
subtitle = "Source: CityPopulation.DE & TFL London Boroughs"
)
R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.