Skip to contents

Example data for spi_adjacency(), spi_expected(), spi_index(), and spi_compare_npafp(). Surveillance counts are simulated. Population counts and the original boundary geometry come from the sources listed below.

Usage

synth_surveillance

Format

A named list with twelve elements:

cases

Tibble, 28,320 x 3. Columns: adm2_guid (character district id, a POLIS-style GUID such as "{54CD979C-CF9D-6A65-567F-8976C9546137}"), month (Date, first-of-month), count (integer). Includes every combination of 236 districts and 120 months, including months with zero cases.

population

Tibble, 2,360 x 3. Columns: adm2_guid, year (integer, 2015-2024), pop_u15 (numeric, under-15 population). Real annual under-15 counts extracted from the WorldPop 0-14 rasters over the source geometry (range ~19k-900k).

covariates

Tibble, 2,360 x 5. District-year covariate layers for the adjusted spi_expected() model. Columns: adm2_guid, year (integer, 2015-2024), dtp3 (numeric, DTP3 immunisation coverage %, a proxy for health service access), urban_prop (numeric, 0-1 urban share, fixed for each district), travel_time_min (numeric, median minutes to the nearest health facility, an access-to-care proxy; right-skewed, a log_transform candidate). Correlated with the simulated reporting shortfalls (lower coverage, worse access) and population (denser is more urban).

boundaries

sf object, 236 x 4 (+ geometry). Columns: adm2_guid, adm2_name (fictional place names), adm1_name (one of 36 fictional provinces), adm0_name ("Harad"), geometry (POLYGON / MULTIPOLYGON on a local grid; CRS deliberately undefined). Derived from real Lake Chad adm2 boundaries (geoBoundaries) across four countries, merged into one gap-free landmass, partitioned into 36 contiguous provinces, relabelled, and rotated onto a local grid with the CRS removed for use as fictional geography.

ward_boundaries

sf object, ~1,548 x 6 (+ geometry). Columns: adm3_guid (the parent adm2_guid GUID suffixed -W01, -W02, ...), adm3_name (parent adm2 name plus a suffix from a pool of directional or geographic descriptors – e.g. "Khandoth North Ward", "Nenad Ford"), adm2_guid, adm2_name, adm1_name, adm0_name, geometry. Each adm2 is subdivided into 5-8 wards by nested Voronoi tessellation.

virus_outcome

Tibble, 2,360 x 5. Columns: adm2_guid, year, any_wpv1 (integer, 0/1; always 0 in these data), any_cvdpv2 (integer, 0/1), any_virus (integer, 0/1). cVDPV2 detections occur the year after a district's detection completeness falls well below the national level in the same year (relative completeness < 0.6), at rate plogis(-0.6); background rate plogis(-4). Included for comparison with SPI results.

es_sites

sf object, 160 x 5 (+ geometry). Environmental surveillance sentinel sites (~67% of districts host one). Columns: es_site_id, site_name, adm3_guid, adm2_guid, adm1_name, geometry (POINT). One site per selected district, placed at the centroid of a random ward.

es_data

Tibble, 19,200 x 4. Monthly ES sample-level rows. Columns: es_site_id, adm2_guid, sample_date (Date, first-of-month), positive_cvdpv2 (integer, 0/1). The probability of a positive sample depends on the same simulated detection shortfall used for virus_outcome.

es_district_year

Tibble, 1,600 x 4. Annual ES totals by district. Columns: adm2_guid, year, n_samples, n_positive.

afp_timeliness

Tibble, 2,360 x 5. District-year AFP timeliness counts. Columns: adm2_guid, year, afp_cases, n_assessable (cases with a usable onset date), n_within_7d (of those, notified inside the window). Onset-to-notification is the one conventional AFP indicator POLIS does not publish. Calculate it from case data. These counts supply onset_notify_pct / onset_notify_n in spi_field_guide_pager()'s indicators_df.

afp_process

Tibble, 2,360 x 6. District-year AFP process counts for the STEPS timeliness and stool adequacy components. Columns: adm2_guid, year, n_cases (AFP cases), n_adequate (cases with adequate stool specimens), n_transport (cases with both the second stool collection and laboratory receipt dates), n_transport_timely (of those, received by the laboratory within 3 days). These are the columns required by spi_field_guide()'s process argument.

simulation_truth

Tibble, 236 x 6. Settings the simulation used for each district; they describe the synthetic data, not true surveillance performance. Columns: adm2_guid, surveillance_profile (character: "resilient", "early_improver", "covid_transient", or "persistent_laggard"), is_blindspot (logical, TRUE for every non-resilient profile), is_low_incidence (logical, TRUE for the low-baseline clusters that seed the NPAFP below target only and Both below categories), covid_nadir (numeric, the district's relative detection completeness at the 2020 trough), and recovered_2024 (logical, TRUE if completeness is back to adequate by 2024).

Data-generating process

The simulated underlying AFP rate is stable over time. The proportion of cases reported, called detection completeness, changes over time. Counts are drawn from a negative-binomial likelihood with mean $$\mu^{obs}_{it} = c_{it}\,\exp\!\big(\alpha + b_i + s(m_t) + \log(\mathrm{pop}_{u15,it} / 10^5)\big),$$ where b_i is a BYM2 spatial random effect (mixing parameter 0.6, marginal precision 4), s(m_t) is a 1st-order harmonic seasonal term, and \(c_{it} = g_t \cdot r_{it}\) is detection completeness. The global factor g_t improves through 2015-2019 to a pre-COVID peak, falls by about 30% in 2020 (COVID-19 disruption), and recovers by 2023-24. The relative factor r_it is 1 for resilient districts (which track g_t) and dips below 1 for the other profiles in specific windows: early_improver (poor 2015-17, recovered by 2019), covid_transient (further decline in 2020-21, recovered by 2023), and persistent_laggard (decline in 2020 that persists through 2024). The SPI model's year effect accounts for g_t, so SPI tracks r_it. Reporting shortfalls become less common before 2020, increase in 2020-21, and decrease as districts recover. virus_outcome marks the year after a completeness gap as cVDPV2-positive at elevated rate.

Four spatially-contiguous clusters (25 districts, is_low_incidence) get a low baseline (b_i overridden so their true rate sits around 1.5 per 100,000 under-15 per year), so their genuine per-capita NPAFP rate sits below the conventional target while the resilient districts can have SPI at or above 1, giving the "NPAFP below target only" category. Sixteen of the twenty-five are also persistent laggards, so their completeness stays low from 2020 on and SPI places them in the "Both below" category. Districts with higher baseline reporting can meet the NPAFP target while SPI is below 1, giving the "SPI below threshold only" category. The simulation includes all four combinations across the ten study years; these categories do not establish adequacy.

Fictional geography

The fictional country Harad has 236 districts in 36 provinces. All place names are invented. The source geometry has been rotated onto a local grid and its CRS removed. The geography is used only for examples.

Attribution

Geometry is derived from real admin-2 boundaries for four bordering Lake Chad countries (Nigeria, Niger, Chad, Cameroon) from geoBoundaries gbOpen ADM2, redistributed under CC-BY 4.0 (Runfola et al. 2020, doi:10.1371/journal.pone.0231866 , https://www.geoboundaries.org). The units are merged, cropped to a region around the basin, joined without gaps, renamed, and rotated (see data-raw/synth_admin_polygons.R and the shipped inst/extdata/synth_admin_polygons.provenance.txt).

Population (pop_u15) is under-15 population estimates summed within each district from WorldPop unconstrained global age-structured rasters (0-14), keyed to the source geometry and shipped as inst/extdata/synth_admin_pop_u15.csv. WorldPop is released under CC-BY 4.0 (https://www.worldpop.org).

Reproducibility

Build the boundary layer with Rscript data-raw/synth_admin_polygons.R (writes inst/extdata/synth_admin_polygons.gpkg), then regenerate this object with Rscript --vanilla data-raw/synth_surveillance.R. The generator uses set.seed(20260702).

Examples

data("synth_surveillance", package = "spi")
names(synth_surveillance)
#>  [1] "cases"            "population"       "covariates"       "boundaries"      
#>  [5] "ward_boundaries"  "virus_outcome"    "es_sites"         "es_data"         
#>  [9] "es_district_year" "afp_timeliness"   "afp_process"      "simulation_truth"
head(synth_surveillance$cases)
#> # A tibble: 6 × 3
#>   adm2_guid                              month      count
#>   <chr>                                  <date>     <int>
#> 1 {54CD979C-CF9D-6A65-567F-8976C9546137} 2015-01-01     0
#> 2 {54CD979C-CF9D-6A65-567F-8976C9546137} 2015-02-01     0
#> 3 {54CD979C-CF9D-6A65-567F-8976C9546137} 2015-03-01     0
#> 4 {54CD979C-CF9D-6A65-567F-8976C9546137} 2015-04-01     0
#> 5 {54CD979C-CF9D-6A65-567F-8976C9546137} 2015-05-01     0
#> 6 {54CD979C-CF9D-6A65-567F-8976C9546137} 2015-06-01     1
head(synth_surveillance$boundaries)
#> Simple feature collection with 6 features and 4 fields
#> Geometry type: MULTIPOLYGON
#> Dimension:     XY
#> Bounding box:  xmin: 247201.8 ymin: 828050.5 xmax: 698647.9 ymax: 1239349
#> CRS:           NA
#>                                adm2_guid adm2_name adm1_name adm0_name
#> 1 {54CD979C-CF9D-6A65-567F-8976C9546137}    Kireth   Khandis     Harad
#> 2 {1854CE21-0D57-9748-D804-77E28CAD45C8}   Vasheth    Tirora     Harad
#> 3 {91E025E5-7F78-2AE6-6790-52BA0D18544B}    Raenan     Belor     Harad
#> 4 {8AF38330-E608-6D8E-F4D9-B025DBEFE44B}     Garar   Sarnmar     Harad
#> 5 {CEE28573-3561-A1CC-59F8-437EA0FD5C03}     Ardai   Sarnmar     Harad
#> 6 {632250CE-B852-246A-56D9-8854C5EB8A4E}  Porostha     Tiris     Harad
#>                             geom
#> 1 MULTIPOLYGON (((279388.1 86...
#> 2 MULTIPOLYGON (((645990.5 11...
#> 3 MULTIPOLYGON (((668581.6 10...
#> 4 MULTIPOLYGON (((550741.1 12...
#> 5 MULTIPOLYGON (((557640.6 11...
#> 6 MULTIPOLYGON (((291111.8 10...

if (FALSE) { # \dontrun{
# end-to-end demo (needs INLA)
spi <- spi_index(
  cases = synth_surveillance$cases,
  population = synth_surveillance$population,
  adjacency = synth_surveillance$boundaries,
  first_assessment = 2018,
  id_col = "adm2_guid",
  n_draws = 200
)
comparison <- spi_compare_npafp(
  spi = spi,
  cases = synth_surveillance$cases,
  population = synth_surveillance$population,
  spi_threshold = 0.80,
  npafp_target = 3,
  strata = c("year", "adm1_name"),
  boundaries = synth_surveillance$boundaries
)
} # }