Reviews each district-year in a spi_compare_npafp() result using the five
STEPS components (strength,
timeliness, extent, persistence, stool adequacy) and one of the field
guide's three review labels (Priority for review, Monitor,
No SPI indication). STEPS is applied only to districts with an SPI below
spi_cut (1 by default, as in the field guide).
SPI compares reported NPAFP cases with expected counts. STEPS adds specimen transport and stool adequacy, and checks whether a low SPI also appears elsewhere in the area or in the previous year. The five components and what each asks:
S: Strength – how large is the shortfall, and how uncertain is the estimate? Read from the SPI value and whether its 90% credible interval lies entirely below 1.
T: Timeliness – are specimens reaching the laboratory within 3 days? Needs
process.E: Extent – is a low SPI more common in the same region (such as a province or state) than nationally? Needs an
extent_colin the comparison.P: Persistence – was the SPI also below the cut in the previous year?
S: Stool adequacy – are stool specimens adequate? Needs
process.
The five STEPS components are reported separately. The review label depends on strength, extent, and persistence. Timeliness and stool adequacy describe specimen handling and do not change the label. These labels are not validated measures of surveillance adequacy or automatic recommendations for field action.
Trend, neighbouring-district context, seasonal patterns, and poliovirus detections through AFP or environmental surveillance are reported separately as supporting context when inputs are supplied. They do not change the generated review label.
Usage
spi_field_guide(
comparison,
process = NULL,
extent_col = NULL,
process_target = 80,
process_min_cases = 5L,
adjacency = NULL,
spi_month = NULL,
detections = NULL,
detection_col = NULL,
es = NULL,
es_col = NULL,
serotype_col = NULL,
detection_serotypes = NULL,
read_year = NULL,
spi_cut = NULL,
spi_rule = NULL,
traj_window = 5L,
traj_tol = 0.01,
traj_alpha = NULL,
noise_alpha = NULL,
id_col = NULL,
verbose = TRUE
)Arguments
- comparison
A
spi_compare_npafp()result (classspi_compare_npafp). Supplies SPI and NPAFP results for every district-year.- process
Optional district-year tibble of AFP process counts, keyed by the district id column and
year, with columnsn_cases(AFP cases),n_adequate(cases with adequate stool specimens),n_transport(cases with both the second stool collection and laboratory receipt dates, in order) andn_transport_timely(of those, received by the laboratory within 3 days of the second stool collection). Enables timeliness (T) and stool adequacy (S). District-years missing fromprocesscount as zero cases. Default: NULL.- extent_col
Name of the admin-1 column in
comparison$district_yearthat defines the area for extent (E). Default: NULL ("adm1_name"when present, as it is whenspi_compare_npafp()was givenboundaries).- process_target
Numeric target, in percent, for timeliness and stool adequacy. Default: 80.
- process_min_cases
Integer. Fewest cases for timeliness or stool adequacy to be assessed. Default: 5.
- adjacency
Optional spatial neighbour object from
spi_adjacency()(classspi_nb). Enables the neighbour contrast, reported as context. Default: NULL.- spi_month
Optional
spi_index()result atdistrict_monthlevel. Enables assessment of seasonal reporting patterns, reported as context. Default: NULL.- detections
Optional tibble of case-based (AFP) poliovirus detections with the district id column and
year. Reported as context. Rows are detections; passdetection_colto filter on a 0/1 flag column. Default: NULL.- detection_col
Optional name of a logical / 0-1 column in
detections; onlyTRUEor 1 rows count as detections. Default: NULL (every row counts).- es
Optional tibble of environmental-surveillance (ES) detections with the district id column and
year. Reported as context. Rows are positives; passes_colto filter on a count / flag column. Default: NULL.- es_col
Optional name of a count / logical column in
es; only rows with a positive count (orTRUE) count as detections. Default: NULL (every row counts).- serotype_col
Optional name of a serotype column in
detectionsand / ores, e.g. holding"cVDPV2","WPV1". When given, the distinct serotypes seen up to each year are recorded asorphan_serotypes/es_serotypes, so the report can name the serotypes found. Without it those columns are NA, which downstream reads as "not recorded" rather than "none found". Default: NULL.- detection_serotypes
Optional character vector of the serotypes that count as a detection, matched against
serotype_col, e.g.c("WPV1", "cVDPV1", "cVDPV2", "cVDPV3"). Rows outside the set are dropped before the detection years, flags and serotypes are computed. Requiresserotype_col. Default: NULL (whatever the input contains counts).- read_year
Integer year shown by
print()and by default in tables and worked examples. Default: NULL (latest year present).- spi_cut
Numeric SPI cutoff below which STEPS is applied. Lower values such as 0.80, 0.60, or 0.40 focus review on larger reporting shortfalls. The same cutoff is used for persistence and extent; the credible-interval check still compares with 1. A district above a lower cutoff may still need review based on other surveillance indicators. Default: NULL, which is 1 for the model-based SPI and 0.5 for the simple SPI.
- spi_rule
Character,
"median"or"interval"."median"labels every district-year with an SPI belowspi_cut."interval"also requires the 90% upper bound (spi_q95) to be below 1, so a district-year whose interval includes 1 is labelledNo SPI indication. Extent and persistence still use the median. Default: NULL (thespi_rulegiven tospi_compare_npafp()).- traj_window
Integer. Trend regression window in years, ending at each year (context). Default: 5.
- traj_tol
Numeric tolerance for the annual trend slope. A slope whose absolute value is below this tolerance is labelled "flat". Default: 0.01.
- traj_alpha
Optional numeric significance level for the trend. When set, a trajectory is only "falling" or "rising" if its OLS slope differs from zero at this two-sided level (needing at least three points in the window); otherwise it is "flat". Default: NULL (slope sign and tolerance only).
- noise_alpha
Optional numeric threshold for a sampling-noise check on strength (S). The SPI credible interval is posterior uncertainty in the expected count with the observed count held fixed, so it says nothing about sampling variation in the count itself. At
observed = 0the ratio is identically zero in every draw, the interval collapses to(0, 0), andcri_excludes_1is TRUE whatever the expected count. Whennoise_alphais set,gate_passadditionally requires the Poisson reference tail \(P(X \le observed \mid \lambda = expected)\) to be at or below it.cri_excludes_1keeps its own meaning either way. The tail is always reported (noise_tail,noise_plausible). Default: NULL (no check). A value such as0.05adds this check; it is not a validated field-action threshold.- id_col
Character district id column. Default: NULL (take
comparison$id_col).- verbose
Logical. Progress and missing-input messages via cli. Default: TRUE.
Value
An object of class spi_field_guide. A list with:
- district_year
Tibble, one row per district-year, carrying the inputs plus every component: strength (
spi_below,cri_excludes_1,gate_pass,noise_tail,noise_plausible), timeliness (n_transport,n_transport_timely,pct_transport_timely,transport_assessed,timeliness_concern), extent (extent_area,extent_others,extent_others_below,extent_pct,national_pct,extent_concern), persistence (spi_previous,persistence_concern), stool adequacy (n_cases,n_adequate,pct_adequate,adequacy_assessed,adequacy_concern), the context signals (s1_discordance,longest_run_below,trailing_run_below,trajectory,neighbour_spi,neighbour_discordant,neighbourhood_shortfall,seasonal,orphan_years,genomic_orphan,orphan_serotypes,es_years,es_detected,es_serotypes), andverdict(factor).- focal
The
read_yearslice ofdistrict_year.- reference
The STEPS reference tibble (what each component asks and how to interpret it).
- read_year, thresholds, params, signals_active, id_col, call
Metadata: focal year,
spi_cut/npafp_target/spi_rule, the tuning parameters, which optional components and context signals were computed, the id column, and the matched call.
Details
Each district-year receives one of three review labels, using the field guide's descriptions:
Priority for review – the SPI is below
spi_cut, its 90% credible interval lies entirely below 1 (and, whennoise_alphais set, sampling noise alone is unlikely to produce the shortfall), and extent or persistence also raises concern. The label identifies districts to consider for closer review.Monitor – the SPI is below
spi_cut, but the interval includes 1, the optional sampling-noise check fails, or neither extent nor persistence supports it. Uncertainty or limited supporting information favours reassessment as new data become available.No SPI indication – the SPI is at or above
spi_cut. There is no SPI indication for additional review; routine surveillance and review based on other indicators continue.
With spi_rule = "interval", a district-year whose 90% credible interval
includes 1 is labelled No SPI indication rather than Monitor, so only
shortfalls whose interval lies entirely below 1 are labelled.
Simple SPI. A comparison built from spi_simple() has no credible
interval. The strength component uses the SPI value alone. The default
cutoff is spi_cut = 0.5, which focuses review on larger relative
shortfalls. This cutoff does not measure uncertainty. A district-year
with an SPI below 0.5 and support from extent
or persistence is labelled Priority for review; one below 0.5 without that
support is labelled Monitor. spi_rule = "interval" is not available.
Extent compares the share of the other districts in the same extent_col
area with an SPI below the cut against the national share in the same year,
and raises concern when the area share is higher. Timeliness and stool
adequacy are assessed on at least process_min_cases cases against a target
of process_target percent; below that they are NA (not assessed).
Without process
or an extent_col the corresponding component is NA. signals_active
records which components and additional findings could be calculated.
See also
spi_field_guide_table() to render it, spi_field_guide_help()
to learn to read it, spi_compare_npafp() for the input, and the one-page
infographic at
system.file("field-guide", "npafp_spi_steps_infographic.html", package = "spi").
Examples
# a precomputed field guide on the synthetic bundle ships with the package
fg <- synth_field_guide
print(fg)
#>
#> ── SPI field guide ─────────────────────────────────────────────────────────────
#> Review year: 2024 | STEPS applied below SPI 1 (median rule) | priority for
#> review: 90% CrI below 1, with extent or persistence
#> Available components and additional findings: "timeliness", "extent",
#> "adequacy", "surroundings", "seasonal", "detect_afp", and "detect_es"
#>
#> ── Review labels for 2024 ──
#>
#> # A tibble: 3 × 3
#> verdict n pct
#> <chr> <int> <dbl>
#> 1 Priority for review 60 25.4
#> 2 Monitor 71 30.1
#> 3 No SPI indication 105 44.5
#> ── Districts with priority for review (top 10 by size of shortfall) ──
#>
#> # A tibble: 60 × 11
#> district obs exp spi cri npafp extent persist transport adequacy
#> <chr> <int> <dbl> <dbl> <chr> <dbl> <lgl> <lgl> <dbl> <dbl>
#> 1 Khandor 0 2.4 0 0.00-0.00 0 TRUE TRUE NA NA
#> 2 Arddor 0 1.7 0 0.00-0.00 0 TRUE TRUE NA NA
#> 3 Ardor 0 1.2 0 0.00-0.00 0 TRUE TRUE NA NA
#> 4 Doloth 0 1.1 0 0.00-0.00 0 TRUE TRUE NA NA
#> 5 Raenan 0 1 0 0.00-0.00 0 TRUE TRUE NA NA
#> 6 Chakis 0 1.9 0 0.00-0.00 0 TRUE TRUE NA NA
#> 7 Kiroth 0 1.5 0 0.00-0.00 0 TRUE FALSE NA NA
#> 8 Vashoth 0 2.3 0 0.00-0.00 0 TRUE TRUE NA NA
#> 9 Suzil 1 8.1 0.13 0.10-0.17 1.6 FALSE TRUE 100 100
#> 10 Nentha 2 14.5 0.14 0.11-0.18 2 TRUE FALSE 100 100
#> # ℹ 50 more rows
#> # ℹ 1 more variable: verdict <chr>
#> ℹ See `spi_field_guide_help()` for worked examples, `spi_field_guide_table()` to render it.
head(as_tibble(fg))
#> # A tibble: 6 × 60
#> adm0_name adm1_name adm2_name adm2_guid year observed expected_total
#> <chr> <chr> <chr> <chr> <dbl> <int> <dbl>
#> 1 Harad Gartha Khandor {01325AA0-BEA1-66… 2015 1 1.55
#> 2 Harad Gartha Khandor {01325AA0-BEA1-66… 2016 2 1.87
#> 3 Harad Gartha Khandor {01325AA0-BEA1-66… 2017 0 1.90
#> 4 Harad Gartha Khandor {01325AA0-BEA1-66… 2018 2 2.11
#> 5 Harad Gartha Khandor {01325AA0-BEA1-66… 2019 5 2.49
#> 6 Harad Gartha Khandor {01325AA0-BEA1-66… 2020 2 1.39
#> # ℹ 53 more variables: spi_median <dbl>, spi_q05 <dbl>, spi_q95 <dbl>,
#> # count_annual <int>, pop_u15 <dbl>, npafp_rate <dbl>, npafp_adequate <lgl>,
#> # spi_below_threshold <lgl>, spi_pass <lgl>, category <fct>,
#> # conventional_pass <lgl>, spi_below <lgl>, noise_tail <dbl>,
#> # noise_plausible <lgl>, cri_excludes_1 <lgl>, gate_pass <lgl>,
#> # s1_discordance <lgl>, spi_previous <dbl>, persistence_concern <lgl>,
#> # national_pct <dbl>, extent_others <int>, extent_others_below <int>, …
if (FALSE) { # \dontrun{
# build one from scratch (needs INLA for the upstream fit)
adj <- spi_adjacency(synth_surveillance$boundaries, id_col = "adm2_guid")
cy <- spi_index(
synth_surveillance$cases, synth_surveillance$population, adj,
first_assessment = 2018, id_col = "adm2_guid"
)
cm <- spi_index(
synth_surveillance$cases, synth_surveillance$population, adj,
first_assessment = 2018, level = "district_month", id_col = "adm2_guid"
)
comparison <- spi_compare_npafp(
cy, synth_surveillance$cases, synth_surveillance$population,
boundaries = synth_surveillance$boundaries
)
fg <- spi_field_guide(
comparison,
process = synth_surveillance$afp_process,
adjacency = adj,
spi_month = cm,
detections = synth_surveillance$virus_outcome |>
dplyr::filter(any_virus == 1) |>
dplyr::select(adm2_guid, year),
es = synth_surveillance$es_district_year,
es_col = "n_positive"
)
} # }