Skip to content

Estimating m probabilities from labels

Open In Colab

Estimating m from a sample of pairwise labels

In this example, we estimate the m probabilities of the model from a table containing pairwise record comparisons which we know are 'true' matches. For example, these may be the result of work by a clerical team who have manually labelled a sample of matches.

The table must be in the following format:

source_dataset_l unique_id_l source_dataset_r unique_id_r
df_1 1 df_2 2
df_1 1 df_2 3

It is assumed that every record in the table represents a certain match.

Note that the column names above are the defaults. They should correspond to the values you've set for unique_id_column_name and source_dataset_column_name, if you've chosen custom values.

from splink.datasets import splink_dataset_labels
from splink.internals.misc import show
import duckdb

all_pairwise_labels = splink_dataset_labels.fake_1000_labels

# Choose labels indicating a match
pairwise_labels = (
    duckdb.sql("select * from all_pairwise_labels where clerical_match_score = 1")
    .arrow()
    .read_all()
)
show(pairwise_labels)
┌─────────────┬──────────────────┬─────────────┬──────────────────┬──────────────────────┐
│ unique_id_l │ source_dataset_l │ unique_id_r │ source_dataset_r │ clerical_match_score │
│    int64    │     varchar      │    int64    │     varchar      │        double        │
├─────────────┼──────────────────┼─────────────┼──────────────────┼──────────────────────┤
│           0 │ fake_1000        │           1 │ fake_1000        │                  1.0 │
│           0 │ fake_1000        │           2 │ fake_1000        │                  1.0 │
│           0 │ fake_1000        │           3 │ fake_1000        │                  1.0 │
│           1 │ fake_1000        │           2 │ fake_1000        │                  1.0 │
│           1 │ fake_1000        │           3 │ fake_1000        │                  1.0 │
│           2 │ fake_1000        │           3 │ fake_1000        │                  1.0 │
│           4 │ fake_1000        │           5 │ fake_1000        │                  1.0 │
│           7 │ fake_1000        │           8 │ fake_1000        │                  1.0 │
│           7 │ fake_1000        │           9 │ fake_1000        │                  1.0 │
│           7 │ fake_1000        │          10 │ fake_1000        │                  1.0 │
└─────────────┴──────────────────┴─────────────┴──────────────────┴──────────────────────┘
  10 rows                                                                      5 columns

We now proceed to estimate the Fellegi Sunter model:

from splink import splink_datasets

df = splink_datasets.fake_1000
show(df, rows=2)
┌───────────┬────────────┬─────────┬────────────┬─────────┬─────────────────────┬─────────┐
│ unique_id │ first_name │ surname │    dob     │  city   │        email        │ cluster │
│   int64   │  varchar   │ varchar │    date    │ varchar │       varchar       │  int64  │
├───────────┼────────────┼─────────┼────────────┼─────────┼─────────────────────┼─────────┤
│         0 │ Robert     │ Alan    │ 1971-06-24 │ NULL    │ robert255@smith.net │       0 │
│         1 │ Robert     │ Allen   │ 1971-05-24 │ NULL    │ roberta25@smith.net │       0 │
└───────────┴────────────┴─────────┴────────────┴─────────┴─────────────────────┴─────────┘
import splink.comparison_library as cl
from splink import DuckDBAPI, Linker, SettingsCreator, block_on

settings = SettingsCreator(
    link_type="dedupe_only",
    blocking_rules_to_generate_predictions=[
        block_on("first_name"),
        block_on("surname"),
    ],
    comparisons=[
        cl.NameComparison("first_name"),
        cl.NameComparison("surname"),
        cl.DateOfBirthComparison(
            "dob",
            input_is_string=False,
        ),
        cl.ExactMatch("city").configure(term_frequency_adjustments=True),
        cl.EmailComparison("email"),
    ],
    retain_intermediate_calculation_columns=True,
)
db_api = DuckDBAPI()
df_sdf = db_api.register(df)
linker = Linker(df_sdf, settings, log_level=None)
deterministic_rules = [
    "l.first_name = r.first_name and levenshtein(r.dob::VARCHAR, l.dob::VARCHAR) <= 1",
    "l.surname = r.surname and levenshtein(r.dob::VARCHAR, l.dob::VARCHAR) <= 1",
    "l.first_name = r.first_name and levenshtein(r.surname, l.surname) <= 2",
    "l.email = r.email",
]

linker.training.estimate_probability_two_random_records_match(deterministic_rules, recall=0.7)
linker.training.estimate_u_using_random_sampling(max_pairs=1e6)
You are using the default value for `max_pairs`, which may be too small and thus lead to inaccurate estimates for your model's u-parameters. Consider increasing to 1e8 or 1e9, which will result in more accurate estimates, but with a longer run time.
# Register the pairwise labels table with the database, and then use it to estimate the m values
pairwise_labels_sdf = db_api.register(pairwise_labels)
labels_df = linker.table_management.register_labels_table(pairwise_labels_sdf)
linker.training.estimate_m_from_pairwise_labels(labels_df)


# If the labels table already existing in the dataset you could run
# linker.training.estimate_m_from_pairwise_labels("labels_tablename_here")
training_blocking_rule = block_on("first_name")
linker.training.estimate_parameters_using_expectation_maximisation(training_blocking_rule)
<EMTrainingSession, blocking on l."first_name" = r."first_name", deactivating comparisons first_name>
linker.visualisations.parameter_estimate_comparisons_chart()
linker.visualisations.match_weights_chart()