Skip to content

Linking businesses

Open In Colab

Matching businessesΒΆ

In this example we attempt to match open business rates data from Stockport (source) with Companies House data (source).

This is quite a challenging scenario because the data is not initially well suited for Splink.

First, there are only three columns to match on:

  • The business name
  • The address
  • The business postcode

Second, the data itself is quite different to many other applications: the business name is an ordered 'bag of words'.

We will therefore need to use a few preprocessing tricks to build a successful matching model/

Let's start by taking a look at the data. To make the size of these files manageable, I have selected only rows from the companies house data where substr(postcode, 1,4) matches at least one postcode in the Stockport data, resulting in:

  • Companies house data is around 1 million records
  • Stockport data has 4,540 records

In this example, to make it easy to understand, I break the calculation into multiple steps. Many calculations can be combined into single steps - you can see a more condensed version here.

First, let's take a look at the data:

import altair as alt
import duckdb

df_companies_house = duckdb.read_parquet("https://github.com/RobinL/company_matching_example/raw/refs/heads/main/companies_house.parquet")
df_companies_house.sort("company_name").show(max_rows=5)

df_stockport_business_rates = duckdb.read_parquet("https://github.com/RobinL/company_matching_example/raw/refs/heads/main/stockport_business_rates.parquet")
df_stockport_business_rates.sort("company_name").show(max_rows=5)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚         company_name         β”‚ company_number β”‚             address_concat              β”‚ postcode β”‚
β”‚           varchar            β”‚    varchar     β”‚                 varchar                 β”‚ varchar  β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ ! LTD                        β”‚ 08209948       β”‚ 9 PRINCES SQUARE                        β”‚ HG1 1ND  β”‚
β”‚ !BIG IMPACT GRAPHICS LIMITED β”‚ 11743365       β”‚ 372 OLD STREET 335 ROSDEN HOUSE         β”‚ EC1V 9LT β”‚
β”‚ !NFOGENIE LTD                β”‚ 13522064       β”‚ 71-75 SHELTON STREET                    β”‚ WC2H 9JQ β”‚
β”‚       Β·                      β”‚    Β·           β”‚       Β·                                 β”‚    Β·     β”‚
β”‚       Β·                      β”‚    Β·           β”‚       Β·                                 β”‚    Β·     β”‚
β”‚       Β·                      β”‚    Β·           β”‚       Β·                                 β”‚    Β·     β”‚
β”‚ 4TECH MOTO LIMITED           β”‚ 05583123       β”‚ 1 BOND STREET                           β”‚ BB8 9DG  β”‚
β”‚ 4TECH SOFTWARE LIMITED       β”‚ 07415956       β”‚ ELEVEN BRINDLEYPLACE 2 BRUNSWICK SQUARE β”‚ B1 2LP   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
  ? rows (>9999 rows, 5 shown)                                                             4 columns



β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚            company_name             β”‚ company_number β”‚                              address_concat                              β”‚ postcode β”‚
β”‚               varchar               β”‚    varchar     β”‚                                 varchar                                  β”‚ varchar  β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ 08 UK LTD                           β”‚ NULL           β”‚ 15 MADOC STREET LLANDUDNO CONWY WALES                                    β”‚ LL30 2TL β”‚
β”‚ 1 GR LTD                            β”‚ NULL           β”‚ 296-296A GORTON ROAD REDDISH STOCKPORT                                   β”‚ SK5 6RN  β”‚
β”‚ 101 CANVAS PRINTS LTD               β”‚ NULL           β”‚ SUITE 44 4TH FLOOR HOULDSWORTH MILL HOULDSWORTH STREET REDDISH STOCKPORT β”‚ SK5 6DA  β”‚
β”‚           Β·                         β”‚  Β·             β”‚                           Β·                                              β”‚    Β·     β”‚
β”‚           Β·                         β”‚  Β·             β”‚                           Β·                                              β”‚    Β·     β”‚
β”‚           Β·                         β”‚  Β·             β”‚                           Β·                                              β”‚    Β·     β”‚
β”‚ ZYGOCITY LTD T/A CRAFTSTAR STENCILS β”‚ NULL           β”‚ UNIT 46 GOYT MILL UPPER HIBBERT LANE MARPLE STOCKPORT                    β”‚ SK6 7HX  β”‚
β”‚ ZYNC GROUP LTD IN LIQUIDATION       β”‚ NULL           β”‚ C/O COWGILLS LIMITED FOURTH FLOOR UNIT 5B THE PARKLANDS BOLTON           β”‚ BL6 4SD  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
  4540 rows (5 shown)                                                                                                              4 columns

Feature engineeringΒΆ

We will do some feature engineering to make this data more amenable to matching:

  • To match companies, we will tokenise the company name and compute the term frequencies of each token. This will allow us to more accurately quantify how closely two business names match
  • We will also pick out a subset of tokens to give us access to new blocking strategies
  • To match addresses, we will use the first number in the address and the postcode only. This is simplistic - for a more advanced approach to address matching in Splink see here

We'll perform this featuring on both datasets simultaneously to ensure the token frequencies are representative of all the data

# Start by creating a table with the data vertically concatenated
sql = """
create or replace table all_input_data as
with concat_data as (
    select *, 'stockport' as source_dataset

from df_stockport_business_rates
union all
select *, 'z_all_companies' as source_dataset
from df_companies_house
)
select ROW_NUMBER() OVER () as unique_id, *
from concat_data
"""

duckdb.execute(sql)

duckdb.table("all_input_data").show(max_rows=10, max_width=100000)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ unique_id β”‚         company_name         β”‚ company_number β”‚                              address_concat                              β”‚ postcode β”‚ source_dataset  β”‚
β”‚   int64   β”‚           varchar            β”‚    varchar     β”‚                                 varchar                                  β”‚ varchar  β”‚     varchar     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚         1 β”‚ 08 UK LTD                    β”‚ NULL           β”‚ 15 MADOC STREET LLANDUDNO CONWY WALES                                    β”‚ LL30 2TL β”‚ stockport       β”‚
β”‚         2 β”‚ 1 GR LTD                     β”‚ NULL           β”‚ 296-296A GORTON ROAD REDDISH STOCKPORT                                   β”‚ SK5 6RN  β”‚ stockport       β”‚
β”‚         3 β”‚ 101 CANVAS PRINTS LTD        β”‚ NULL           β”‚ SUITE 44 4TH FLOOR HOULDSWORTH MILL HOULDSWORTH STREET REDDISH STOCKPORT β”‚ SK5 6DA  β”‚ stockport       β”‚
β”‚         4 β”‚ 121 DIRECT MAIL LTD          β”‚ NULL           β”‚ UNIT 18 CHARTER HOUSE LATHAM CLOSE BREDBURY STOCKPORT                    β”‚ SK6 2SD  β”‚ stockport       β”‚
β”‚         5 β”‚ 121 PERSONAL TRAINING LTD    β”‚ NULL           β”‚ 198 WILMSLOW ROAD HEALD GREEN STOCKPORT                                  β”‚ SK8 3BH  β”‚ stockport       β”‚
β”‚         Β· β”‚             Β·                β”‚  Β·             β”‚              Β·                                                           β”‚    Β·     β”‚     Β·           β”‚
β”‚         Β· β”‚             Β·                β”‚  Β·             β”‚              Β·                                                           β”‚    Β·     β”‚     Β·           β”‚
β”‚         Β· β”‚             Β·                β”‚  Β·             β”‚              Β·                                                           β”‚    Β·     β”‚     Β·           β”‚
β”‚      9996 β”‚ AL-MAALIK LOCUM SERVICES LTD β”‚ 14351700       β”‚ 239 MOSELEY ROAD LEVENSHULME                                             β”‚ M19 2LJ  β”‚ z_all_companies β”‚
β”‚      9997 β”‚ AL-MAALIK PHARMACIES LTD     β”‚ 14421852       β”‚ 239 MOSELEY ROAD LEVENSHULME                                             β”‚ M19 2LJ  β”‚ z_all_companies β”‚
β”‚      9998 β”‚ AL-MAALIK PROPERTIES LTD     β”‚ 14412454       β”‚ 1041 STOCKPORT ROAD                                                      β”‚ M19 2TF  β”‚ z_all_companies β”‚
β”‚      9999 β”‚ AL-MADAD OUTREACH LTD        β”‚ 14827390       β”‚ 124-128 CITY ROAD                                                        β”‚ EC1V 2NX β”‚ z_all_companies β”‚
β”‚     10000 β”‚ AL-MADANI LTD                β”‚ 14415359       β”‚ 71-75 SHELTON STREET COVENT GARDEN                                       β”‚ WC2H 9JQ β”‚ z_all_companies β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
  ? rows (>9999 rows, 10 shown)                                                                                                                           6 columns
# Tokenize company names, and explode tokens across rows
# The tokenisation approach here is simplistic, more advanced strategies could be used
# in a more fully-fledged application


sql = """
CREATE OR REPLACE TABLE unnested AS
SELECT
    unique_id,
    unnest(regexp_split_to_array(upper(trim(company_name)), '\\s+')) as name_token,
    generate_subscripts(regexp_split_to_array(upper(trim(company_name)), '\\s+'), 1) as token_position_in_name
FROM all_input_data
"""

unnested = duckdb.execute(sql)
duckdb.table("unnested").show(max_rows=10, max_width=100000)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ unique_id β”‚ name_token β”‚ token_position_in_name β”‚
β”‚   int64   β”‚  varchar   β”‚         int64          β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚         1 β”‚ 08         β”‚                      1 β”‚
β”‚         1 β”‚ UK         β”‚                      2 β”‚
β”‚         1 β”‚ LTD        β”‚                      3 β”‚
β”‚         2 β”‚ 1          β”‚                      1 β”‚
β”‚         2 β”‚ GR         β”‚                      2 β”‚
β”‚         Β· β”‚ Β·          β”‚                      Β· β”‚
β”‚         Β· β”‚ Β·          β”‚                      Β· β”‚
β”‚         Β· β”‚ Β·          β”‚                      Β· β”‚
β”‚      2619 β”‚ MEDATECH   β”‚                      1 β”‚
β”‚      2619 β”‚ UK         β”‚                      2 β”‚
β”‚      2619 β”‚ LTD        β”‚                      3 β”‚
β”‚      2620 β”‚ MEDIA      β”‚                      1 β”‚
β”‚      2620 β”‚ +          β”‚                      2 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
  ? rows (>9999 rows, 10 shown)         3 columns
# Compute token relative frequencies
sql = """
CREATE OR REPLACE TABLE token_frequencies AS
SELECT
    name_token as token,
    count(*)::float/(select count(*) from unnested) as rel_freq
FROM unnested
GROUP BY token
ORDER BY rel_freq DESC

"""

# Execute and show results
token_frequencies = duckdb.execute(sql)
duckdb.table("token_frequencies").show(max_rows=10, max_width=100000)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   token    β”‚   rel_freq    β”‚
β”‚  varchar   β”‚     float     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ LTD        β”‚    0.14284134 β”‚
β”‚ LIMITED    β”‚    0.13450336 β”‚
β”‚ SERVICES   β”‚   0.012372046 β”‚
β”‚ &          β”‚    0.01089211 β”‚
β”‚ MANAGEMENT β”‚  0.0076835644 β”‚
β”‚   Β·        β”‚        Β·      β”‚
β”‚   Β·        β”‚        Β·      β”‚
β”‚   Β·        β”‚        Β·      β”‚
β”‚ FETCH      β”‚ 6.0517837e-06 β”‚
β”‚ COMPARISON β”‚ 6.0517837e-06 β”‚
β”‚ AVATAR     β”‚ 6.0517837e-06 β”‚
β”‚ SPG        β”‚ 6.0517837e-06 β”‚
β”‚ KOALA      β”‚ 6.0517837e-06 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
  ? rows           2 columns
  (>9999 rows, 10 shown)
# Reconstruct the company name as a list of tokens with their relative frequency
# This creates a new column called name_tokens_with_freq
# Example: 101 CANVAS PRINTS LTD  becomes
# [
#  {'token': 101, 'rel_freq': 3.19e-05},
#  {'token': CANVAS, 'rel_freq': 2.20e-05},
#  {'token': PRINTS, 'rel_freq': 3.16e-05},
#  {'token': LTD, 'rel_freq': 0.142}
# ]
sql = """
CREATE OR REPLACE TABLE input_data_with_tokens AS
WITH
tokens_with_freq AS (
    SELECT
        m.unique_id,
        -- Create a list of structs containing each token and its frequency
        list_transform(
            list_zip(
                array_agg(u.name_token ORDER BY u.token_position_in_name),
                array_agg(COALESCE(tf.rel_freq, 0.0) ORDER BY u.token_position_in_name)
            ),
            x -> struct_pack(token := x[1], rel_freq := x[2])
        ) as name_tokens_with_freq
    FROM all_input_data m
    JOIN unnested u ON m.unique_id = u.unique_id
    LEFT JOIN token_frequencies tf ON u.name_token = tf.token
    GROUP BY m.unique_id
)
SELECT
    m.*,
    t.name_tokens_with_freq
FROM all_input_data m
LEFT JOIN tokens_with_freq t ON m.unique_id = t.unique_id
order by m.unique_id
"""

# Execute and show results
duckdb.execute(sql)
duckdb.table("input_data_with_tokens").show(max_rows=10, max_width=200)
FloatProgress(value=0.0, layout=Layout(width='auto'), style=ProgressStyle(bar_color='black'))


β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ unique_id β”‚         company_name         β”‚ company_number β”‚                    address_concat                    β”‚ postcode β”‚ source_dataset  β”‚                name_tokens_with_freq                 β”‚
β”‚   int64   β”‚           varchar            β”‚    varchar     β”‚                       varchar                        β”‚ varchar  β”‚     varchar     β”‚       struct(token varchar, rel_freq float)[]        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚         1 β”‚ 08 UK LTD                    β”‚ NULL           β”‚ 15 MADOC STREET LLANDUDNO CONWY WALES                β”‚ LL30 2TL β”‚ stockport       β”‚ [{'token': 08, 'rel_freq': 2.2006486e-06}, {'token'… β”‚
β”‚         2 β”‚ 1 GR LTD                     β”‚ NULL           β”‚ 296-296A GORTON ROAD REDDISH STOCKPORT               β”‚ SK5 6RN  β”‚ stockport       β”‚ [{'token': 1, 'rel_freq': 0.000688803}, {'token': G… β”‚
β”‚         3 β”‚ 101 CANVAS PRINTS LTD        β”‚ NULL           β”‚ SUITE 44 4TH FLOOR HOULDSWORTH MILL HOULDSWORTH STR… β”‚ SK5 6DA  β”‚ stockport       β”‚ [{'token': 101, 'rel_freq': 3.1909403e-05}, {'token… β”‚
β”‚         4 β”‚ 121 DIRECT MAIL LTD          β”‚ NULL           β”‚ UNIT 18 CHARTER HOUSE LATHAM CLOSE BREDBURY STOCKPO… β”‚ SK6 2SD  β”‚ stockport       β”‚ [{'token': 121, 'rel_freq': 1.7055027e-05}, {'token… β”‚
β”‚         5 β”‚ 121 PERSONAL TRAINING LTD    β”‚ NULL           β”‚ 198 WILMSLOW ROAD HEALD GREEN STOCKPORT              β”‚ SK8 3BH  β”‚ stockport       β”‚ [{'token': 121, 'rel_freq': 1.7055027e-05}, {'token… β”‚
β”‚         Β· β”‚             Β·                β”‚  Β·             β”‚              Β·                                       β”‚    Β·     β”‚     Β·           β”‚                          Β·                           β”‚
β”‚         Β· β”‚             Β·                β”‚  Β·             β”‚              Β·                                       β”‚    Β·     β”‚     Β·           β”‚                          Β·                           β”‚
β”‚         Β· β”‚             Β·                β”‚  Β·             β”‚              Β·                                       β”‚    Β·     β”‚     Β·           β”‚                          Β·                           β”‚
β”‚      9996 β”‚ AL-MAALIK LOCUM SERVICES LTD β”‚ 14351700       β”‚ 239 MOSELEY ROAD LEVENSHULME                         β”‚ M19 2LJ  β”‚ z_all_companies β”‚ [{'token': AL-MAALIK, 'rel_freq': 8.252432e-07}, {'… β”‚
β”‚      9997 β”‚ AL-MAALIK PHARMACIES LTD     β”‚ 14421852       β”‚ 239 MOSELEY ROAD LEVENSHULME                         β”‚ M19 2LJ  β”‚ z_all_companies β”‚ [{'token': AL-MAALIK, 'rel_freq': 8.252432e-07}, {'… β”‚
β”‚      9998 β”‚ AL-MAALIK PROPERTIES LTD     β”‚ 14412454       β”‚ 1041 STOCKPORT ROAD                                  β”‚ M19 2TF  β”‚ z_all_companies β”‚ [{'token': AL-MAALIK, 'rel_freq': 8.252432e-07}, {'… β”‚
β”‚      9999 β”‚ AL-MADAD OUTREACH LTD        β”‚ 14827390       β”‚ 124-128 CITY ROAD                                    β”‚ EC1V 2NX β”‚ z_all_companies β”‚ [{'token': AL-MADAD, 'rel_freq': 2.7508108e-07}, {'… β”‚
β”‚     10000 β”‚ AL-MADANI LTD                β”‚ 14415359       β”‚ 71-75 SHELTON STREET COVENT GARDEN                   β”‚ WC2H 9JQ β”‚ z_all_companies β”‚ [{'token': AL-MADANI, 'rel_freq': 2.7508108e-07}, {… β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
  ? rows (>9999 rows, 10 shown)                                                                                                                                                              7 columns
# Derive rarest tokens - these will be used for blocking
# This would be simpler if Duckdb list sort supported a lambda function
# but it doesn't, so we have to unnest the list and sort manually
# https://duckdb.org/docs/sql/functions/list.html#sorting-lists
sql = """
with
tokens_unnested as (
    SELECT
        unique_id,
        unnest(name_tokens_with_freq) as token_info
    FROM input_data_with_tokens
),
rare_tokens as (
    SELECT
        unique_id,
        array_agg(token_info.token ORDER BY token_info.rel_freq ASC)[:2] as rarest_tokens
    FROM tokens_unnested
    WHERE token_info.rel_freq < 0.01
    GROUP BY unique_id
)
select m.*, rarest_tokens
from input_data_with_tokens as m
left join rare_tokens on m.unique_id = rare_tokens.unique_id
order by m.unique_id
"""
input_data_with_tokens_and_rarest_tokens = duckdb.sql(sql)
input_data_with_tokens_and_rarest_tokens.show(max_rows=10, max_width=1000)
FloatProgress(value=0.0, layout=Layout(width='auto'), style=ProgressStyle(bar_color='black'))


β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ unique_id β”‚         company_name         β”‚ company_number β”‚                              address_concat                              β”‚ postcode β”‚ source_dataset  β”‚                                                                                name_tokens_with_freq                                                                                β”‚      rarest_tokens      β”‚
β”‚   int64   β”‚           varchar            β”‚    varchar     β”‚                                 varchar                                  β”‚ varchar  β”‚     varchar     β”‚                                                                       struct(token varchar, rel_freq float)[]                                                                       β”‚        varchar[]        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚         1 β”‚ 08 UK LTD                    β”‚ NULL           β”‚ 15 MADOC STREET LLANDUDNO CONWY WALES                                    β”‚ LL30 2TL β”‚ stockport       β”‚ [{'token': 08, 'rel_freq': 2.2006486e-06}, {'token': UK, 'rel_freq': 0.0059337737}, {'token': LTD, 'rel_freq': 0.14284134}]                                                         β”‚ [08, UK]                β”‚
β”‚         2 β”‚ 1 GR LTD                     β”‚ NULL           β”‚ 296-296A GORTON ROAD REDDISH STOCKPORT                                   β”‚ SK5 6RN  β”‚ stockport       β”‚ [{'token': 1, 'rel_freq': 0.000688803}, {'token': GR, 'rel_freq': 2.3656972e-05}, {'token': LTD, 'rel_freq': 0.14284134}]                                                           β”‚ [GR, 1]                 β”‚
β”‚         3 β”‚ 101 CANVAS PRINTS LTD        β”‚ NULL           β”‚ SUITE 44 4TH FLOOR HOULDSWORTH MILL HOULDSWORTH STREET REDDISH STOCKPORT β”‚ SK5 6DA  β”‚ stockport       β”‚ [{'token': 101, 'rel_freq': 3.1909403e-05}, {'token': CANVAS, 'rel_freq': 2.2006485e-05}, {'token': PRINTS, 'rel_freq': 3.163432e-05}, {'token': LTD, 'rel_freq': 0.14284134}]      β”‚ [CANVAS, PRINTS]        β”‚
β”‚         4 β”‚ 121 DIRECT MAIL LTD          β”‚ NULL           β”‚ UNIT 18 CHARTER HOUSE LATHAM CLOSE BREDBURY STOCKPORT                    β”‚ SK6 2SD  β”‚ stockport       β”‚ [{'token': 121, 'rel_freq': 1.7055027e-05}, {'token': DIRECT, 'rel_freq': 0.00050367345}, {'token': MAIL, 'rel_freq': 1.5679621e-05}, {'token': LTD, 'rel_freq': 0.14284134}]       β”‚ [MAIL, 121]             β”‚
β”‚         5 β”‚ 121 PERSONAL TRAINING LTD    β”‚ NULL           β”‚ 198 WILMSLOW ROAD HEALD GREEN STOCKPORT                                  β”‚ SK8 3BH  β”‚ stockport       β”‚ [{'token': 121, 'rel_freq': 1.7055027e-05}, {'token': PERSONAL, 'rel_freq': 8.8851186e-05}, {'token': TRAINING, 'rel_freq': 0.00092289696}, {'token': LTD, 'rel_freq': 0.14284134}] β”‚ [121, PERSONAL]         β”‚
β”‚         Β· β”‚             Β·                β”‚  Β·             β”‚              Β·                                                           β”‚    Β·     β”‚     Β·           β”‚                                                                                          Β·                                                                                          β”‚        Β·                β”‚
β”‚         Β· β”‚             Β·                β”‚  Β·             β”‚              Β·                                                           β”‚    Β·     β”‚     Β·           β”‚                                                                                          Β·                                                                                          β”‚        Β·                β”‚
β”‚         Β· β”‚             Β·                β”‚  Β·             β”‚              Β·                                                           β”‚    Β·     β”‚     Β·           β”‚                                                                                          Β·                                                                                          β”‚        Β·                β”‚
β”‚      9996 β”‚ AL-MAALIK LOCUM SERVICES LTD β”‚ 14351700       β”‚ 239 MOSELEY ROAD LEVENSHULME                                             β”‚ M19 2LJ  β”‚ z_all_companies β”‚ [{'token': AL-MAALIK, 'rel_freq': 8.252432e-07}, {'token': LOCUM, 'rel_freq': 6.6844696e-05}, {'token': SERVICES, 'rel_freq': 0.012372046}, {'token': LTD, 'rel_freq': 0.14284134}] β”‚ [AL-MAALIK, LOCUM]      β”‚
β”‚      9997 β”‚ AL-MAALIK PHARMACIES LTD     β”‚ 14421852       β”‚ 239 MOSELEY ROAD LEVENSHULME                                             β”‚ M19 2LJ  β”‚ z_all_companies β”‚ [{'token': AL-MAALIK, 'rel_freq': 8.252432e-07}, {'token': PHARMACIES, 'rel_freq': 4.676378e-06}, {'token': LTD, 'rel_freq': 0.14284134}]                                           β”‚ [AL-MAALIK, PHARMACIES] β”‚
β”‚      9998 β”‚ AL-MAALIK PROPERTIES LTD     β”‚ 14412454       β”‚ 1041 STOCKPORT ROAD                                                      β”‚ M19 2TF  β”‚ z_all_companies β”‚ [{'token': AL-MAALIK, 'rel_freq': 8.252432e-07}, {'token': PROPERTIES, 'rel_freq': 0.005062042}, {'token': LTD, 'rel_freq': 0.14284134}]                                            β”‚ [AL-MAALIK, PROPERTIES] β”‚
β”‚      9999 β”‚ AL-MADAD OUTREACH LTD        β”‚ 14827390       β”‚ 124-128 CITY ROAD                                                        β”‚ EC1V 2NX β”‚ z_all_companies β”‚ [{'token': AL-MADAD, 'rel_freq': 2.7508108e-07}, {'token': OUTREACH, 'rel_freq': 1.9530757e-05}, {'token': LTD, 'rel_freq': 0.14284134}]                                            β”‚ [AL-MADAD, OUTREACH]    β”‚
β”‚     10000 β”‚ AL-MADANI LTD                β”‚ 14415359       β”‚ 71-75 SHELTON STREET COVENT GARDEN                                       β”‚ WC2H 9JQ β”‚ z_all_companies β”‚ [{'token': AL-MADANI, 'rel_freq': 2.7508108e-07}, {'token': LTD, 'rel_freq': 0.14284134}]                                                                                           β”‚ [AL-MADANI]             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
  ? rows (>9999 rows, 10 shown)                                                                                                                                                                                                                                                                                                                                           8 columns
# Finally we're going to extract the 'house number' from the address, which, combined with the postcode will be used for matching

sql = """
CREATE OR REPLACE TABLE data_for_matching AS
select
    unique_id,
    source_dataset,
    company_name,
    company_number,
    COALESCE(
        REGEXP_EXTRACT(address_concat, '(\\d+[A-Z]?)'),
        REGEXP_EXTRACT(address_concat, '(\\S+)\\s+HOUSE', 1)
    ) AS first_num_in_address,
    postcode,
    name_tokens_with_freq,
    rarest_tokens
from input_data_with_tokens_and_rarest_tokens

"""
duckdb.execute(sql)
duckdb.table("data_for_matching").show(max_rows=10, max_width=400)
FloatProgress(value=0.0, layout=Layout(width='auto'), style=ProgressStyle(bar_color='black'))


β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ unique_id β”‚ source_dataset  β”‚         company_name         β”‚ company_number β”‚ first_num_in_address β”‚ postcode β”‚                                                                                name_tokens_with_freq                                                                                β”‚      rarest_tokens      β”‚
β”‚   int64   β”‚     varchar     β”‚           varchar            β”‚    varchar     β”‚       varchar        β”‚ varchar  β”‚                                                                       struct(token varchar, rel_freq float)[]                                                                       β”‚        varchar[]        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚         1 β”‚ stockport       β”‚ 08 UK LTD                    β”‚ NULL           β”‚ 15                   β”‚ LL30 2TL β”‚ [{'token': 08, 'rel_freq': 2.2006486e-06}, {'token': UK, 'rel_freq': 0.0059337737}, {'token': LTD, 'rel_freq': 0.14284134}]                                                         β”‚ [08, UK]                β”‚
β”‚         2 β”‚ stockport       β”‚ 1 GR LTD                     β”‚ NULL           β”‚ 296                  β”‚ SK5 6RN  β”‚ [{'token': 1, 'rel_freq': 0.000688803}, {'token': GR, 'rel_freq': 2.3656972e-05}, {'token': LTD, 'rel_freq': 0.14284134}]                                                           β”‚ [GR, 1]                 β”‚
β”‚         3 β”‚ stockport       β”‚ 101 CANVAS PRINTS LTD        β”‚ NULL           β”‚ 44                   β”‚ SK5 6DA  β”‚ [{'token': 101, 'rel_freq': 3.1909403e-05}, {'token': CANVAS, 'rel_freq': 2.2006485e-05}, {'token': PRINTS, 'rel_freq': 3.163432e-05}, {'token': LTD, 'rel_freq': 0.14284134}]      β”‚ [CANVAS, PRINTS]        β”‚
β”‚         4 β”‚ stockport       β”‚ 121 DIRECT MAIL LTD          β”‚ NULL           β”‚ 18                   β”‚ SK6 2SD  β”‚ [{'token': 121, 'rel_freq': 1.7055027e-05}, {'token': DIRECT, 'rel_freq': 0.00050367345}, {'token': MAIL, 'rel_freq': 1.5679621e-05}, {'token': LTD, 'rel_freq': 0.14284134}]       β”‚ [MAIL, 121]             β”‚
β”‚         5 β”‚ stockport       β”‚ 121 PERSONAL TRAINING LTD    β”‚ NULL           β”‚ 198                  β”‚ SK8 3BH  β”‚ [{'token': 121, 'rel_freq': 1.7055027e-05}, {'token': PERSONAL, 'rel_freq': 8.8851186e-05}, {'token': TRAINING, 'rel_freq': 0.00092289696}, {'token': LTD, 'rel_freq': 0.14284134}] β”‚ [121, PERSONAL]         β”‚
β”‚         Β· β”‚     Β·           β”‚             Β·                β”‚  Β·             β”‚  Β·                   β”‚    Β·     β”‚                                                                                          Β·                                                                                          β”‚        Β·                β”‚
β”‚         Β· β”‚     Β·           β”‚             Β·                β”‚  Β·             β”‚  Β·                   β”‚    Β·     β”‚                                                                                          Β·                                                                                          β”‚        Β·                β”‚
β”‚         Β· β”‚     Β·           β”‚             Β·                β”‚  Β·             β”‚  Β·                   β”‚    Β·     β”‚                                                                                          Β·                                                                                          β”‚        Β·                β”‚
β”‚      9996 β”‚ z_all_companies β”‚ AL-MAALIK LOCUM SERVICES LTD β”‚ 14351700       β”‚ 239                  β”‚ M19 2LJ  β”‚ [{'token': AL-MAALIK, 'rel_freq': 8.252432e-07}, {'token': LOCUM, 'rel_freq': 6.6844696e-05}, {'token': SERVICES, 'rel_freq': 0.012372046}, {'token': LTD, 'rel_freq': 0.14284134}] β”‚ [AL-MAALIK, LOCUM]      β”‚
β”‚      9997 β”‚ z_all_companies β”‚ AL-MAALIK PHARMACIES LTD     β”‚ 14421852       β”‚ 239                  β”‚ M19 2LJ  β”‚ [{'token': AL-MAALIK, 'rel_freq': 8.252432e-07}, {'token': PHARMACIES, 'rel_freq': 4.676378e-06}, {'token': LTD, 'rel_freq': 0.14284134}]                                           β”‚ [AL-MAALIK, PHARMACIES] β”‚
β”‚      9998 β”‚ z_all_companies β”‚ AL-MAALIK PROPERTIES LTD     β”‚ 14412454       β”‚ 1041                 β”‚ M19 2TF  β”‚ [{'token': AL-MAALIK, 'rel_freq': 8.252432e-07}, {'token': PROPERTIES, 'rel_freq': 0.005062042}, {'token': LTD, 'rel_freq': 0.14284134}]                                            β”‚ [AL-MAALIK, PROPERTIES] β”‚
β”‚      9999 β”‚ z_all_companies β”‚ AL-MADAD OUTREACH LTD        β”‚ 14827390       β”‚ 124                  β”‚ EC1V 2NX β”‚ [{'token': AL-MADAD, 'rel_freq': 2.7508108e-07}, {'token': OUTREACH, 'rel_freq': 1.9530757e-05}, {'token': LTD, 'rel_freq': 0.14284134}]                                            β”‚ [AL-MADAD, OUTREACH]    β”‚
β”‚     10000 β”‚ z_all_companies β”‚ AL-MADANI LTD                β”‚ 14415359       β”‚ 71                   β”‚ WC2H 9JQ β”‚ [{'token': AL-MADANI, 'rel_freq': 2.7508108e-07}, {'token': LTD, 'rel_freq': 0.14284134}]                                                                                           β”‚ [AL-MADANI]             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
  ? rows (>9999 rows, 10 shown)                                                                                                                                                                                                                                                                                       8 columns

We've now prepared our data for matching. We'll use a fairly straightforward matching model with one innovation: we need to define a comparison that is able to exploit the information in name_tokens_with_freq effectively.

The approach we're going to take is to define similarity as the number of tokens in common between two business names, adjusting for term frequency. This can be computed by multiplying together the token frequencies of the matching tokens.

We will create a comparison level for different thresholds of this value.

def calculate_tf_product_array_sql(token_rel_freq_array_name):

    return f"""
    list_intersect({token_rel_freq_array_name}_l, {token_rel_freq_array_name}_r)
        .list_transform(x -> x.rel_freq::float)
        .list_concat([1.0::FLOAT]) -- in case there are no matches
        .list_reduce((p, q) -> p * q)
    """
# Prepare our two input datasets

df_stockport = duckdb.sql(
    "select * from data_for_matching where source_dataset = 'stockport'"
)

df_all_companies = duckdb.sql(
    "select * from data_for_matching where source_dataset = 'z_all_companies'"
)
from splink import DuckDBAPI, Linker, SettingsCreator, block_on
import splink.comparison_library as cl
con = duckdb.connect(":default:")
db_api = DuckDBAPI(connection=con)

# Note NULL level definition is surprising due to https://github.com/moj-analytical-services/splink/issues/2929
settings = SettingsCreator(
    link_type="link_only",
    unique_id_column_name="unique_id",
    probability_two_random_records_match=1/1e6,
    comparisons=[
        {
            "output_column_name": "name_tokens_with_freq",
            "comparison_levels": [
                {
                    "sql_condition": f"""
                    {calculate_tf_product_array_sql("name_tokens_with_freq")} > 2
                    or "name_tokens_with_freq_l" IS NULL OR "name_tokens_with_freq_r" IS NULL""",
                    "label_for_charts": "name_tokens_with_freq is NULL",
                    "is_null_level": True,
                },
                {
                    "sql_condition": f"""
                    {calculate_tf_product_array_sql("name_tokens_with_freq")} < 1e-10
                    """,
                    "label_for_charts": "Array product is less than 1e-10",
                },
                {
                    "sql_condition": f"""
                    {calculate_tf_product_array_sql("name_tokens_with_freq")} < 1e-8
                    """,
                    "label_for_charts": "Array product is less than 1e-8",
                },
                {
                    "sql_condition": f"""
                    {calculate_tf_product_array_sql("name_tokens_with_freq")} < 1e-6
                    """,
                    "label_for_charts": "Array product is less than 1e-6",
                },
                {
                    "sql_condition": f"""
                    {calculate_tf_product_array_sql("name_tokens_with_freq")} < 1e-4
                    """,
                    "label_for_charts": "Array product is less than 1e-4",
                },
                {"sql_condition": "ELSE", "label_for_charts": "All other comparisons"},
            ],
            "comparison_description": "ExactMatch",
        },
        cl.PostcodeComparison("postcode"),
        cl.ExactMatch("first_num_in_address"),
    ],
    blocking_rules_to_generate_predictions=[
        block_on("postcode"),
        block_on("rarest_tokens", "substr(postcode,1,3)"),
        "l.rarest_tokens[1] = r.rarest_tokens[2] and substr(l.company_name,1,3) = substr(r.company_name,1,3)",
        "l.rarest_tokens[2] = r.rarest_tokens[1] and substr(l.company_name,1,3) = substr(r.company_name,1,3)",
        block_on("company_name"),
    ],
    additional_columns_to_retain=["company_name"],
    retain_intermediate_calculation_columns=True,
    retain_matching_columns=True,
)

df_stockport_sdf = db_api.register(df_stockport)
df_all_companies_sdf = db_api.register(df_all_companies)
linker = Linker([df_stockport_sdf, df_all_companies_sdf], settings)
# Probably not worth training m values given how few columns we have;
# by not training them we'll be using the defaults

linker.training.estimate_u_using_random_sampling(max_pairs=1e7)
linker.visualisations.match_weights_chart()
----- Estimating u probabilities using random sampling -----


Estimating u with: max_pairs = 10,000,000, min_count_per_level = 100, num_chunks = 10



Estimating u for: name_tokens_with_freq (Comparison 1 of 3)


  Running probe chunk (~1.00% of max_pairs)


  Min u_count: 0 for comparison level Array product is less than 1e-8 (cvv=3)


  Probe did not converge; restarting with normal chunking



  Running chunk 1/10


  Count of 1 for level Array product is less than 1e-10 (cvv=4). Chunk took 0.8 seconds.


  Min u_count not hit, continuing.


  Running chunk 2/10


  Count of 1 for level Array product is less than 1e-10 (cvv=4). Chunk took 0.8 seconds.


  Min u_count not hit, continuing.


  Running chunk 3/10


  Count of 1 for level Array product is less than 1e-10 (cvv=4). Chunk took 0.9 seconds.


  Min u_count not hit, continuing.


  Running chunk 4/10


  Count of 1 for level Array product is less than 1e-10 (cvv=4). Chunk took 0.9 seconds.


  Min u_count not hit, continuing.


  Running chunk 5/10


  Count of 2 for level Array product is less than 1e-10 (cvv=4). Chunk took 0.9 seconds.


  Min u_count not hit, continuing.


  Running chunk 6/10


  Count of 2 for level Array product is less than 1e-10 (cvv=4). Chunk took 0.9 seconds.


  Min u_count not hit, continuing.


  Running chunk 7/10


  Count of 3 for level Array product is less than 1e-10 (cvv=4). Chunk took 0.9 seconds.


  Min u_count not hit, continuing.


  Running chunk 8/10


  Count of 5 for level Array product is less than 1e-10 (cvv=4). Chunk took 0.8 seconds.


  Min u_count not hit, continuing.


  Running chunk 9/10


  Count of 6 for level Array product is less than 1e-10 (cvv=4). Chunk took 0.6 seconds.


  Min u_count not hit, continuing.


  Running chunk 10/10


  Count of 6 for level Array product is less than 1e-10 (cvv=4). Chunk took 0.8 seconds.


  Min u_count not hit, continuing.



Estimating u for: postcode (Comparison 2 of 3)


  Running probe chunk (~1.00% of max_pairs)


  Min u_count: 7 for comparison level Exact match on full postcode (cvv=4)


  Probe did not converge; restarting with normal chunking



  Running chunk 1/10


  Count of 52 for level Exact match on full postcode (cvv=4). Chunk took 1.1 seconds.


  Min u_count not hit, continuing.


  Running chunk 2/10


  Count of 122 for level Exact match on full postcode (cvv=4). Chunk took 0.9 seconds.


  Exiting early since min count of 122 exceeds min_count_per_level = 100



Estimating u for: first_num_in_address (Comparison 3 of 3)


  Running probe chunk (~1.00% of max_pairs)


  Min u_count: 3,102 for comparison level Exact match on first_num_in_address (cvv=1)


  Exiting early since min count of 3,102 exceeds min_count_per_level = 100



Estimated u probabilities using random sampling



Your model is not yet fully trained. Missing estimates for:
    - name_tokens_with_freq (no m values are trained).
    - postcode (no m values are trained).
    - first_num_in_address (no m values are trained).
# Get some results

df_predictions = linker.inference.predict(threshold_match_weight=-5)



# Since the companies house data is a canonical list, here we're taking the
# best matching company for each Stockport business
sql = f"""
WITH ranked_matches AS (
    SELECT *,
           ROW_NUMBER() OVER (PARTITION BY unique_id_l ORDER BY match_weight DESC) as rank
    FROM {df_predictions.physical_name}
),
best_match as (
    select * from ranked_matches
    where rank = 1
    order by match_weight desc
),
matched_stockport_ids as (
    select distinct unique_id_l
    from best_match
)
select * from best_match

order by match_probability desc
"""

ranked_matches = con.sql(sql)
ranked_matches.show(max_rows=10, max_width=400)
Blocking time: 0.64 seconds


Predict time (post-blocking): 0.75 seconds



 -- WARNING --
You have called predict(), but there are some parameter estimates which have neither been estimated or specified in your settings dictionary.  To produce predictions the following untrained parameters will use default values.
Comparison: 'name_tokens_with_freq':
    m values not fully trained
Comparison: 'postcode':
    m values not fully trained
Comparison: 'first_num_in_address':
    m values not fully trained


β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”
β”‚    match_weight    β”‚  match_probability  β”‚ source_dataset_l β”‚ source_dataset_r β”‚ unique_id_l β”‚ unique_id_r β”‚      name_tokens_with_freq_l      β”‚ name_tokens_with_fr… β”‚ gamma_name_tokens_w… β”‚ mw_name_tokens_with… β”‚ … β”‚ first_num_in_addres… β”‚ gamma_first_num_in_… β”‚ mw_first_num_in_add… β”‚   rarest_tokens_l    β”‚   rarest_tokens_r    β”‚    company_name_l    β”‚    company_name_r    β”‚ match_key β”‚ rank  β”‚
β”‚       double       β”‚       double        β”‚     varchar      β”‚     varchar      β”‚    int64    β”‚    int64    β”‚ struct(token varchar, rel_freq f… β”‚ struct(token varcha… β”‚        int32         β”‚        double        β”‚ … β”‚       varchar        β”‚        int32         β”‚        double        β”‚      varchar[]       β”‚      varchar[]       β”‚       varchar        β”‚       varchar        β”‚  varchar  β”‚ int64 β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€
β”‚ 19.607912884711958 β”‚  0.9999987485038272 β”‚ stockport        β”‚ z_all_companies  β”‚         116 β”‚      381637 β”‚ [{'token': AEROCO, 'rel_freq': 1… β”‚ [{'token': AEROCO, … β”‚                    4 β”‚     20.6472507829629 β”‚ … β”‚ 6                    β”‚                    1 β”‚    4.934057730908987 β”‚ [AEROCO, INTERNATIO… β”‚ [AEROCO, INTERNATIO… β”‚ AEROCO GROUP INTERN… β”‚ AEROCO GROUP INTERN… β”‚ 0         β”‚     1 β”‚
β”‚ 19.607912884711958 β”‚  0.9999987485038272 β”‚ stockport        β”‚ z_all_companies  β”‚        1013 β”‚      394213 β”‚ [{'token': CORNERSTONE, 'rel_fre… β”‚ [{'token': CORNERST… β”‚                    4 β”‚     20.6472507829629 β”‚ … β”‚ 2                    β”‚                    1 β”‚    4.934057730908987 β”‚ [TELECOMMUNICATIONS… β”‚ [TELECOMMUNICATIONS… β”‚ CORNERSTONE TELECOM… β”‚ CORNERSTONE TELECOM… β”‚ 0         β”‚     1 β”‚
β”‚ 19.607912884711958 β”‚  0.9999987485038272 β”‚ stockport        β”‚ z_all_companies  β”‚        2013 β”‚      409852 β”‚ [{'token': ICELAND, 'rel_freq': … β”‚ [{'token': ICELAND,… β”‚                    4 β”‚     20.6472507829629 β”‚ … β”‚                      β”‚                    1 β”‚    4.934057730908987 β”‚ [ICELAND, FROZEN]    β”‚ [ICELAND, FROZEN]    β”‚ ICELAND FROZEN FOOD… β”‚ ICELAND FROZEN FOOD… β”‚ 0         β”‚     1 β”‚
β”‚ 19.607912884711958 β”‚  0.9999987485038272 β”‚ stockport        β”‚ z_all_companies  β”‚        2015 β”‚      410995 β”‚ [{'token': IDEAL, 'rel_freq': 5.… β”‚ [{'token': IDEAL, '… β”‚                    4 β”‚     20.6472507829629 β”‚ … β”‚ 24B                  β”‚                    1 β”‚    4.934057730908987 β”‚ [SEATING, IDEAL]     β”‚ [SEATING, IDEAL]     β”‚ IDEAL SEATING LTD    β”‚ IDEAL SEATING LTD    β”‚ 0         β”‚     1 β”‚
β”‚ 19.607912884711958 β”‚  0.9999987485038272 β”‚ stockport        β”‚ z_all_companies  β”‚        2931 β”‚      424305 β”‚ [{'token': OFFERTON, 'rel_freq':… β”‚ [{'token': OFFERTON… β”‚                    4 β”‚     20.6472507829629 β”‚ … β”‚                      β”‚                    1 β”‚    4.934057730908987 β”‚ [OFFERTON, DENTIST]  β”‚ [OFFERTON, FOLD]     β”‚ OFFERTON FOLD DENTA… β”‚ OFFERTON FOLD DENTA… β”‚ 0         β”‚     1 β”‚
β”‚          Β·         β”‚           Β·         β”‚     Β·            β”‚        Β·         β”‚          Β·  β”‚         Β·   β”‚                 Β·                 β”‚          Β·           β”‚                    Β· β”‚             Β·        β”‚ … β”‚          Β·           β”‚                    Β· β”‚            Β·         β”‚     Β·                β”‚     Β·                β”‚      Β·               β”‚        Β·             β”‚ Β·         β”‚     Β· β”‚
β”‚          Β·         β”‚           Β·         β”‚     Β·            β”‚        Β·         β”‚          Β·  β”‚         Β·   β”‚                 Β·                 β”‚          Β·           β”‚                    Β· β”‚             Β·        β”‚ … β”‚          Β·           β”‚                    Β· β”‚            Β·         β”‚     Β·                β”‚     Β·                β”‚      Β·               β”‚        Β·             β”‚ Β·         β”‚     Β· β”‚
β”‚          Β·         β”‚           Β·         β”‚     Β·            β”‚        Β·         β”‚          Β·  β”‚         Β·   β”‚                 Β·                 β”‚          Β·           β”‚                    Β· β”‚             Β·        β”‚ … β”‚          Β·           β”‚                    Β· β”‚            Β·         β”‚     Β·                β”‚     Β·                β”‚      Β·               β”‚        Β·             β”‚ Β·         β”‚     Β· β”‚
β”‚ -2.418257860572516 β”‚ 0.15759816330593598 β”‚ stockport        β”‚ z_all_companies  β”‚        3371 β”‚      882220 β”‚ [{'token': RENTHAL, 'rel_freq': … β”‚ [{'token': RENTHAL,… β”‚                    2 β”‚    8.347661149696824 β”‚ … β”‚                      β”‚                    1 β”‚    4.934057730908987 β”‚ [RENTHAL]            β”‚ [RENTHAL]            β”‚ RENTHAL LTD          β”‚ RENTHAL LIMITED      β”‚ 1         β”‚     1 β”‚
β”‚ -4.720103692228599 β”‚ 0.03655397376506417 β”‚ stockport        β”‚ z_all_companies  β”‚        1230 β”‚      617874 β”‚ [{'token': DOM, 'rel_freq': 1.07… β”‚ [{'token': DOM, 're… β”‚                    2 β”‚    8.347661149696824 β”‚ … β”‚ 26                   β”‚                    1 β”‚    4.934057730908987 β”‚ [DOM, PROPERTIES]    β”‚ [DOM, PROPERTIES]    β”‚ DOM PROPERTIES LTD   β”‚ DOM PROPERTIES LIMI… β”‚ 1         β”‚     1 β”‚
β”‚ -4.720103692228599 β”‚ 0.03655397376506417 β”‚ stockport        β”‚ z_all_companies  β”‚        2140 β”‚      634190 β”‚ [{'token': JAMES, 'rel_freq': 0.… β”‚ [{'token': JAMES, '… β”‚                    2 β”‚    8.347661149696824 β”‚ … β”‚ 4                    β”‚                    1 β”‚    4.934057730908987 β”‚ [JAMES, RETAIL]      β”‚ [JAMES, RETAIL]      β”‚ JAMES RETAIL LIMITED β”‚ JAMES RETAIL LIMITED β”‚ 1         β”‚     1 β”‚
β”‚ -4.720103692228599 β”‚ 0.03655397376506417 β”‚ stockport        β”‚ z_all_companies  β”‚        3986 β”‚     1036621 β”‚ [{'token': TGL, 'rel_freq': 4.12… β”‚ [{'token': TGL, 're… β”‚                    2 β”‚    8.347661149696824 β”‚ … β”‚                      β”‚                    1 β”‚    4.934057730908987 β”‚ [TGL, SOLUTIONS]     β”‚ [TGL, SOLUTIONS]     β”‚ TGL SOLUTIONS LTD    β”‚ TGL SOLUTIONS LIMIT… β”‚ 1         β”‚     1 β”‚
β”‚ -4.720103692228599 β”‚ 0.03655397376506417 β”‚ stockport        β”‚ z_all_companies  β”‚        3657 β”‚      508179 β”‚ [{'token': SIMULATION, 'rel_freq… β”‚ [{'token': SIMULATI… β”‚                    2 β”‚    8.347661149696824 β”‚ … β”‚ 10                   β”‚                    1 β”‚    4.934057730908987 β”‚ [SIMULATION, SOLUTI… β”‚ [SIMULATION, SOLUTI… β”‚ SIMULATION SOLUTION… β”‚ SIMULATION SOLUTION… β”‚ 1         β”‚     1 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”˜
  2719 rows (10 shown)                                                                                                                                                                                                                                                                                                                                                                   24 columns (19 shown)
sql = """
CREATE OR REPLACE TABLE comparison AS
select * from ranked_matches
where company_name_l != company_name_r
order by random()
limit 1
"""
duckdb.execute(sql)


sql = """
SELECT match_probability, COLUMNS('^(t[^f_].*|b[^f_].*|[^tb].*_l)$') AS '\\1'
FROM comparison
UNION ALL
SELECT match_probability, COLUMNS('^(t[^f_].*|b[^f_].*|[^tb].*_r)$') AS '\\1'
FROM comparison;
"""
duckdb.sql(sql).show(max_rows=10, max_width=200)

recs = duckdb.table("comparison").arrow().read_all().to_pylist()
linker.visualisations.waterfall_chart(recs)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ match_probability  β”‚ source_dataset_l β”‚ unique_id_l β”‚              name_tokens_with_freq_l               β”‚ postcode_l β”‚ first_num_in_address_l β”‚ rarest_tokens_l β”‚          company_name_l           β”‚
β”‚       double       β”‚     varchar      β”‚    int64    β”‚      struct(token varchar, rel_freq float)[]       β”‚  varchar   β”‚        varchar         β”‚    varchar[]    β”‚              varchar              β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ 0.9999987485038272 β”‚ stockport        β”‚         136 β”‚ [{'token': AIR, 'rel_freq': 0.00022501631}, {'toke β”‚ KT6 6AP    β”‚ 171                    β”‚ [CHARTER, AIR]  β”‚ AIR CHARTER SERVICE LIMITED       β”‚
β”‚                    β”‚                  β”‚             β”‚ n': CHARTER, 'rel_freq': 2.2006485e-05}, {'token': β”‚            β”‚                        β”‚                 β”‚                                   β”‚
β”‚                    β”‚                  β”‚             β”‚  SERVICE, 'rel_freq': 0.00064121396}, {'token': L… β”‚            β”‚                        β”‚                 β”‚                                   β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ 0.9999987485038272 β”‚ z_all_companies  β”‚      754937 β”‚ [{'token': AIR, 'rel_freq': 0.00022501631}, {'toke β”‚ KT6 6AP    β”‚ 171                    β”‚ [CHARTER, AIR]  β”‚ AIR CHARTER SERVICE GROUP LIMITED β”‚
β”‚                    β”‚                  β”‚             β”‚ n': CHARTER, 'rel_freq': 2.2006485e-05}, {'token': β”‚            β”‚                        β”‚                 β”‚                                   β”‚
β”‚                    β”‚                  β”‚             β”‚  SERVICE, 'rel_freq': 0.00064121396}, {'token': G… β”‚            β”‚                        β”‚                 β”‚                                   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
import altair as alt
# Plot histogram of match weights

# This list does not include any stockport records where no comparison pairs were made
# because blocking rules did not find any potential matches

# Add in unmatched records
sql = """
WITH matched_stockport_ids as (
    select distinct unique_id_l
    from ranked_matches
)
select
    -10 as match_weight,
    0.0 as match_probability,
from data_for_matching t
where t.source_dataset = 'stockport'
and t.unique_id not in (select unique_id_l from matched_stockport_ids)
"""

unmatched_records = duckdb.sql(sql)
unmatched_records.show(max_rows=10, max_width=400)


sql = """
select match_probability, match_weight from ranked_matches
union all
select match_probability, match_weight from unmatched_records
order by match_probability desc
"""

all_records = con.sql(sql)

# Create the histogram
chart = (
    alt.Chart(all_records.df())
    .mark_bar()
    .encode(
        alt.X("match_weight:Q", bin=alt.Bin(maxbins=50), title="Match Weight"),
        alt.Y("count():Q", title="Count"),
    )
    .properties(title="Distribution of Match Probabilities", width=600, height=400)
)

# Save the chart
chart
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ match_weight β”‚ match_probability β”‚
β”‚    int32     β”‚   decimal(2,1)    β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚          -10 β”‚               0.0 β”‚
β”‚          -10 β”‚               0.0 β”‚
β”‚          -10 β”‚               0.0 β”‚
β”‚          -10 β”‚               0.0 β”‚
β”‚          -10 β”‚               0.0 β”‚
β”‚           Β·  β”‚                Β·  β”‚
β”‚           Β·  β”‚                Β·  β”‚
β”‚           Β·  β”‚                Β·  β”‚
β”‚          -10 β”‚               0.0 β”‚
β”‚          -10 β”‚               0.0 β”‚
β”‚          -10 β”‚               0.0 β”‚
β”‚          -10 β”‚               0.0 β”‚
β”‚          -10 β”‚               0.0 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
  1821 rows (10 shown)   2 columns

Extensions and final notesΒΆ

When running this against the full companies house data rather than the small subset here, the number of unmatched records (the bar on the left hand side of the histogram) reduces to about 890 records.

There are a number of ways the accuracy of this example could be improved:

  • Additional data cleaning, e.g. dealing better with company names like A B C Engineering vs ABC engineering, or translating word aliases like LTD to LIMITED
  • A more sophisticated approach to tokenisation
  • Blocking could be improved in various ways, e.g. by blocking on address tokens, using more 'rare' tokens, using partial postcodes better and so on
  • Allowing fuzzy matching on tokens in the company name, not just full token matching