## ----include = FALSE----------------------------------------------------------
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = '#>'
)

## ----setup--------------------------------------------------------------------
library(irelink)
library(ggplot2)

## ----head---------------------------------------------------------------------
head(febrl4a)
head(febrl4b)

## ----completeness, message = FALSE--------------------------------------------
con <- DBI::dbConnect(duckdb::duckdb())
comp <- il_completeness(febrl4a, febrl4b, con = con)
comp

## ----completeness-plot, fig.width = 6, fig.height = 3.5-----------------------
autoplot(comp)

## ----spec---------------------------------------------------------------------
spec <- il_spec() |>
  il_compare(given_name, cl_name()) |>
  il_compare(surname, cl_name()) |>
  il_compare(date_of_birth, cl_exact()) |>
  il_compare(postcode, cl_exact()) |>
  il_block_on(surname) |>
  il_block_on(given_name)

spec

## ----model--------------------------------------------------------------------
model <- il_model(
  febrl4a, febrl4b,
  spec = spec,
  con = con,
  link_type = 'link'
)

model

## ----train--------------------------------------------------------------------
model <- il_estimate_prior(
  model,
  block_on(given_name, surname),
  block_on(surname, suburb),
  recall = 0.6
)

model <- il_estimate_u(model, max_pairs = 1e5)
model <- il_estimate_em(model, block_on(surname))
model <- il_estimate_em(model, block_on(suburb))

## ----weights, fig.width = 6, fig.height = 3.5---------------------------------
autoplot(model)

## ----params, fig.width = 7, fig.height = 4------------------------------------
autoplot(model, type = 'parameters')

## ----weights-table------------------------------------------------------------
il_weights(model)

## ----predict------------------------------------------------------------------
predictions <- predict(model, threshold = 0.5)
nrow(predictions)

## ----hist, fig.width = 6, fig.height = 3--------------------------------------
autoplot(predictions)

## ----cluster------------------------------------------------------------------
clusters <- il_cluster(predictions, threshold = 0.85)
head(clusters)

## ----labels-------------------------------------------------------------------
# Extract entity number from rec_id (e.g., "rec-1070-org" -> "1070")
entity_a <- sub('^rec-(\\d+)-org$', '\\1', febrl4a$rec_id)
entity_b <- sub('^rec-(\\d+)-dup-\\d+$', '\\1', febrl4b$rec_id)

# Build id-entity lookup (unique_id auto-generated by il_model)
ids_a <- data.frame(unique_id = seq_len(nrow(febrl4a)), entity = entity_a)
ids_b <- data.frame(unique_id = seq_len(nrow(febrl4b)), entity = entity_b)

# True matches: same entity across tables
positives <- merge(ids_a, ids_b, by = 'entity')
names(positives) <- c('entity', 'unique_id_l', 'unique_id_r')
positives$is_match <- 1L
positives <- positives[, c('unique_id_l', 'unique_id_r', 'is_match')]

# Sample non-matching pairs
set.seed(42)
n_neg <- min(nrow(positives), 2000L)
neg_l <- sample(ids_a$unique_id, n_neg, replace = TRUE)
neg_r <- sample(ids_b$unique_id, n_neg, replace = TRUE)
ent_l <- ids_a$entity[match(neg_l, ids_a$unique_id)]
ent_r <- ids_b$entity[match(neg_r, ids_b$unique_id)]
negatives <- data.frame(
  unique_id_l = neg_l,
  unique_id_r = neg_r,
  is_match = ifelse(ent_l == ent_r, 1L, 0L)
)

labels <- rbind(positives, negatives)
nrow(labels)
sum(labels$is_match)

## ----accuracy, fig.width = 6, fig.height = 3.5--------------------------------
acc <- il_accuracy(model, labels = labels)
autoplot(acc)

## ----roc, fig.width = 5, fig.height = 4---------------------------------------
roc <- il_roc(model, labels = labels)
autoplot(roc)

## ----pr, fig.width = 5, fig.height = 4----------------------------------------
pr <- il_precision_recall(model, labels = labels)
autoplot(pr)

## ----cleanup------------------------------------------------------------------
il_cleanup(model)
DBI::dbDisconnect(con, shutdown = TRUE)

