30 Day Map Challenge: Typography

Author
Published

November 19, 2024

Day 18: Typography

This map uses the location of postcodes in the UK, and produces a map where the “Postcode Area” (the first letters of a postcode) are scaled by the number of unit postcodes contained within them. The map is created from the mean locations of Unit Postcdoes within each Postcode Area. A Dorling Cartogram is then used to adjust these locations and generate circles which are then used to scale the labels for the plot.

The postcode files used for this map were extracted from the Ordnance Survey Codepoint as CSV. They are extracted to a folder and then DuckDB was used to read this collection of multiple CSV into a single data frame.

library(tidyverse)
library(DBI)
library(duckdb)
library(magrittr)
library(sf)
library(cartogram)

# Connect to an in memort DuckDB database
con <- dbConnect(duckdb::duckdb(), dbdir = ":memory:")

# Set the path to the directory containing CSV files
directory_path <- "CSV"

# Register CSV files in the directory as a virtual DuckDB table
dbExecute(con, paste0("CREATE VIEW all_data AS SELECT * FROM read_csv_auto('", directory_path, "/*.csv')"))
[1] 0
# Query the combined data and store it as a tibble
codepoint <- dbGetQuery(con, "SELECT * FROM all_data") %>%
  as_tibble()

# Disconnect from the database
dbDisconnect(con, shutdown = TRUE)

After importing the data, we then adjust the column names.

# Renaming columns in all_data
codepoint <- codepoint %>%
  rename(
    Postcode = column0,
    Positional_quality_indicator = column1,
    Eastings = column2,
    Northings = column3,
    Country_code = column4,
    NHS_regional_HA_code = column5,
    NHS_HA_code = column6,
    Admin_county_code = column7,
    Admin_district_code = column8,
    Admin_ward_code = column9
  )

And then create a new variable name for postcode areas. In many cases this is the first two letters of the unit postcode, however, in some places, only the first letter, requiring removal of a number.

codepoint %<>%
    mutate(pcd_area = str_sub(Postcode, 1, 2) %>% str_remove("\\d"))

Next we create a new data frame which is calculated as the average Easting and Northing location for each postcode area, and additionally counting the number of postcodes. We can use simple arithmetic calculation as Easting and Northings are a symmetrical and in units of meters.

pcd_area <- codepoint %>%
  group_by(pcd_area) %>%
  summarise(
    avg_easting = mean(Eastings, na.rm = TRUE),
    avg_northing = mean(Northings, na.rm = TRUE),
    record_count = n()
  )

The data frame is then converted into into an SF object with a CRS of 27700 which is OSGB, however is then transformed into a geographic co-ordinate system 3857, which is Mercator, enabling subsequent processing.

# Convert to sf object
sf_pcd_area <- pcd_area %>%
  st_as_sf(coords = c("avg_easting", "avg_northing"), crs = 27700) %>%
  st_transform(3857)

The Dorling Cartogram can be found within the Catogram package. In this instance it creates a new set of circular geometry for each of the Postcode Areas. The area of these new shapes are then captured as a list and converted into quintiles.

# Create the Dorling Cartogram
dorling_cartogram <- cartogram_dorling(sf_pcd_area, k=0.4, weight="record_count",itermax = 1000,m_weight=0.2)
# Calculate the area of the circles
sizes <- st_area(dorling_cartogram) %>%
  ntile(5)

These can then be used to create a Typography only map by using the centroid locations of the circles (which are hidden), with the labels plotted but scaled by their associated shape size.

# Plot using ggplot2
ggplot(dorling_cartogram) +
  #scale_size_continuous(range = c(1, 10), guide = "none") +  # Adjust size
  geom_sf_text(aes(label = pcd_area), size = sizes) +
  coord_fixed() +
  theme_minimal() +
  theme(axis.text=element_blank(),
        axis.ticks = element_blank(),
        axis.title = element_blank(),
        panel.grid.major = element_blank(),
        panel.grid.minor = element_blank(),
        panel.spacing = unit(0,"mm"),
        plot.margin =  unit(rep(0,4),"mm"))