R package for generating simulated antigenic cartography data. Used to create reproducible test datasets for benchmarking Racmacs methods and evaluating the impact of noise, missing data, and surveillance structure on map quality.
Install from GitHub: devtools::install_github("drserajames/acsimdata")
R/
map_maker.R # Coordinate and distance generation
dist_to_titre.R # Distance → HI titre conversion
add_noise.R # Noise injection
missing_titres.R # Random missing data
missing_titres_ext.R # Structured missing data (threshold, informed, block, by_distance)
sim_surv_tables.R # Multi-period surveillance table simulation
tests/testthat/ # One test file per R file
man/ # Rd files — generated by devtools::document(), do not edit
NAMESPACE # Generated by devtools::document(), do not edit
# 1. Generate map coordinates and distances
m <- map_maker_random(n_antigens, n_sera, range, seed = 1)
# m$antigen_coord — n_ag × dimensions matrix, rownames "AG1"..."AGn"
# m$sera_coord — n_sr × dimensions matrix, rownames "SR1"..."SRn"
# m$dist — full (AG+SR) × (AG+SR) distance matrix
# m$slim_dist — n_ag × n_sr submatrix
# 2. Add noise to distances
noisy <- add_noise(m$dist)
# noisy$noise_dist_table — distances + titre_noise + antigen_noise + serum_noise
# 3. Convert distances to HI titre values
ti <- dist_to_hi_titre(noisy$noise_dist_table)
# ti$round_titre — numeric matrix (10, 20, 40, ...)
# ti$lessthan_titre — character matrix ("10", "<10", ...) — main downstream format
# 4. Introduce missing data (one or more mechanisms)
miss <- miss_titres_random(ti$lessthan_titre, proportion = 0.2)
# miss$rm_titre — character matrix with "*" for missing cells → pass to Racmacs| Function | Description |
|---|---|
map_maker_random(n_antigens, n_sera, range, ...) |
Random uniform coordinates in [0, range]^dimensions |
map_maker_coord(n_antigens, n_sera, true_ag_coord, true_sr_coord=true_ag_coord, ...) |
Cluster-based coordinates around specified centres. If true_ag_coord and true_sr_coord have different numbers of rows, the shorter is recycled to match the longer with a warning. |
| Function | Description |
|---|---|
dist_to_hi_titre(dists, base=2, divisor=10, max_log_titre=9, min_log_titre=0) |
Converts distance matrix to HI-like titres. Returns round_titre (numeric), lessthan_titre (character with "<X" floor), lessthanhack_titre (numeric floor substitute) |
noise_dist_to_hi_titre(noise_dists, ...) |
Wrapper that converts both true and noisy distances |
| Function | Description |
|---|---|
add_noise(dists, ...) |
Three-component noise: per-titre (rnorm), per-antigen (rnorm), per-serum (runif). Each component is a configurable distribution |
add_noise_targeted(dists, antigen, serum, ...) |
Noise on a single cell, entire antigen row, or entire serum column |
| Function | Description |
|---|---|
miss_titres_random(titre, proportion, keep_homologous=TRUE) |
Uniform random removal of proportion of titres |
All structured missing functions return at minimum: full_titre, rm_titre, rm_ind, rm_ind_arr, params. All accept keep_homologous=TRUE to protect homologous (diagonal) pairs.
| Function | Mechanism |
|---|---|
miss_titres_threshold(titre, threshold, keep_homologous=TRUE) |
Deterministic: titres strictly below threshold (raw HI units) become "*". Handles numeric (round_titre) and character (lessthan_titre) input. For "<X" entries: removed when X <= threshold. |
miss_titres_informed(titre, midpoint_titre=40, steepness=1, keep_homologous=TRUE, seed) |
Probabilistic MAR/MNAR: P(missing) = logistic(–steepness × log₂(titre / midpoint_titre)). P=0.5 at midpoint_titre, rising toward 1 as titre falls. Each cell sampled independently. |
miss_titres_block(titre, antigens, sera, keep_homologous=TRUE) |
Deterministic: removes all cells in a rectangular antigen×serum subblock. antigens/sera accept character names or integer indices. Both must be specified. |
miss_titres_by_distance(titre, ag_coord, sr_coord, midpoint_dist=3, steepness=1, keep_homologous=TRUE, seed) |
Probabilistic: P(missing) = logistic(steepness × (dist – midpoint_dist)). Computes pairwise Euclidean distances from coordinate matrices; reorders by rownames if present. Returns dist_matrix in output. |
miss_titres_banded(titre, bandwidth, n_sera=NULL, keep_homologous=TRUE) |
Deterministic banded structure: cells where |row − col| > bandwidth become "*", mimicking surveillance practice where only contemporaneous antigen–serum pairs are tested. Optionally reduces to n_sera columns by dropping the most sparsely-filled sera after banding (ties broken by lower column index). Returns additional fields titre_reduced (banded matrix with dropped columns removed) and dropped_sera (names of dropped columns). |
| Function | Description |
|---|---|
sim_surv_tables(n_tables, n_ag_per_table, n_sr_per_table, n_ref_ag=0, n_ref_sr=0, n_ref_ag_overlap=NULL, n_ref_sr_overlap=NULL, ag_drift, ...) |
Generates sequential HI tables mimicking surveillance data. Each table has two groups: reference antigens/sera (shared across multiple tables) and table-specific antigens/sera (unique to one table). Antigens drift along dimension 1 by ag_drift per period. Returns merged_titre_table, per-table coordinate and membership information. |
| Function | Description |
|---|---|
sim_from_map(map, coord_noise_sd=0, layers=FALSE, noise_params=list(), base=2, divisor=10, max_log_titre=9, min_log_titre=0, seed) |
Treats the optimised coordinates of a Racmacs acmap as the ground truth and simulates new HI titre data from them. Observed missingness from the real titer table is stamped onto the simulated titres. Optionally adds noise via add_noise() and/or preserves layer structure. Requires Racmacs. |
sim_from_map — key details:
- Extracts
agCoords,srCoords, andtiterTablefrom the map; computes slim AG×SR distances internally. coord_noise_sd: Gaussian noise (in antigenic units) added to each coordinate dimension before computing distances. Prevents the source coordinates sitting at the exact titre-space minimum — important for absolute benchmarking. Default 0 (no perturbation).noise_params: named list forwarded toadd_noise()(omitdists— injected automatically). Adds measurement noise after distances are computed. Empty list = no noise. The two noise sources are independent.layers = TRUE: additionally callstiterTableLayers(map)and applies each layer's"*"pattern independently; returnssim_titre_layers(named list of matrices). The mergedsim_titreis unaffected.max_log_titre: accepts a per-serum vector; passRacmacs::srColbases(map)to match the original titre scale.- Internal AG1/AG2…/SR1/SR2… naming is used for pipeline compatibility; original antigen/serum names are restored in all output matrices.
- Returns:
sim_titre(merged missingness),full_sim_titre(no missingness),sim_titre_layers(NULL or list),observed_titre,dist,noise,ag_coord/sr_coord(perturbed ifcoord_noise_sd > 0),params.
sim_surv_tables — reference vs table-specific structure:
n_ag_per_table/n_sr_per_table: table-specific points, unique to each table. Accept scalar or vector of lengthn_tables.n_ref_ag/n_ref_sr: reference points shared across multiple tables.n_ref_ag_overlap/n_ref_sr_overlap: how many references carry over between consecutive tables.- Default (
NULL→n_ref_ag): simple case — all references fixed, same set in every table. - Set lower: complex/WHO-like case — reference set rotates;
n_ref_ag − n_ref_ag_overlapreferences are replaced each period.
- Default (
- In the merged output, antigen indices 1..
n_ref_ag(first introduced) are reference; higher indices are table-specific.
CDC H3N2 hi_gp surveillance data characteristics (for parameter guidance):
- 345 tables; ~55 antigens/table (~38 table-specific, ~17 reference), ~13 sera/table (nearly all reference)
- ~40 antigens added/dropped per transition; sera change in ~42% of transitions, usually by 1–2 at a time
- Serum persistence: median 6 tables, long tail (some appear in 250+ tables)
- Matrix naming: antigens are rows (
"AG1","AG2", ...), sera are columns ("SR1","SR2", ...). The full distance matrix frommap_maker_*has both AG and SR as rows and columns; use$slim_distfor the AG×SR submatrix. - Missing value encoding:
"*"= not measured (truly absent),"<X"= measured but below detection (censored). Both are standard Racmacs inputs. - Titre scale: default
base=2,divisor=10→ titres 10, 20, 40, 80, ... (standard HI dilution series). Log₂(titre/10) gives the log-scale used internally. - Homologous titres: the diagonal where AG number = SR number. Protected by
keep_homologous=TRUEin all missing-data functions; identified by matching the numeric suffix in "AGn"/"SRn" names. - Seeds: pass
seedfor reproducibility in any stochastic function; omitted seeds are sampled randomly and stored in$params$seed.
devtools::document() # regenerate NAMESPACE and man/ from roxygen2 tags
devtools::test() # run all tests (372 as of June 2026)
devtools::check() # full R CMD checkNAMESPACE and man/ are fully generated — never edit them directly. All new functions need #' @export in their roxygen block and a corresponding test file in tests/testthat/.
Internal helper functions (not exported) are prefixed with . (e.g. .homologous_ind, .parse_titre_numeric in missing_titres_ext.R).