This article shows how to prepare a POLIS AFP line list for the
simple SPI (spi_simple()) and the model-based SPI
(spi_index()). It is for users with POLIS access under a
GPEI data-sharing agreement who clean their data with polished. The examples
require your own POLIS data and are not run when this article is built.
Preparing data for SPI covers
data preparation in general.
1. Load the cleaned data
polished downloads and cleans the AFP line list, the population and the district boundaries.
cleaned <- polished::load_polished(country = "AFG")
afp <- cleaned$afp
population <- cleaned$pop_adm2
districts <- readRDS("data/gpei_adm2_shape.rds")2. What the tables contain
| Table | Rows | Columns used here |
|---|---|---|
afp |
One per AFP case |
adm2_guid, the paralysis onset date
(paralysis_onset_date), the onset year
(year_onset) and the classification
(classification_all) |
population |
One per district and year |
adm2_guid, year and the under-15
population (u15_pop) |
districts |
One per district |
adm2_guid and the geometry |
districts is the GPEI district shapefile used when
running polished. All three tables use the POLIS district GUID
(adm2_guid), so they link to each other without matching
names.
Cases are assigned to a month and a year by their date of paralysis onset.
3. Decide how to handle pending classifications
Cases still pending classification are mostly recent cases. The expected history can be biased if recent years contain many unresolved cases but previous years do not.
Decide whether pending cases count as NPAFP before aggregating, and apply the same rule to every year. Define the rule once and use it for both the simple and the model-based SPI:
# NPAFP with pending cases; use "NPAFP" alone to leave them out
npafp_classes <- c("NPAFP", "PENDING", "LAB PENDING")
afp_npafp <- afp |>
dplyr::filter(classification_all %in% npafp_classes)Leaving pending cases out lowers the most recent counts relative to earlier years. Counting them includes some cases that may later receive another classification.
4. NPAFP counts for the simple SPI
Count the cases by district and year of onset:
npafp_year <- afp_npafp |>
dplyr::count(adm2_guid, year = year_onset, name = "npafp_cases")
spi_results <- spi_simple(
npafp_year,
population = population,
id_col = "adm2_guid",
pop_col = "u15_pop"
)A count from the line list has no row for a district-year without a case. Passing the population table means those district-years are included with zero cases.
polished can also produce the counts directly.
polished::calc_polio_indicators() returns, at district
level, one row per district and year with the district GUID in
guid and the number of NPAFP cases in
npafp_cases:
indicators <- polished::calc_polio_indicators(
cases = afp,
population = population,
levels = "adm2"
)
npafp_year <- indicators$adm2 |>
dplyr::select(adm2_guid = guid, year, npafp_cases)By default calc_polio_indicators() counts pending cases
as NPAFP (include_pending = TRUE). Set
include_pending to match the rule chosen in section 3. Its
district table lists only district-years with AFP activity, so pass the
population table to spi_simple() as above.
The simple SPI calculates expected counts from each district’s own
earlier NPAFP rate. If a district has no earlier year or no earlier
NPAFP case, history_check records the reason and SPI is
NA. Include these rows in the surveillance review, using
the NPAFP rate and the available history.
5. Monthly counts for the model-based SPI
The model needs one row per district and month, including months with no case. Count the cases by district and month of onset:
months <- seq(as.Date("2016-01-01"), as.Date("2025-12-01"), by = "month")
npafp_month <- afp_npafp |>
dplyr::mutate(
month = as.Date(format(paralysis_onset_date, "%Y-%m-01"))
) |>
dplyr::count(adm2_guid, month, name = "count")Then build the monthly grid. Start from all districts in the boundary file, not only districts that appear in the case data. This keeps districts that reported zero NPAFP cases during the entire period.
6. Check the inputs and calculate the model-based SPI
spi_check_inputs() reports any case or population GUID
that is not in the district layer, along with missing months and
population problems:
spi_check_inputs(
cases = cases,
population = population,
shapefile = districts,
id_col = "adm2_guid",
pop_col = "u15_pop"
)Then fit the model and calculate the SPI. This needs INLA and takes several minutes; see Calculating the model-based SPI.
spi_year <- spi_index(
cases = cases,
population = population,
adjacency = districts,
first_assessment = 2019,
id_col = "adm2_guid",
pop_col = "u15_pop"
)