| perch | R Documentation |
**Bird note**: A starling tests a perch before committing its weight to it —
sitting briefly, sensing stability, probing whether this particular spot will
hold before deciding to stay or move on. perch() does exactly that to
a threshold: it tries every candidate value across your scored pairs and shows
you precisely what the linkage looks like at each one — match count, link rate,
clerical burden — so you can commit to the perch that holds, informed by the
same benchmarks the AIHW, WA Data Linkage Unit, and PHRN use in practice.
Sweeps a range of candidate Fellegi-Sunter threshold values across a scored
pairs object and returns a structured sensitivity table with one row per
candidate cutoff. Each row reports the number and percentage of pairs that
would be accepted or rejected at that threshold, the number falling in a
configurable clerical review zone around it, and (if n_records_df1 is
supplied) the estimated link rate for the primary dataset. Four canonical
thresholds are highlighted in the printed table and the plot with annotations
from the three key Australian and international linkage authorities — AIHW,
WA Data Linkage Unit, and the Population Health Research Network (PHRN).
Call perch() after murmuration_plot confirms your weight
distribution is bimodal, then pass the chosen value to
murmuration(threshold_value = ...).
perch(
pairs_pred,
weight_col = "weights",
n_records_df1 = NULL,
thresholds = seq(5, 30, by = 1),
clerical_window = 6,
highlight_thresholds = c(10, 15, 17, 20),
report = TRUE,
plot = TRUE,
palette = "sch",
interactive = FALSE
)
pairs_pred |
A pairs object (data frame) as returned by
|
weight_col |
Character. Name of the numeric linkage weight column.
Default |
n_records_df1 |
Integer or |
thresholds |
Numeric vector of candidate threshold values to evaluate.
Default |
clerical_window |
Numeric. Width of the clerical review zone centred on
each threshold value: the band
|
highlight_thresholds |
Numeric vector. Threshold values to mark with
|
report |
Logical. If |
plot |
Logical. If |
palette |
Character. Colour palette: |
interactive |
Logical. If |
## Interpreting the output
Use perch() together with murmuration_plot:
murmuration_plot() confirms the weight distribution is bimodal
and shows you where the valley between the two clusters lies.
perch() quantifies the match count, link rate, and clerical
burden at each candidate cutoff in that valley.
Choose the threshold that best balances precision and recall for your
study design (see benchmarks below), then pass it to
murmuration(threshold_value = ...).
The n_clerical column is the key diagnostic for threshold placement.
A large n_clerical relative to n_above means the threshold
sits in a high-density region of the score distribution — a small shift
would reclassify many pairs. You want to sit in the valley (low density),
not on a slope (high density). If every threshold in your range has a large
clerical burden, the two score modes overlap substantially — this is a
signal that the variable set or blocking strategy needs revision before
threshold selection will be meaningful.
## Australian and international linkage benchmarks
The Fellegi-Sunter weights are log-likelihood ratios and are **dataset-specific** — no universal cutoff exists. These benchmarks from recognised Australian and international linkage authorities inform where to look:
| Threshold | Authority and basis | |—|—| | **10–20 (clerical zone)** | **AIHW and WA Data Linkage Unit (WADLU)**: the established practice is a two-threshold approach with a clerical review zone of roughly 10–20. Pairs above ~20 are auto-accepted as matches; pairs below ~10 are auto-rejected as non-matches; pairs in between are sent for human adjudication. When operating without a dedicated clerical reviewer (as in most routine surveillance settings), the single threshold should sit somewhere in this range, with the direction of error dictated by the study design. | | **~15–20** | **Population Health Research Network (PHRN)**: no universal cutoff is mandated, but the operational target is a false-match rate below 0.5 | **12–17** | **starling balanced default** — typical starting point for SCPHU routine surveillance linkage with a complete variable set. | | **17–22** | **High specificity** — appropriate when false matches carry high analytical cost, e.g. vaccine effectiveness studies where falsely attributing vaccination status to an unvaccinated person biases the VE estimate downward. | | **8–12** | **High sensitivity** — suitable for small datasets or rare-disease surveillance where missing a true match is the dominant concern. |
## Note on link rate
The link_rate column approximates the proportion of primary-dataset
records (df1) that would receive at least one accepted match at a
given threshold. It counts accepted pairs above threshold rather than unique
linked records (one df1 record can appear in multiple pairs), so it is an
upper bound on the true link rate. Nonetheless it is useful for spotting
the point at which raising the threshold begins to meaningfully reduce
coverage — a sharp inflection in the link rate curve often marks the valley
between the two score modes.
When plot = FALSE, returns the sensitivity data frame
invisibly. When plot = TRUE (default), prints the data frame to the
console (if report = TRUE) and returns the plot object. The data
frame has one row per threshold value and the following columns:
thresholdNumeric. The candidate cutoff.
n_aboveInteger. Pairs with weight \geq threshold
(accepted as matches at this cutoff).
n_belowInteger. Pairs with weight < threshold
(rejected as non-matches).
pct_aboveNumeric. Percentage of all pairs above threshold.
n_clericalInteger. Pairs in the clerical review zone
[threshold - clerical_window/2,
threshold + clerical_window/2].
pct_clericalNumeric. Percentage in the clerical zone.
link_rateNumeric or NA. Proportion of
n_records_df1 records that would receive a match. Only populated
when n_records_df1 is supplied.
referenceCharacter. Annotation for the four highlighted
threshold values; NA for all others.
Australian Institute of Health and Welfare (2021). Data linkage: cohort studies. AIHW, Canberra. https://www.aihw.gov.au/reports/methods-data-development/data-linkage
Holman, C.D.J. et al. (1999). A population-based linkage study of 3.4 million records in Western Australia. Australian and New Zealand Journal of Public Health, 23(5): 453–459. doi:10.1111/j.1467-842X.1999.tb01297.x
Population Health Research Network (2023). PHRN Linkage Guidelines. PHRN, Perth. https://www.phrn.org.au/
Fellegi, I. and Sunter, A. (1969). A theory for record linkage. Journal of the American Statistical Association, 64(328): 1183–1210. doi:10.2307/2286061
murmuration for the linkage step.
murmuration_plot for visual weight distribution inspection.
preflight for pre-linkage data quality auditing.
flock for blocking variable construction.
## Not run:
# Standard workflow: score pairs then perch to find the best threshold
library(reclin2)
pairs <- pair_blocking(df1, df2, "block2")
compare_pairs(pairs,
on = c("lettername1", "lettername2", "dob", "medicare10"),
default_comparator = jaro_winkler(0.9), inplace = TRUE)
m <- problink_em(
~ lettername1 + lettername2 + dob + medicare10, data = pairs)
pairs_pred <- predict(m, pairs = pairs, add = TRUE)
# Step 1: inspect the distribution
murmuration_plot(pairs_pred, threshold = 17)
# Step 2: quantify the trade-off across the plausible range
results <- perch(pairs_pred, n_records_df1 = nrow(df1))
# Step 3: narrow to the AIHW clerical zone for fine-grained inspection
results <- perch(pairs_pred,
thresholds = seq(10, 20, by = 0.5),
n_records_df1 = nrow(df1))
# Get only the data frame (no plot)
tbl <- perch(pairs_pred, plot = FALSE)
# Interactive dual-panel plot for exploratory use
perch(pairs_pred, n_records_df1 = nrow(df1), interactive = TRUE)
# Greyscale for publication figures
perch(pairs_pred, palette = "grey")
# Step 4: apply the chosen threshold
linked <- murmuration(df1, df2,
linkage_type = "v2c",
event_date = "onset_date",
blocking_var = "block2",
compare_vars = c("lettername1", "lettername2", "dob", "medicare10"),
threshold_value = 18)
## End(Not run)
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.