Classifies each district-year by whether its SPI is below the SPI threshold and whether its NPAFP rate is below the NPAFP target. The default SPI threshold is 1. The four categories are:
Neither below – NPAFP >= target AND SPI >= threshold.
Both below – NPAFP < target AND SPI < threshold.
SPI below threshold only – NPAFP >= target BUT SPI < threshold.
NPAFP below target only – NPAFP < target BUT SPI >= threshold.
The two measures answer different questions, so neither is a reference standard for the other. The categories describe where they agree or differ; they do not establish whether surveillance is adequate.
Agreement is summarised as percentage agreement and Cohen's kappa, plus a McNemar test of whether one measure places more district-years below its threshold than the other.
The simple SPI and the model-based SPI go through the same comparison. For
a simple result, spi_median holds the single SPI value and spi_q05 and
spi_q95 are NA. District-years without a simple SPI (see
history_check in spi_simple()) are left out of the comparison.
Usage
spi_compare_npafp(
spi,
cases = NULL,
population = NULL,
year_end_month = 12L,
spi_threshold = 1,
spi_rule = c("median", "interval"),
npafp_target = 3,
npafp_multiplier = 100000L,
strata = NULL,
id_col = NULL,
pop_col = "pop_u15",
boundaries = NULL,
verbose = TRUE
)Arguments
- spi
A district-year SPI: a
spi_simple()result, or aspi_index()result atdistrict_yearlevel.- cases
Optional tibble with the district id column and
count(integer), and eithermonthoryear. Pass to override the observed counts inspi$summary. Default: NULL (use the index's observed counts).- population
Tibble with the district id column,
year(integer), and the population denominator column (seepop_col), with one positive denominator per district and reporting year. For non-calendar years, supply the mean population over that reporting year. Required for aspi_index()result. For aspi_simple()result the default NULL uses the population already inspi$summary.- year_end_month
Integer 1 to 12. Month in which the reporting year closes, matching the
year_end_monththe SPI was computed with. The conventional rate is grouped on the same rolling year, so the two sides of the comparison cover the same months. The simple SPI uses calendar years only. Default: 12 (calendar years).- spi_threshold
Numeric. An SPI below this value counts as below the threshold, subject to
spi_rule. For the model-based SPI the comparison uses the posterior median. The reference is 1: with national centring, district and national observed-to-expected ratios are equal. Default: 1.- spi_rule
Character.
"median"usesspi_medianalone."interval"additionally requires the 90% upper bound (spi_q95) to be below 1. This requires stronger evidence of a relative reporting shortfall, not evidence of missed cases. The paper uses this rule for a sensitivity analysis. The simple SPI has no interval, so it accepts only"median". Default:"median".- npafp_target
Numeric. NPAFP reporting-rate target per
npafp_multiplierperson-years. Set this to the target used by the programme. Default: 3, as in the accompanying study.- npafp_multiplier
Numeric. Denominator scaling for the NPAFP rate. Default: 100000 (per 100,000 under-15 person-years).
- strata
Character vector of column names to stratify the comparison by. Any column present in the district-year summary is valid:
"year","adm1_name","adm0_name", or any joined covariate. Set NULL to summarise all district-years together. Default: NULL.- id_col
Character. Name of the district id column. Inferred from the SPI result if NULL. Default: NULL.
- pop_col
Character. Population denominator column name. Default:
"pop_u15".- boundaries
Optional
sfobject with the district id column and any adm1/adm0 name columns. When supplied its non-geometry columns are joined onto the district-year table sostratacan reference region/country labels. Default: NULL.- verbose
Logical. Progress messages via cli. Default: TRUE.
Value
An object of class spi_compare_npafp. A list with:
- district_year
Tibble with per-district-year classification:
{id_col},year,count_annual,pop_u15,npafp_rate,npafp_adequate(logical: rate target met),observed(reported count fromspi$summary, when present),expected_total(expected count, when present),spi_median,spi_q05,spi_q95,spi_below_threshold(logical),spi_pass(logical,!spi_below_threshold),category(factor: Neither below / SPI below threshold only / NPAFP below target only / Both below).- crosstab
2x2 table of counts and row/column percentages.
- metrics
Summary measures across all district-years:
n,pct_agreement,cohens_kappa,mcnemar_p, plus per-cell counts.- by_stratum
Tibble of per-stratum metrics when
stratais non-NULL; else NULL.- thresholds
Named list echoing the SPI threshold, the
spi_rule, and the NPAFP target used.- id_col
The id column name.
- method
"simple"or"model": which SPI was compared.- call
Matched call.
Examples
annual <- synth_surveillance$cases |>
dplyr::mutate(year = as.integer(format(month, "%Y"))) |>
dplyr::summarise(count = sum(count), .by = c(adm2_guid, year))
simple <- spi_simple(
data = annual,
population = synth_surveillance$population,
id_col = "adm2_guid",
count_col = "count",
pop_col = "pop_u15",
verbose = FALSE
)
spi_compare_npafp(simple, npafp_target = 3)
#> ℹ 8 district-years without a simple SPI left out (see history_check).
#> ℹ 1,644 district-years | agreement 52.3% | kappa 0.13
#>
#> ── SPI and NPAFP classification ────────────────────────────────────────────────
#> SPI cut: 1 (median rule) | NPAFP target: 3 per 100,000 person-years
#> 1,644 district-years | agreement 52.3% | kappa 0.13 | McNemar p 0.0000
#>
#> ── Category counts ──
#>
#> # A tibble: 4 × 3
#> category n pct
#> <chr> <int> <dbl>
#> 1 Neither below 678 41.2
#> 2 SPI below threshold only 742 45.1
#> 3 NPAFP below target only 42 2.6
#> 4 Both below 182 11.1
if (FALSE) { # \dontrun{
spi_dy <- spi_index(
cases = synth_surveillance$cases,
population = synth_surveillance$population,
adjacency = synth_surveillance$boundaries,
first_assessment = 2018,
id_col = "adm2_guid"
)
comparison <- spi_compare_npafp(
spi = spi_dy,
population = synth_surveillance$population,
spi_threshold = 1,
npafp_target = 3,
strata = c("year", "adm1_name"),
boundaries = synth_surveillance$boundaries
)
print(comparison)
summary(comparison)
plot(comparison)
} # }