library(FlexRL)
#> If you are happy with FlexRL, please cite us! If you are unhappy, please cite us anyway (and feel free to complain as well).
FlexRL is a package proposing flexible probabilistic Record Linkage with diagnostic tools for downstream inference on linked data. The record linkage algorithm StEM() uses a Stochastic Expectation Maximisation approach to combine 2 data sources and outputs the set of records referring to the same entities.
More details on the record linkage methodology; on the false discovery proportion estimation; and on the potential risks of downstream inference on linked data (Robach et al., in preparation).
Several other algorithms for record linkage are available in R. The link_with_*() wrappers of FlexRL run them through one common interface, so that the same diagnostics and false discovery proportion (FDP) estimators apply to all of them: link_with_FlexRL(), link_with_BRL(), link_with_fastLink(), link_with_multilink(), link_with_reclin2(), link_with_diyar(), link_with_fedmatch(). Each wrapper takes the two encoded sources and a named list of arguments forwarded to the underlying package, and returns a list with idxA, idxB (row indices of the linked records in dataA and dataB) and LinkScore (the linkage scores, NULL when the package does not return any).
This vignette runs these wrappers on the same pipeline for:
peakRAMNew <- function(...){
# Capture R expressions or function calls
args <- c(as.list(match.call(expand.dots = FALSE)$`...`), NULL)
Function <- sapply(args, function(e) paste0(deparse(e), collapse = ""))
Function <- sapply(Function, function(e) gsub(" ", "", e))
# Initialize containers for output
numfunc <- length(args)
totaltime <- vector("numeric", numfunc)
RAMused <- vector("numeric", numfunc)
RAMpeak <- vector("numeric", numfunc)
i <- 1
for(arg in args){
# Reset garbage collector and save baseline
start <- gc(verbose = FALSE, reset = TRUE)
# Evaluate regular and anonymous functions
evalTime <- system.time(result <- eval.parent(arg))
if(inherits(result, "function")){
evalTime <- system.time(output <- result())
rm(result)
}else{
output <- result
rm(result)
}
# Call garbage collector and save post-eval
end <- gc(verbose = FALSE, reset = FALSE)
rm(output)
# Calculate total and peak RAM used
totaltime[i] <- as.numeric(evalTime["elapsed"])
get_column_mb = function(object, before_column) {
ind = which(colnames(object) == before_column)
stopifnot(colnames(object)[ind+1] == "(Mb)")
if (length(ind) == 0) {
stop("No value found for that column")
}
# need to ref index and not colname because duplicated colnames for (Mb)
object[2, ind + 1]
}
RAMused[i] <- get_column_mb(end, "used")- get_column_mb(start, "used")
RAMpeak[i] <- get_column_mb(end, "max used") - get_column_mb(start, "max used")
i <- i + 1
}
data.frame("Function_Call" = Function,
"Elapsed_Time_sec" = totaltime,
"Total_RAM_Used_MiB" = RAMused,
"Peak_RAM_Used_MiB" = RAMpeak,
row.names = 1:numfunc)
}
wrapper_args <- function(method, prep_data, ...) {
PIVs <- names(prep_data$PIVs_config)
args <- c(...)
switch(method,
FlexRL = c(list(data = prep_data, threads = 4),
args[intersect(names(args), names(formals(FlexRL::StEM)))]),
BRL = c(list(flds = PIVs, types = rep("bi", length(PIVs)), nIter = 1000),
args[intersect(names(args), c(names(formals(BRL::compareRecords)), names(formals(BRL::bipartiteGibbs))))]),
fastLink = c(list(varnames = PIVs, tol.em = 1e-06, threshold.match = 0.5, return.all = TRUE, n.cores = 4),
args[intersect(names(args), names(formals(fastLink::fastLink)))]),
multilink = c(list(types = rep("bi", length(PIVs)), breaks = stats::setNames(as.list(rep(NA, length(PIVs))), PIVs),
duplicates = c(0, 0), n_iter = 1000, max_cc_size = 50),
args[intersect(names(args), c(names(formals(multilink::create_comparison_data)),
names(formals(multilink::reduce_comparison_data)),
names(formals(multilink::specify_prior)),
names(formals(multilink::gibbs_sampler)),
names(formals(multilink::find_bayes_estimate)),
names(formals(multilink::relabel_bayes_estimate))))]),
reclin2 = c(list(on = PIVs, formula = stats::as.formula(paste("~", paste(PIVs, collapse = "+"))),
type = "mpost", add = TRUE, variable = "selected", score = "mpost", threshold = 0.5),
args[intersect(names(args), c(names(formals(reclin2::pair)),
names(formals(reclin2::compare_pairs)),
names(formals(reclin2::problink_em)),
names(formals(stats::predict)),
names(formals(reclin2::select_threshold))))]),
diyar = c(list(probabilistic = TRUE, return_weights = TRUE),
args[intersect(names(args), c(names(formals(diyar::prob_score_range)),
names(formals(diyar::links_wf_probabilistic))))]),
fedmatch = c(list(by = PIVs, match_type = "multivar", unique_key_1 = "unique_key_A",
unique_key_2 = "unique_key_B", nthread = 4,
multivar_settings = fedmatch::build_multivar_settings(
compare_type = rep("indicator", length(PIVs)),
wgts = rep(1 / length(PIVs), length(PIVs)))),
args[intersect(names(args), names(formals(fedmatch::merge_plus)))])
)
}
# unix::rlimit_as(20e9) # may be needed on some OS
run_wrapper <- function(method, prep_data, arguments) {
PIVs <- names(prep_data$PIVs_config)
link_with <- get(paste0("link_with_", method), envir = asNamespace("FlexRL"))
if (method == "diyar") {
prep_data$encodedA <- prep_data$encodedA[, c(PIVs, "local_id", "source")]
prep_data$encodedB <- prep_data$encodedB[, c(PIVs, "local_id", "source")]
}
if (Sys.info()["sysname"] == "Darwin") {
tryCatch(link_with(prep_data$encodedA, prep_data$encodedB, arguments),
error = function(e) {
if (grepl("cannot allocate|memory|bad_alloc|too large|long vectors", conditionMessage(e), ignore.case = TRUE)) {
message(method, ": memory error (", conditionMessage(e), "), results reported as NA")
return(NULL)
}
stop(e)
})
} else if (Sys.info()["sysname"] %in% c("Linux","Windows")) {
tryCatch(callr::r(function(link_with, A, B, arguments) {link_with(A, B, arguments)},
args = list(link_with = link_with, A = prep_data$encodedA,
B = prep_data$encodedB, arguments = arguments)),
error = function(e) {
if (grepl("cannot allocate|memory|bad_alloc|too large|long vectors|crashed or was killed|signal 9|SIGKILL",
conditionMessage(e), ignore.case = TRUE)) {
message(method, ": memory error (", conditionMessage(e), "), results reported as NA")
return(NULL)
}
stop(e)
})
} else {
print("Unknown Operating System.")
}
}
evaluate_linkage <- function(fit, true_pairs, threshold = 0.5) {
if (is.null(fit)) return(rep(NA_real_, 5))
delta_result <- data.frame(idxA = fit$idxA, idxB = fit$idxB)
if (!is.null(fit$LinkScore)) delta_result <- delta_result[fit$LinkScore > threshold, , drop = FALSE]
linked_pairs <- do.call(paste, c(delta_result, list(sep = "_")))
true_positive <- length(intersect(linked_pairs, true_pairs))
false_positive <- length(setdiff(linked_pairs, true_pairs))
false_negative <- length(setdiff(true_pairs, linked_pairs))
c(TP = true_positive, FP = false_positive, FN = false_negative,
sensitivity = true_positive / (true_positive + false_negative),
FDP = false_positive / (true_positive + false_positive))
}
results_rows <- c("TP ", "FP ", "FN ",
"sensitivity ", "FDP ", "est. FDP score ",
"est. FDP* score ", "est. FDP* synth ", "max SMD ",
"min support IoU ", "max MMD ", "time (secs) ",
"memory (Mb) ")
methods <- c("BRL", "multilink", "fastLink", "reclin2", "diyar", "fedmatch", "FlexRL")
run_all_wrappers <- function(prep_data, variables_type, methods, FDP_estimation = TRUE,
maxIter4CV = 2, n_repeats = 3, ...) {
args <- list(...)
op <- options()
on.exit(options(op))
PIVs <- names(prep_data$PIVs_config)
true_pairs <- do.call(paste, c(prep_data$true_pairs, list(sep = "_")))
df_results <- data.frame(matrix(NA, nrow = length(results_rows), ncol = 0))
rownames(df_results) <- results_rows
t0 <- Sys.time()
MeM <- peakRAMNew(naive_fit <- naive_linkage(PIVs = PIVs, encodedA = prep_data$encodedA, encodedB = prep_data$encodedB))
elapsed <- as.numeric(Sys.time() - t0, units = "secs")
df_results[1:5, "Naive"] <- evaluate_linkage(list(idxA = naive_fit[[1]], idxB = naive_fit[[2]]), true_pairs)
df_results[12,"Naive"] <- elapsed
df_results[13, "Naive"] <- MeM$Peak_RAM_Used_MiB
for (method in methods) {
arguments <- wrapper_args(method, prep_data, args)
t0 <- Sys.time()
MeM <- peakRAMNew(fit <- run_wrapper(method, prep_data, arguments))
elapsed <- as.numeric(Sys.time() - t0, units = "secs")
df_results[1:5, method] <- evaluate_linkage(fit, true_pairs)
if (is.null(fit) || length(fit$idxA) == 0) next
encodedA <- prep_data$encodedA
encodedB <- prep_data$encodedB
if (method == "diyar") {
encodedA <- encodedA[, c(PIVs, "local_id", "source")]
encodedB <- encodedB[, c(PIVs, "local_id", "source")]
}
diag <- do.call(RL_diagnostics, c(list(fit, encodedA, encodedB, PIVs, variables_type, PIVs,
true_pairs = prep_data$true_pairs, FDP_estimation = FDP_estimation,
RL_method = method, data = prep_data, maxIter4CV = maxIter4CV, n_repeats = n_repeats),
arguments))
if (FDP_estimation) {
df_results[6, method] <- diag$FDP_measures$curve$RL_FDP_score[1]
df_results[7, method] <- diag$FDP_measures$curve$augmRL_FDP_score[1]
df_results[8, method] <- diag$FDP_measures$curve$augmRL_FDP_synth[1]
}
df_results[9, method] <- max(diag$discrepancy_measures[1, grep("smd", names(diag$discrepancy_measures))])
df_results[10, method] <- min(diag$discrepancy_measures[1, grep("iou", names(diag$discrepancy_measures))])
df_results[11, method] <- max(diag$discrepancy_measures[1, grep("mmd", names(diag$discrepancy_measures))])
df_results[12, method] <- elapsed
df_results[13, method] <- MeM$Peak_RAM_Used_MiB
}
df_results
}
We will fill a table of results throughout the vignette, gathering for each record linkage method: linkage performance, data quality diagnostics: FDP estimation, measures of divergence of linked data from sources.
We use 4 Partially Identifying Variables to link the records: sex, birth month and birth year are stable, the state code may change over time but we cannot model these changes so we set this variable as flexible.
df1982 <- read.csv("ex-reg-NLTCS-82.csv", row.names = 1)
df1994 <- read.csv("ex-reg-NLTCS-94.csv", row.names = 1)
PIVs_config <- list(
SEX = list(dynamics = "stable", bound_mistakes = c(0.10, 0.10), fix_mistakes = c(NA, NA)),
DOB_MONTH = list(dynamics = "stable", bound_mistakes = c(0.10, 0.10), fix_mistakes = c(NA, NA)),
DOB_YEAR = list(dynamics = "stable", bound_mistakes = c(0.10, 0.10), fix_mistakes = c(NA, NA)),
STATE = list(dynamics = "flexible", bound_mistakes = c(NA, NA), fix_mistakes = c(NA, NA)))
PIVs_type <- list(SEX = FALSE, DOB_MONTH = TRUE, DOB_YEAR = TRUE, STATE = FALSE)
prepare_data() removes the records for which the linking variables are outside of the common support, checks that PIVs are not strongly associated with each other (which may degrade record linkage performance), maps PIV values to natural numbers and encodes missing values as 0. The StEM algorithm denotes the smallest source as A and the largest as B. The true pairs are known from the identifier ID (they are indexed on the rows of encodedA and encodedB, after the support restriction).
prep_data <- prepare_data(df1982, df1994, "1982", "1994", PIVs_config, same_mistakes = TRUE, uniq_id = "ID")
#> '1982' is the larger source, saved as B; '1994' saved as A.
c(n_A = nrow(prep_data$encodedA), n_B = nrow(prep_data$encodedB), n_links = nrow(prep_data$true_pairs))
#> n_A n_B n_links
#> 491 901 332
prep_data$n_values
#> SEX DOB_MONTH DOB_YEAR STATE
#> 2 12 28 9
df_results_NLTCS <- run_all_wrappers(prep_data, PIVs_type, methods)
cat("Record Linkage\n")
#> Record Linkage
knitr::kable(df_results_NLTCS[1:5, ], digits = 2, align=rep('c', ncol(df_results_NLTCS)))
| Naive | BRL | multilink | fastLink | reclin2 | diyar | fedmatch | FlexRL | |
|---|---|---|---|---|---|---|---|---|
| TP | 320.00 | 211.00 | 221.00 | 265.00 | 320.00 | 126.00 | 266.00 | 224.00 |
| FP | 163.00 | 15.00 | 26.00 | 78.00 | 163.00 | 5.00 | 223.00 | 28.00 |
| FN | 12.00 | 121.00 | 111.00 | 67.00 | 12.00 | 206.00 | 66.00 | 108.00 |
| sensitivity | 0.96 | 0.64 | 0.67 | 0.80 | 0.96 | 0.38 | 0.80 | 0.67 |
| FDP | 0.34 | 0.07 | 0.11 | 0.23 | 0.34 | 0.04 | 0.46 | 0.11 |
cat("FDP estimation\n")
#> FDP estimation
knitr::kable(df_results_NLTCS[6:8, ], digits = 2, align=rep('c', ncol(df_results_NLTCS)))
| Naive | BRL | multilink | fastLink | reclin2 | diyar | fedmatch | FlexRL | |
|---|---|---|---|---|---|---|---|---|
| est. FDP score | NA | 0.15 | NaN | 0.30 | 0.49 | NaN | 0.07 | 0.16 |
| est. FDP* score | NA | 0.17 | NaN | 0.31 | NA | NaN | 0.07 | 0.18 |
| est. FDP* synth | NA | 0.08 | 0.22 | 0.44 | NA | 0.03 | 0.13 | 0.16 |
cat("Linked divergence\n")
#> Linked divergence
knitr::kable(df_results_NLTCS[9:11, ], digits = 2, align=rep('c', ncol(df_results_NLTCS)))
| Naive | BRL | multilink | fastLink | reclin2 | diyar | fedmatch | FlexRL | |
|---|---|---|---|---|---|---|---|---|
| max SMD | NA | 0.15 | 0.13 | 0.14 | 0.18 | 0.43 | 0.11 | 0.18 |
| min support IoU | NA | 0.88 | 0.88 | 0.88 | 0.88 | 0.88 | 0.88 | 0.88 |
| max MMD | NA | 0.01 | 0.01 | 0.02 | 0.02 | 0.03 | 0.01 | 0.02 |
cat("Computational time and memory\n")
#> Computational time and memory
knitr::kable(df_results_NLTCS[12:13, ], digits = 2, align=rep('c', ncol(df_results_NLTCS)))
| Naive | BRL | multilink | fastLink | reclin2 | diyar | fedmatch | FlexRL | |
|---|---|---|---|---|---|---|---|---|
| time (secs) | 0.23 | 5.8 | 165 | 4.2 | 0.36 | 3 | 2.6 | 7.3 |
| memory (Mb) | 2.50 | 85.0 | 385 | 273.1 | 155.50 | 117 | 105.5 | 109.3 |
We use 5 Partially Identifying Variables to link the records: birth year, sex, birth region are stable, marital status and occupation are flexible. The occupation is built by combining the working status and the branch of activity together as one PIV. We create such variable because the high association level between working status and branch of activity may damage the record linkage performance.
df2012 <- comp[comp$ANNO == 2012, var_of_interest]
df2014 <- comp[comp$ANNO == 2014, var_of_interest]
df2012 <- df2012[!df2012$ID %in% df2012$ID[duplicated(df2012$ID)], ]
df2014 <- df2014[!df2014$ID %in% df2014$ID[duplicated(df2014$ID)], ]
rownames(df2012) <- 1:nrow(df2012)
rownames(df2014) <- 1:nrow(df2014)
PIVs_config <- list(
ANASC = list(dynamics = "stable", bound_mistakes = c(0.10, 0.10), fix_mistakes = c(NA, NA)),
SESSO = list(dynamics = "stable", bound_mistakes = c(0.10, 0.10), fix_mistakes = c(NA, NA)),
STACIV = list(dynamics = "flexible", bound_mistakes = c(NA, NA), fix_mistakes = c(NA, NA)),
NASCREG = list(dynamics = "stable", bound_mistakes = c(0.10, 0.10), fix_mistakes = c(NA, NA)),
SETTP11QUALP10 = list(dynamics = "flexible", bound_mistakes = c(NA, NA), fix_mistakes = c(NA, NA)))
PIVs_type <- list(ANASCI = TRUE, SESSO = FALSE, STACIV = FALSE, NASCREG = FALSE, SETTP11QUALP10 = FALSE)
PIVs_config_full <- c(PIVs_config,
list(IREG = list(dynamics = "flexible", bound_mistakes = c(NA, NA), fix_mistakes = c(NA, NA))))
PIVs_type_full <- c(PIVs_type, IREG = FALSE)
prep_data_full <- prepare_data(df2012, df2014, "2012", "2014", PIVs_config_full, same_mistakes = TRUE, uniq_id = "ID")
#> '2012' is the larger source, saved as B; '2014' saved as A.
true_pairs_full <- do.call(paste, c(prep_data_full$true_pairs, list(sep = "_")))
df_results_full <- data.frame(matrix(NA, nrow = 7, ncol = 0))
rownames(df_results_full) <- c("TP", "FP", "FN", "sensitivity", "FDP", "time (mins)", "memory (Mb)")
for (method in methods) {
t0 <- Sys.time()
args <- list(n_iter = 100)
MeM <- peakRAMNew(fit <- run_wrapper(method, prep_data_full, wrapper_args(method, prep_data_full, args)))
df_results_full[1:5, method] <- evaluate_linkage(fit, true_pairs_full)
df_results_full[6, method] <- if (is.null(fit)) NA else round(as.numeric(Sys.time() - t0, units = "mins"))
df_results_full[7, method] <- if (is.null(fit)) NA else MeM$Peak_RAM_Used_MiB
}
cat("Record Linkage performance on large files")
#> Record Linkage performance on large files
knitr::kable(df_results_full, digits = 2, align=rep('c', ncol(df_results_full)))
| BRL | multilink | fastLink | reclin2 | diyar | fedmatch | FlexRL | |
|---|---|---|---|---|---|---|---|
| TP | NA | NA | 5001.00 | NA | NA | 5057.00 | 4118.00 |
| FP | NA | NA | 11235.00 | NA | NA | 14154.00 | 5552.00 |
| FN | NA | NA | 5567.00 | NA | NA | 5511.00 | 6450.00 |
| sensitivity | NA | NA | 0.47 | NA | NA | 0.48 | 0.39 |
| FDP | NA | NA | 0.69 | NA | NA | 0.74 | 0.57 |
| time (mins) | NA | NA | 8.00 | NA | NA | 5.00 | 6.00 |
| memory (Mb) | NA | NA | 25918.30 | NA | NA | 1845.40 | 4033.60 |
Note that when methods do not ensure the one-to-one assignment constraint the number of False Positives may be high due to many-to-many falsely linked pairs of records.
Blocking on the region code IREG splits the task into 20 smaller ones. Within a region, IREG is constant and the linkage uses the 4 other PIVs. Region 2 is the regional subset used above.
regions <- sort(unique(c(df2012$IREG, df2014$IREG)))
results_IREG <- list()
for (r in regions) {
block2012 <- df2012[df2012$IREG == r, ]
block2014 <- df2014[df2014$IREG == r, ]
prep_data_r <- prepare_data(block2012, block2014, "2012", "2014", PIVs_config, same_mistakes = TRUE, uniq_id = "ID")
true_pairs_r <- do.call(paste, c(prep_data_r$true_pairs, list(sep = "_")))
for (method in methods) {
t0 <- Sys.time()
MeM <- peakRAMNew(fit <- run_wrapper(method, prep_data_r, wrapper_args(method, prep_data_r)))
results_IREG[[length(results_IREG) + 1]] <- data.frame(
IREG = r, n_A = nrow(prep_data_r$encodedA),
n_B = nrow(prep_data_r$encodedB), n_links = length(true_pairs_r),
method = method, round(t(evaluate_linkage(fit, true_pairs_r)),2),
seconds = round(as.numeric(Sys.time() - t0, units = "secs")),
memory = MeM$Peak_RAM_Used_MiB)
}
}
results_IREG <- do.call(rbind, results_IREG)
cat("Record Linkage on regional subsets")
#> Record Linkage on regional subsets
knitr::kable(results_IREG, digits = 2, align=rep('c', ncol(results_IREG)))
| IREG | n_A | n_B | n_links | method | TP | FP | FN | sensitivity | FDP | seconds | memory |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 1360 | 1495 | 645 | BRL | 61 | 25 | 584 | 0.09 | 0.29 | 19 | 1886.3 |
| 1 | 1360 | 1495 | 645 | multilink | 153 | 399 | 492 | 0.24 | 0.72 | 1112 | 1651.5 |
| 1 | 1360 | 1495 | 645 | fastLink | 321 | 812 | 324 | 0.50 | 0.72 | 4 | 182.4 |
| 1 | 1360 | 1495 | 645 | reclin2 | 632 | 25520 | 13 | 0.98 | 0.98 | 1 | 667.5 |
| 1 | 1360 | 1495 | 645 | diyar | 47 | 5 | 598 | 0.07 | 0.10 | 12 | 874.4 |
| 1 | 1360 | 1495 | 645 | fedmatch | 402 | 958 | 243 | 0.62 | 0.70 | 10 | 874.8 |
| 1 | 1360 | 1495 | 645 | FlexRL | 205 | 113 | 440 | 0.32 | 0.36 | 22 | 874.8 |
| 2 | 44 | 53 | 24 | BRL | 22 | 10 | 2 | 0.92 | 0.31 | 1 | 5.5 |
| 2 | 44 | 53 | 24 | multilink | 23 | 10 | 1 | 0.96 | 0.30 | 1 | 124.2 |
| 2 | 44 | 53 | 24 | fastLink | 21 | 14 | 3 | 0.88 | 0.40 | 3 | 15.9 |
| 2 | 44 | 53 | 24 | reclin2 | 23 | 50 | 1 | 0.96 | 0.68 | 1 | 2.9 |
| 2 | 44 | 53 | 24 | diyar | 10 | 0 | 14 | 0.42 | 0.00 | 1 | 17.5 |
| 2 | 44 | 53 | 24 | fedmatch | 22 | 22 | 2 | 0.92 | 0.50 | 1 | 34.2 |
| 2 | 44 | 53 | 24 | FlexRL | 22 | 1 | 2 | 0.92 | 0.04 | 3 | 109.9 |
| 3 | 2103 | 2140 | 918 | BRL | 40 | 11 | 878 | 0.04 | 0.22 | 37 | 1120.9 |
| 3 | 2103 | 2140 | 918 | multilink | 121 | 585 | 797 | 0.13 | 0.83 | 3063 | 3385.0 |
| 3 | 2103 | 2140 | 918 | fastLink | 450 | 1383 | 468 | 0.49 | 0.75 | 6 | 483.2 |
| 3 | 2103 | 2140 | 918 | reclin2 | 911 | 54782 | 7 | 0.99 | 0.98 | 2 | 859.8 |
| 3 | 2103 | 2140 | 918 | diyar | 54 | 1 | 864 | 0.06 | 0.02 | 23 | 1405.2 |
| 3 | 2103 | 2140 | 918 | fedmatch | 545 | 1558 | 373 | 0.59 | 0.74 | 17 | 768.7 |
| 3 | 2103 | 2140 | 918 | FlexRL | 272 | 148 | 646 | 0.30 | 0.35 | 33 | 768.6 |
| 4 | 437 | 541 | 229 | BRL | 81 | 29 | 148 | 0.35 | 0.26 | 3 | 276.7 |
| 4 | 437 | 541 | 229 | multilink | 96 | 83 | 133 | 0.42 | 0.46 | 75 | 768.3 |
| 4 | 437 | 541 | 229 | fastLink | 141 | 233 | 88 | 0.62 | 0.62 | 6 | 768.4 |
| 4 | 437 | 541 | 229 | reclin2 | 228 | 16275 | 1 | 1.00 | 0.99 | 1 | 153.9 |
| 4 | 437 | 541 | 229 | diyar | 10 | 0 | 219 | 0.04 | 0.00 | 3 | 768.8 |
| 4 | 437 | 541 | 229 | fedmatch | 157 | 280 | 72 | 0.69 | 0.64 | 3 | 545.5 |
| 4 | 437 | 541 | 229 | FlexRL | 74 | 31 | 155 | 0.32 | 0.30 | 9 | 769.1 |
| 5 | 1181 | 1319 | 750 | BRL | 156 | 31 | 594 | 0.21 | 0.17 | 13 | 764.7 |
| 5 | 1181 | 1319 | 750 | multilink | 266 | 410 | 484 | 0.35 | 0.61 | 609 | 1268.9 |
| 5 | 1181 | 1319 | 750 | fastLink | 365 | 710 | 385 | 0.49 | 0.66 | 26 | 1805.3 |
| 5 | 1181 | 1319 | 750 | reclin2 | 598 | 341135 | 152 | 0.80 | 1.00 | 1 | 744.8 |
| 5 | 1181 | 1319 | 750 | diyar | 4 | 0 | 746 | 0.01 | 0.00 | 10 | 741.9 |
| 5 | 1181 | 1319 | 750 | fedmatch | 349 | 832 | 401 | 0.47 | 0.70 | 9 | 213.3 |
| 5 | 1181 | 1319 | 750 | FlexRL | 195 | 100 | 555 | 0.26 | 0.34 | 19 | 709.5 |
| 6 | 459 | 513 | 238 | BRL | 98 | 35 | 140 | 0.41 | 0.26 | 3 | 277.0 |
| 6 | 459 | 513 | 238 | multilink | 122 | 84 | 116 | 0.51 | 0.41 | 71 | 746.1 |
| 6 | 459 | 513 | 238 | fastLink | 153 | 202 | 85 | 0.64 | 0.57 | 5 | 505.8 |
| 6 | 459 | 513 | 238 | reclin2 | 236 | 2752 | 2 | 0.99 | 0.92 | 1 | 152.1 |
| 6 | 459 | 513 | 238 | diyar | 6 | 0 | 232 | 0.03 | 0.00 | 3 | 650.0 |
| 6 | 459 | 513 | 238 | fedmatch | 169 | 289 | 69 | 0.71 | 0.63 | 4 | 117.9 |
| 6 | 459 | 513 | 238 | FlexRL | 98 | 48 | 140 | 0.41 | 0.33 | 8 | 314.8 |
| 7 | 670 | 710 | 321 | BRL | 79 | 31 | 242 | 0.25 | 0.28 | 5 | 544.7 |
| 7 | 670 | 710 | 321 | multilink | 132 | 151 | 189 | 0.41 | 0.53 | 178 | 741.9 |
| 7 | 670 | 710 | 321 | fastLink | 210 | 337 | 111 | 0.65 | 0.62 | 6 | 83.3 |
| 7 | 670 | 710 | 321 | reclin2 | 316 | 6320 | 5 | 0.98 | 0.95 | 1 | 306.3 |
| 7 | 670 | 710 | 321 | diyar | 34 | 2 | 287 | 0.11 | 0.06 | 4 | 747.1 |
| 7 | 670 | 710 | 321 | fedmatch | 227 | 443 | 94 | 0.71 | 0.66 | 5 | 141.8 |
| 7 | 670 | 710 | 321 | FlexRL | 127 | 54 | 194 | 0.40 | 0.30 | 11 | 409.2 |
| 8 | 1479 | 1524 | 898 | BRL | 140 | 23 | 758 | 0.16 | 0.14 | 18 | 742.7 |
| 8 | 1479 | 1524 | 898 | multilink | 290 | 421 | 608 | 0.32 | 0.59 | 1130 | 2010.0 |
| 8 | 1479 | 1524 | 898 | fastLink | 452 | 865 | 446 | 0.50 | 0.66 | 37 | 2174.9 |
| 8 | 1479 | 1524 | 898 | reclin2 | 721 | 339232 | 177 | 0.80 | 1.00 | 1 | 930.9 |
| 8 | 1479 | 1524 | 898 | diyar | 20 | 0 | 878 | 0.02 | 0.00 | 14 | 879.2 |
| 8 | 1479 | 1524 | 898 | fedmatch | 510 | 969 | 388 | 0.57 | 0.66 | 12 | 217.9 |
| 8 | 1479 | 1524 | 898 | FlexRL | 299 | 104 | 599 | 0.33 | 0.26 | 24 | 721.5 |
| 9 | 1363 | 1500 | 711 | BRL | 44 | 10 | 667 | 0.06 | 0.19 | 18 | 897.3 |
| 9 | 1363 | 1500 | 711 | multilink | 107 | 371 | 604 | 0.15 | 0.78 | 1060 | 1891.1 |
| 9 | 1363 | 1500 | 711 | fastLink | 343 | 857 | 368 | 0.48 | 0.71 | 38 | 2576.3 |
| 9 | 1363 | 1500 | 711 | reclin2 | 8 | 188790 | 703 | 0.01 | 1.00 | 1 | 829.4 |
| 9 | 1363 | 1500 | 711 | diyar | 27 | 0 | 684 | 0.04 | 0.00 | 12 | 837.2 |
| 9 | 1363 | 1500 | 711 | fedmatch | 338 | 1025 | 373 | 0.48 | 0.75 | 11 | 212.7 |
| 9 | 1363 | 1500 | 711 | FlexRL | 199 | 88 | 512 | 0.28 | 0.31 | 22 | 706.0 |
| 10 | 658 | 687 | 384 | BRL | 70 | 12 | 314 | 0.18 | 0.15 | 4 | 519.6 |
| 10 | 658 | 687 | 384 | multilink | 119 | 139 | 265 | 0.31 | 0.54 | 159 | 838.6 |
| 10 | 658 | 687 | 384 | fastLink | 206 | 359 | 178 | 0.54 | 0.64 | 8 | 840.0 |
| 10 | 658 | 687 | 384 | reclin2 | 321 | 102152 | 63 | 0.84 | 1.00 | 1 | 300.0 |
| 10 | 658 | 687 | 384 | diyar | 2 | 0 | 382 | 0.01 | 0.00 | 5 | 827.2 |
| 10 | 658 | 687 | 384 | fedmatch | 238 | 420 | 146 | 0.62 | 0.64 | 5 | 126.7 |
| 10 | 658 | 687 | 384 | FlexRL | 113 | 58 | 271 | 0.29 | 0.34 | 11 | 374.1 |
| 11 | 762 | 872 | 486 | BRL | 119 | 30 | 367 | 0.24 | 0.20 | 6 | 767.1 |
| 11 | 762 | 872 | 486 | multilink | 182 | 213 | 304 | 0.37 | 0.54 | 220 | 839.7 |
| 11 | 762 | 872 | 486 | fastLink | 249 | 422 | 237 | 0.51 | 0.63 | 10 | 843.6 |
| 11 | 762 | 872 | 486 | reclin2 | 394 | 135933 | 92 | 0.81 | 1.00 | 1 | 438.6 |
| 11 | 762 | 872 | 486 | diyar | 4 | 0 | 482 | 0.01 | 0.00 | 6 | 841.5 |
| 11 | 762 | 872 | 486 | fedmatch | 251 | 511 | 235 | 0.52 | 0.67 | 6 | 145.9 |
| 11 | 762 | 872 | 486 | FlexRL | 142 | 54 | 344 | 0.29 | 0.28 | 13 | 429.0 |
| 12 | 977 | 1142 | 455 | BRL | 38 | 14 | 417 | 0.08 | 0.27 | 11 | 804.0 |
| 12 | 977 | 1142 | 455 | multilink | 125 | 235 | 330 | 0.27 | 0.65 | 516 | 1085.7 |
| 12 | 977 | 1142 | 455 | fastLink | 240 | 567 | 215 | 0.53 | 0.70 | 18 | 1323.5 |
| 12 | 977 | 1142 | 455 | reclin2 | 375 | 3911 | 80 | 0.82 | 0.91 | 1 | 713.7 |
| 12 | 977 | 1142 | 455 | diyar | 6 | 0 | 449 | 0.01 | 0.00 | 8 | 716.1 |
| 12 | 977 | 1142 | 455 | fedmatch | 254 | 722 | 201 | 0.56 | 0.74 | 8 | 172.2 |
| 12 | 977 | 1142 | 455 | FlexRL | 152 | 56 | 303 | 0.33 | 0.27 | 17 | 529.0 |
| 13 | 454 | 493 | 311 | BRL | 139 | 24 | 172 | 0.45 | 0.15 | 3 | 264.6 |
| 13 | 454 | 493 | 311 | multilink | 164 | 95 | 147 | 0.53 | 0.37 | 57 | 715.0 |
| 13 | 454 | 493 | 311 | fastLink | 197 | 203 | 114 | 0.63 | 0.51 | 6 | 715.3 |
| 13 | 454 | 493 | 311 | reclin2 | 310 | 17730 | 1 | 1.00 | 0.98 | 1 | 146.0 |
| 13 | 454 | 493 | 311 | diyar | 39 | 0 | 272 | 0.13 | 0.00 | 3 | 557.1 |
| 13 | 454 | 493 | 311 | fedmatch | 198 | 255 | 113 | 0.64 | 0.56 | 3 | 105.6 |
| 13 | 454 | 493 | 311 | FlexRL | 137 | 33 | 174 | 0.44 | 0.19 | 8 | 279.1 |
| 14 | 280 | 299 | 183 | BRL | 101 | 22 | 82 | 0.55 | 0.18 | 1 | 107.1 |
| 14 | 280 | 299 | 183 | multilink | 113 | 44 | 70 | 0.62 | 0.28 | 19 | 716.2 |
| 14 | 280 | 299 | 183 | fastLink | 131 | 106 | 52 | 0.72 | 0.45 | 4 | 367.1 |
| 14 | 280 | 299 | 183 | reclin2 | 182 | 7282 | 1 | 0.99 | 0.98 | 1 | 55.6 |
| 14 | 280 | 299 | 183 | diyar | 2 | 0 | 181 | 0.01 | 0.00 | 2 | 325.4 |
| 14 | 280 | 299 | 183 | fedmatch | 137 | 143 | 46 | 0.75 | 0.51 | 2 | 83.4 |
| 14 | 280 | 299 | 183 | FlexRL | 105 | 20 | 78 | 0.57 | 0.16 | 6 | 186.1 |
| 15 | 1939 | 2160 | 1069 | BRL | 25 | 22 | 1044 | 0.02 | 0.47 | 35 | 947.1 |
| 15 | 1939 | 2160 | 1069 | multilink | 69 | 648 | 1000 | 0.06 | 0.90 | 2229 | 3837.2 |
| 15 | 1939 | 2160 | 1069 | fastLink | 276 | 1526 | 793 | 0.26 | 0.85 | 74 | 4640.2 |
| 15 | 1939 | 2160 | 1069 | reclin2 | 1069 | 1572964 | 0 | 1.00 | 1.00 | 2 | 2191.0 |
| 15 | 1939 | 2160 | 1069 | diyar | 1 | 0 | 1068 | 0.00 | 0.00 | 21 | 1505.1 |
| 15 | 1939 | 2160 | 1069 | fedmatch | 280 | 1659 | 789 | 0.26 | 0.86 | 16 | 291.4 |
| 15 | 1939 | 2160 | 1069 | FlexRL | 108 | 117 | 961 | 0.10 | 0.52 | 30 | 847.4 |
| 16 | 1130 | 1354 | 683 | BRL | 47 | 11 | 636 | 0.07 | 0.19 | 13 | 842.6 |
| 16 | 1130 | 1354 | 683 | multilink | 112 | 470 | 571 | 0.16 | 0.81 | 528 | 1382.1 |
| 16 | 1130 | 1354 | 683 | fastLink | 266 | 774 | 417 | 0.39 | 0.74 | 26 | 1916.9 |
| 16 | 1130 | 1354 | 683 | reclin2 | 681 | 404295 | 2 | 1.00 | 1.00 | 1 | 825.0 |
| 16 | 1130 | 1354 | 683 | diyar | 9 | 1 | 674 | 0.01 | 0.10 | 10 | 822.6 |
| 16 | 1130 | 1354 | 683 | fedmatch | 290 | 840 | 393 | 0.42 | 0.74 | 10 | 209.6 |
| 16 | 1130 | 1354 | 683 | FlexRL | 154 | 64 | 529 | 0.23 | 0.29 | 19 | 689.2 |
| 17 | 264 | 276 | 193 | BRL | 116 | 45 | 77 | 0.60 | 0.28 | 1 | 95.2 |
| 17 | 264 | 276 | 193 | multilink | 122 | 78 | 71 | 0.63 | 0.39 | 13 | 829.0 |
| 17 | 264 | 276 | 193 | fastLink | 130 | 100 | 63 | 0.67 | 0.43 | 4 | 280.5 |
| 17 | 264 | 276 | 193 | reclin2 | 193 | 7266 | 0 | 1.00 | 0.97 | 1 | 49.5 |
| 17 | 264 | 276 | 193 | diyar | 16 | 1 | 177 | 0.08 | 0.06 | 2 | 312.2 |
| 17 | 264 | 276 | 193 | fedmatch | 127 | 137 | 66 | 0.66 | 0.52 | 2 | 88.6 |
| 17 | 264 | 276 | 193 | FlexRL | 134 | 39 | 59 | 0.69 | 0.23 | 5 | 193.3 |
| 18 | 501 | 554 | 362 | BRL | 99 | 22 | 263 | 0.27 | 0.18 | 3 | 334.9 |
| 18 | 501 | 554 | 362 | multilink | 151 | 131 | 211 | 0.42 | 0.46 | 61 | 832.6 |
| 18 | 501 | 554 | 362 | fastLink | 205 | 246 | 157 | 0.57 | 0.55 | 6 | 832.3 |
| 18 | 501 | 554 | 362 | reclin2 | 311 | 118067 | 51 | 0.86 | 1.00 | 1 | 190.1 |
| 18 | 501 | 554 | 362 | diyar | 7 | 0 | 355 | 0.02 | 0.00 | 3 | 682.0 |
| 18 | 501 | 554 | 362 | fedmatch | 194 | 307 | 168 | 0.54 | 0.61 | 4 | 120.6 |
| 18 | 501 | 554 | 362 | FlexRL | 121 | 49 | 241 | 0.33 | 0.29 | 9 | 341.8 |
| 19 | 1524 | 1625 | 1010 | BRL | 52 | 9 | 958 | 0.05 | 0.15 | 19 | 829.8 |
| 19 | 1524 | 1625 | 1010 | multilink | 138 | 574 | 872 | 0.14 | 0.81 | 999 | 2245.8 |
| 19 | 1524 | 1625 | 1010 | fastLink | 354 | 1057 | 656 | 0.35 | 0.75 | 41 | 2855.0 |
| 19 | 1524 | 1625 | 1010 | reclin2 | 1006 | 677113 | 4 | 1.00 | 1.00 | 2 | 1277.3 |
| 19 | 1524 | 1625 | 1010 | diyar | 5 | 0 | 1005 | 0.00 | 0.00 | 15 | 892.4 |
| 19 | 1524 | 1625 | 1010 | fedmatch | 351 | 1173 | 659 | 0.35 | 0.77 | 12 | 259.5 |
| 19 | 1524 | 1625 | 1010 | FlexRL | 166 | 58 | 844 | 0.16 | 0.26 | 24 | 649.3 |
| 20 | 826 | 827 | 478 | BRL | 69 | 24 | 409 | 0.14 | 0.26 | 6 | 649.3 |
| 20 | 826 | 827 | 478 | multilink | 142 | 273 | 336 | 0.30 | 0.66 | 239 | 846.6 |
| 20 | 826 | 827 | 478 | fastLink | 232 | 491 | 246 | 0.49 | 0.68 | 12 | 852.4 |
| 20 | 826 | 827 | 478 | reclin2 | 478 | 71205 | 0 | 1.00 | 0.99 | 1 | 443.9 |
| 20 | 826 | 827 | 478 | diyar | 13 | 2 | 465 | 0.03 | 0.13 | 6 | 851.7 |
| 20 | 826 | 827 | 478 | fedmatch | 245 | 580 | 233 | 0.51 | 0.70 | 6 | 163.4 |
| 20 | 826 | 827 | 478 | FlexRL | 140 | 63 | 338 | 0.29 | 0.31 | 13 | 508.2 |
Averaged over the regions and total computing time:
summary_IREG <- do.call(rbind, lapply(split(results_IREG, results_IREG$method), function(d) {
data.frame(TP = mean(d$TP, na.rm=TRUE), FP = mean(d$FP, na.rm=TRUE),
FN = mean(d$FN, na.rm=TRUE), sensitivity = mean(d$sensitivity, na.rm=TRUE),
FDP = mean(d$FDP, na.rm=TRUE), method = d$method[1],
minutes = sum(d$seconds) / 60,
memory = mean(d$memory))
}))
rownames(summary_IREG) <- summary_IREG$method
newdf <- as.data.frame(apply(t(summary_IREG)[c("TP","FP","FN","sensitivity","FDP","minutes","memory"),methods], 2, function(x) sprintf("%.2f",as.numeric(as.character(x)))))
names(newdf) <- names(df_results_full)
rownames(newdf) <- rownames(df_results_full)
knitr::kable(newdf, align=rep('c', ncol(newdf)))
| BRL | multilink | fastLink | reclin2 | diyar | fedmatch | FlexRL | |
|---|---|---|---|---|---|---|---|
| TP | 80.00 | 137.00 | 247.00 | 450.00 | 16.00 | 264.00 | 148.00 |
| FP | 22.00 | 270.70 | 563.20 | 204638.70 | 0.60 | 656.10 | 64.90 |
| FN | 438.00 | 380.00 | 270.00 | 68.00 | 502.00 | 253.00 | 369.00 |
| sensitivity | 0.26 | 0.36 | 0.55 | 0.89 | 0.06 | 0.58 | 0.36 |
| FDP | 0.23 | 0.58 | 0.63 | 0.97 | 0.02 | 0.67 | 0.29 |
| time (mins) | 3.65 | 205.65 | 5.67 | 0.38 | 2.72 | 2.43 | 5.10 |
| memory (Mb) | 634.00 | 1338.00 | 1203.00 | 564.00 | 763.00 | 245.00 | 520.00 |
colours <- grDevices::hcl.colors(length(methods), "Dark 3")
names(colours) <- methods
plot(NA, xlim = c(0, 1), ylim = c(0, 1), xlab = "sensitivity", ylab = "FDP",
main = "SHIW linked region by region (one point per region)")
for (method in methods) {
d <- results_IREG[results_IREG$method == method, ]
points(d$sensitivity, d$FDP, col = colours[method], pch = 19)
}
legend("bottomright", legend = methods, col = colours, pch = 19, bty = "n")
The 5 PIVs have little discriminating power on these data and the two waves overlap on about 50% of their records (10.568 true links, 19.362 records in A, 20.016 records in B), so the task is hard for every package. BRL, multilink, reclin2 and diyar are not scalable (out of memory errors).
We compare methods with blocking over the 20 regions: reclin2 (EM on the comparison patterns, threshold at 0.5 on the posterior) links a lot of records reaching a high sensitivity at the price of a high FDP; fastLink, fedmatch and multilink link about half of the true pairs with a FDP around 0.7; BRL and diyar are the most conservative, with a FDP around 0.3 and a sensitivity around 0.2; FlexRL performance is between these conservative and permissive methods.
In terms of computational time however, FlexRL and multilink are more than 10 times slower than the other methods. The results were computed with the code above, on a machine with Apple M1 Max ARM chip, 8-core CPU, 32 GB of memory.