--- title: "A worked example: a 57 x 57 input-output-style table" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{A worked example: a 57 x 57 input-output-style table} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>") ``` ```{r setup} library(RASbound) ``` This vignette applies the package to the kind of problem it was written for. A square, non-negative, input-output-style table must be adjusted to new row and column totals, and every cell must stay inside a plausible range. The data come from a real application. They are **rounded to integers** and ship with the package in `inst/extdata`. ```{r data} read_mat <- function(f) { as.matrix(utils::read.csv(system.file("extdata", f, package = "RASbound"), header = FALSE)) } x0 <- unname(read_mat("example_x0.csv")) lower <- unname(read_mat("example_lower.csv")) upper <- unname(read_mat("example_upper.csv")) row_targets <- read_mat("example_row_targets.csv")[, 1] col_targets <- read_mat("example_col_targets.csv")[, 1] dim(x0) c(sum(row_targets), sum(col_targets)) sum(x0 == 0) # exact zeros in the seed sum(lower > 0) # cells with a positive lower bound tail(row_targets, 3) # the last two rows (and columns) have target 0 ``` This input is demanding for three reasons. Many cells have a positive lower bound as well as an upper bound. The seed contains exact zeros. And the last two rows and columns have a target of exactly 0. ## Hard-freeze variants ```{r fit} args <- list(x0 = x0, row_targets = row_targets, col_targets = col_targets, lower = lower, upper = upper, tol = 1e-6, max_iter = 1000, max_outer = 500) fit_sim <- suppressWarnings(do.call(bras_freeze_sim, args)) fit_seq <- suppressWarnings(do.call(bras_freeze_seq, args)) fit_hyb <- suppressWarnings(do.call(bras_freeze_hybrid, args)) sapply(list(sim = fit_sim, seq = fit_seq, hybrid = fit_hyb), function(f) c(converged = f$converged, feasible = f$feasible, n_fixed = nrow(f$fixed_cells))) ``` Every cell should respect its range, and the margins should be matched: ```{r check} ok <- function(f) c( within_bounds = all(f$matrix >= lower - 1e-6 & f$matrix <= upper + 1e-6), max_margin_error = max(f$diagnostics$row_margin_error, f$diagnostics$col_margin_error) ) sapply(list(sim = fit_sim, seq = fit_seq, hybrid = fit_hyb), ok) ``` The variants fix cells in a different order. When several bounds bind at once, they can return slightly different matrices. This is how far apart they are: ```{r diff} max(abs(fit_sim$matrix - fit_seq$matrix)) max(abs(fit_sim$matrix - fit_hyb$matrix)) ``` ## Where the bounds bind `plot()` shows the fitted matrix and marks the cells that were fixed at a bound. An upward triangle is a cell at its upper bound, and a downward triangle is a cell at its lower bound: ```{r plot, fig.width = 5, fig.height = 5} plot(fit_seq) ``` The exact cells and values are in `fit_seq$fixed_cells`. If your matrix has row and column names, `plot()` shows them as axis labels. See `?plot.bras_fit` for how to read the colours. ## The `survey` wrappers on the same problem The `bras_calibrate_*()` wrappers remove the rows and columns with a zero target before calling `survey`, and put them back as zeros afterwards. On the original, unrounded version of this table, all three wrappers still failed inside `survey::calibrate()`. The cause is not known. This is what they return on the rounded data: ```{r calibrate, eval = requireNamespace("survey", quietly = TRUE) && requireNamespace("MASS", quietly = TRUE)} try_wrapper <- function(f) { tryCatch({ fit <- suppressWarnings(f(x0, row_targets, col_targets, lower = lower, upper = upper)) paste("returned; converged =", fit$converged) }, error = function(e) paste("failed:", substr(conditionMessage(e), 1, 70))) } c(raking = try_wrapper(bras_calibrate_raking), linear = try_wrapper(bras_calibrate_linear)) ``` If a wrapper fails on a real table like this one, use a `bras_freeze_*()` function instead.