Geting started¶
Install¶
uk_address_matcher is a Python package available on PyPI. You can install with pip:
pip install uk_address_matcher
Input data requirements¶
Both your messy addresses and your canonical addresses need at least these columns:
| Column | Description |
|---|---|
unique_id |
Stable unique identifier |
address_concat |
Address text, which can include the postcode |
Optionally you can provide:
| Column | Description |
|---|---|
postcode |
If provided, this postcode is used in favour over any postcode provided in address_concat |
ukam_label |
The unique ID of the true match. If provided, it enables accuracy analysis output |
Choose whether to pre-process your canonical dataset¶
If you're linking to a small canonical dataset (of say, less than 500,000 rows), then it's simplest to process the data on-the-fly.
If you're linking to a large canonical dataset (for example, national-scale NGD), then we recommend a one-time pre-processing step. It computes reusable datasets (indices and feature tables) once, so subsequent matching runs are fast.
The examples below use the fictional London datasets from ukam_datasets, which are included for runnable examples.
import duckdb
from uk_address_matcher import AddressMatcher, ukam_datasets
con = duckdb.connect()
df_messy = ukam_datasets.as_relation("fictional_london_messy", con=con)
df_canonical = ukam_datasets.as_relation("fictional_london_canonical", con=con)
matcher = AddressMatcher(
canonical_addresses=df_canonical,
addresses_to_match=df_messy,
con=con,
)
result = matcher.match()
print(result.matches().limit(5).to_df().to_markdown(index=False))
| unique_id | resolved_canonical_id | ukam_label | original_address_concat | clean_full_address_canonical | original_address_concat_canonical | match_reason | match_weight | distinguishability |
|---|---|---|---|---|---|---|---|---|
| m_0001165 | c_0008592 | c_0008592 | Suite 1, 171 Primrosemarket St, West Alder, London | SUITE 1 171 PRIMROSEMARKET STREET WEST ALDER LONDON | Suite 1, 171 Primrosemarket Street, West Alder, London | splink: probabilistic match | 56.2066 | nan |
| m_0001287 | c_0002728 | c_0002728 | Unit 11,164 Kestrellane Cl,Maple Green,London | UNIT 11 164 KESTRELLANE CLOSE MAPLE GREEN LONDON | Unit 11, 164 Kestrellane Close, Maple Green, London | splink: probabilistic match | 55.3354 | nan |
| m_0001609 | c_0003965 | c_0003965 | Apt 15, 39 Kestrelmead Ave, West Alder, London | APARTMENT 15 39 KESTRELMEAD AVENUE WEST ALDER LONDON | Apt 15, 39 Kestrelmead Avenue, West Alder, London | exact: full match | nan | nan |
| m_0000713 | c_0006590 | c_0006590 | Unit 16, 63 Cliftongate Gdns, West Alder, London | UNIT 16 63 CLIFTONGATE GARDENS WEST ALDER LONDON | Unit 16, 63 Cliftongate Gardens, West Alder, London | exact: full match | nan | nan |
| m_0001408 | c_0005878 | c_0005878 | APT 17, 122 AMBERHURST LN, NEW HUXLEY, LONDON | APARTMENT 17 122 AMBERHURST LANE NEW HUXLEY LONDON | Apt 17, 122 Amberhurst Lane, New Huxley, London | exact: full match | nan | nan |
import duckdb
import os
import tempfile
from uk_address_matcher import (
AddressMatcher,
prepare_canonical_folder,
ukam_datasets,
)
con = duckdb.connect()
df_messy = ukam_datasets.as_relation("fictional_london_messy", con=con)
df_canonical = ukam_datasets.as_relation("fictional_london_canonical", con=con)
# One-time preparation
output_folder = tempfile.mkdtemp()
prepare_canonical_folder(
df_canonical,
output_folder=output_folder,
con=con,
overwrite=True,
)
# Pass the folder path instead of a relation
matcher = AddressMatcher(
canonical_addresses=output_folder,
addresses_to_match=df_messy,
con=con,
)
result = matcher.match()
print("Prepared folder contents:")
for f in sorted(os.listdir(output_folder)):
print(f" {f}")
print()
print(result.matches().limit(5).to_df().to_markdown(index=False))
Prepared folder contents: ukam_canonical_addresses.parquet ukam_inverted_index.parquet ukam_manifest.json ukam_term_frequencies.parquet
| unique_id | resolved_canonical_id | ukam_label | original_address_concat | clean_full_address_canonical | match_reason | match_weight | distinguishability |
|---|---|---|---|---|---|---|---|
| m_0001165 | c_0008592 | c_0008592 | Suite 1, 171 Primrosemarket St, West Alder, London | SUITE 1 171 PRIMROSEMARKET STREET WEST ALDER LONDON | splink: probabilistic match | 56.2066 | nan |
| m_0001287 | c_0002728 | c_0002728 | Unit 11,164 Kestrellane Cl,Maple Green,London | UNIT 11 164 KESTRELLANE CLOSE MAPLE GREEN LONDON | splink: probabilistic match | 55.3354 | nan |
| m_0001609 | c_0003965 | c_0003965 | Apt 15, 39 Kestrelmead Ave, West Alder, London | APARTMENT 15 39 KESTRELMEAD AVENUE WEST ALDER LONDON | exact: full match | nan | nan |
| m_0000713 | c_0006590 | c_0006590 | Unit 16, 63 Cliftongate Gdns, West Alder, London | UNIT 16 63 CLIFTONGATE GARDENS WEST ALDER LONDON | exact: full match | nan | nan |
| m_0001408 | c_0005878 | c_0005878 | APT 17, 122 AMBERHURST LN, NEW HUXLEY, LONDON | APARTMENT 17 122 AMBERHURST LANE NEW HUXLEY LONDON | exact: full match | nan | nan |
The output_folder contains parquet files plus ukam_manifest.json
(package version, row counts, file hashes) for reproducibility.
Subsequent matching exercises that use the same canonical data can reuse this folder, skipping the prepare_canonical_folder step.
Reading results¶
matcher.match() returns a MatchResult object:
| Property / method | Returns |
|---|---|
.matches() |
DuckDB relation with unique_id, resolved_canonical_id, match_reason, and more. |
.match_metrics() |
Match-reason breakdown with counts and percentages. |
.accuracy_analysis() |
Threshold-based accuracy analysis from labelled data (requires ukam_label in messy input). |
Customising stages
The default pipeline is ExactMatchStage → SplinkStage. Pass your own
stages list to change behaviour:
from uk_address_matcher import (
AddressMatcher,
ExactMatchStage,
PeeledAddressStage,
UniqueTrigramStage,
SplinkStage,
)
matcher = AddressMatcher(
canonical_addresses=df_canonical,
addresses_to_match=df_messy,
con=con,
stages=[
ExactMatchStage(),
PeeledAddressStage(),
UniqueTrigramStage(),
SplinkStage(
final_match_weight_threshold=20.0,
final_distinguishability_threshold=5.0,
),
],
)
Use AddressMatcher.available_stages() to discover registered stage classes. See Choosing a matching threshold and Optimising accuracy for further accuracy advice. The API reference covers the main API docs.
Using labelled data¶
If you know the correct match for each address, add a ukam_label column to
your messy data. It propagates through to results, enabling accuracy analysis
with MatchResult.accuracy_analysis().