fams <- available_indicators()
table(fams$family)
#>
#> AFP Composite Dose ES Lab SIA Stool
#> 7 4 8 8 1 7 3
#> Timeliness Virus
#> 17 7
# the AFP family, with what each indicator needs
available_indicators(family = "AFP")[, c("code", "label", "requires_pop")]
#> # A tibble: 7 × 3
#> code label requires_pop
#> <chr> <chr> <lgl>
#> 1 afp_count AFP count FALSE
#> 2 npafp_count NPAFP count FALSE
#> 3 npafp_rate NPAFP rate (per 100k under-15) TRUE
#> 4 npafp_rate_nopending NPAFP rate, no pending (per 100k) TRUE
#> 5 unclass_cases_pct Unclassified cases (%) FALSE
#> 6 fup_insa_cases_pct 60-day follow-up of inadequate cases (%) FALSE
#> 7 case_contacts_avg Contacts sampled per case (avg) FALSEOnce the surveillance streams are cleaned (see the Cleaning POLIS data article), calc_polio_indicators() computes the WHO POLIS indicator catalogue — the NPAFP rate, stool adequacy, timeliness, dose history, environmental, virus, SIA and composite families — at the admin levels you ask for.
Browse the catalogue: available_indicators()
Every indicator the package knows about lives in a single registry. Read it with available_indicators(), optionally filtered by family:
requires_pop = TRUE marks the indicators that need an under-15 population denominator (the rate families); everything else is computed from the case/ES/ virus/SIA tables alone.
Inputs
calc_polio_indicators() needs only a cleaned cases table to start; es, sia, virus, lab, population and admin_units are optional and unlock the indicators that depend on them. A missing source simply skips its indicators with a message rather than erroring.
The example below builds a small cleaned AFP table by hand so it renders without a live pull — in practice cases is the output of clean_afp() or run_pipeline().
cases <- tibble::tibble(
epid = sprintf("NIE-BOR-D%02d-24-%03d", rep(1:2, length.out = 24), 1:24),
classification_all = rep(c("NPAFP", "NPAFP", "NPAFP", "WPV1", "NOT-AFP", "PENDING"), 4),
age_months = rep(c(12, 30, 48, 24), 6),
year_onset = 2024L,
paralysis_onset_date = as.Date("2024-01-01") + (1:24),
adm0 = "NIGERIA", adm0_guid = "G0",
adm1 = rep(c("BORNO", "YOBE"), length.out = 24),
adm1_guid = rep(c("P1", "P2"), length.out = 24),
adm2 = rep(c("MMC", "JERE", "GUJBA", "NGANZAI"), length.out = 24),
adm2_guid = rep(c("D1", "D2", "D3", "D4"), length.out = 24),
adequate_stool = rep(c("Yes", "No"), length.out = 24),
notify_to_invest = rep(c(1, 2, 5, 1), 6)
)The under-15 population denominator is an external table keyed by adm2_guid, year and u15_pop (one row per admin unit per year):
Compute
Ask for the admin levels you want; each is aggregated independently from the case GUIDs. indicators selects what to compute: "core" (the default — the core KPI set), "all" (the full registered catalogue), or an explicit vector of codes. This article walks the broader catalogue, so it asks for "all":
ind <- calc_polio_indicators(
cases = cases,
population = population,
levels = c("adm0", "adm1", "adm2"),
indicators = "all",
verbose = FALSE
)
names(ind)
#> [1] "adm0" "adm1" "adm2" "long" "meta"
#> [6] "validation"The result is a list: one wide tibble per level (adm0 / adm1 / adm2, one row per admin unit × year with every indicator as a column), a tidy long table (one row per admin × year × indicator), and a meta record of what ran, was skipped, and the thresholds used.
ind$long[
ind$long$indicator %in% c("afp_count", "npafp_count", "npafp_rate", "stool_adequacy_pct"),
c("level", "name", "indicator", "value")
]
#> # A tibble: 28 × 4
#> level name indicator value
#> <chr> <chr> <chr> <dbl>
#> 1 adm0 NIGERIA afp_count 20
#> 2 adm0 NIGERIA npafp_count 12
#> 3 adm0 NIGERIA npafp_rate 0.798
#> 4 adm0 NIGERIA stool_adequacy_pct 40
#> 5 adm1 BORNO afp_count 8
#> 6 adm1 YOBE afp_count 12
#> 7 adm1 BORNO npafp_count 8
#> 8 adm1 YOBE npafp_count 4
#> 9 adm1 BORNO npafp_rate 0.798
#> 10 adm1 YOBE npafp_rate 0.798
#> # ℹ 18 more rowsThe wide per-level tibble is the analyst-facing shape — one row per unit:
ind$adm1[, c("name", "afp_count", "npafp_count", "npafp_rate", "stool_adequacy_pct")]
#> # A tibble: 2 × 5
#> name afp_count npafp_count npafp_rate stool_adequacy_pct
#> <chr> <dbl> <dbl> <dbl> <dbl>
#> 1 BORNO 8 8 0.798 100
#> 2 YOBE 12 4 0.798 0Population denominators and suppression
Rate indicators (e.g. npafp_rate) divide a count by the under-15 population and scale by rate_multiplier (default per 100,000). To keep small-denominator noise out of the output, a unit’s rate is suppressed (NA) when its population is below min_pop or its case count is below min_cases. Pass no population and the rate families are skipped entirely — every population-independent indicator (counts, percentages, timeliness) still computes:
no_pop <- calc_polio_indicators(
cases = cases,
levels = "adm0",
indicators = "all",
verbose = FALSE
)
no_pop$long[no_pop$long$indicator %in% c("npafp_count", "npafp_rate"), c("indicator", "value")]
#> # A tibble: 1 × 2
#> indicator value
#> <chr> <dbl>
#> 1 npafp_count 12Choosing the population source
When the end-to-end pipeline builds the denominator itself from a raw POLIS population table (via clean_pop()), you choose which population it uses with pop_source on polis_config() (or directly on clean_pop(pop_source =)):
-
"reconciled"(default): a trusted POLIS value, else WorldPop, else a district / province / country fallback. -
"polis": the full POLIS population (WorldPop is ignored even if supplied). -
"worldpop": the WorldPop value, else POLIS, else the fallback.
Whatever the mode, the cleaned population keeps <age>_pop_polis and <age>_pop_wp next to the chosen <age>_pop, so the three sources stay comparable.
See ?calc_polio_indicators for the full argument set — the column-name overrides, the classification vocabularies, and every threshold.
