Skip to contents

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 a spi_index() result at district_year level.

cases

Optional tibble with the district id column and count (integer), and either month or year. Pass to override the observed counts in spi$summary. Default: NULL (use the index's observed counts).

population

Tibble with the district id column, year (integer), and the population denominator column (see pop_col), with one positive denominator per district and reporting year. For non-calendar years, supply the mean population over that reporting year. Required for a spi_index() result. For a spi_simple() result the default NULL uses the population already in spi$summary.

year_end_month

Integer 1 to 12. Month in which the reporting year closes, matching the year_end_month the 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" uses spi_median alone. "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_multiplier person-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 sf object with the district id column and any adm1/adm0 name columns. When supplied its non-geometry columns are joined onto the district-year table so strata can 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 from spi$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 strata is 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)
} # }