
Clean POLIS population and (optionally) reconcile against WorldPop
Source:R/clean_pop.R
clean_pop.RdBuilds adm0/adm1/adm2 population denominators (under-5, under-15, all-ages)
from the raw POLIS Population table. Reduces duplicate (place, year) rows
to their median, treats zeros and blanks as missing, flags departures from
district history and fills gaps using district or parent-area estimates.
When worldpop is supplied, also compares the two sources and uses WorldPop
in the replacement rules described below.
Usage
clean_pop(
population,
cfg = polis_active_config(),
shape = NULL,
worldpop = NULL,
years = 2010:2027,
thresholds = list(ratio_lo = 1/3, ratio_hi = 3, mad_k = 5, min_votes = 1L, dens_lo = 5,
share_lo = 0.2, share_hi = 0.7, min_level_years = 2L),
reference_date = .polis_reference_date(cfg),
pop_source = c("reconciled", "polis", "worldpop"),
verbose = TRUE
)Arguments
- population
A raw POLIS Population data frame (columns
PlaceId,PlaceDisplayName,Year,AgeGroupName,Value), or a path to one.- cfg
A
polis_config()object; defaults topolis_active_config(). Only its presence is required – clean_pop reads no scoping from it (pop is foundational/global), but it keeps the cleaner signature uniform.- shape
Optional already-processed district shape (an
sfpolygon layer or its long ADM2 attribute table, or a path to one). Supplies the adm0/adm1 parents POLIS lacks, the boundary-validity windows used for the roll-ups, and the universe of districts. With no shape the output is keyed onadm2_guidonly and cannot be rolled up. DefaultNULL.- worldpop
Optional named list with elements
all,u5,u15. Each is either a directory of annual WorldPop GeoTIFFs (one per year, the year in the file name; zonal-summed to theshapeviaterra+exactextractr– both optional Suggests), or a pre-extracted adm2-by-year table (data frame or path) carryingadm2_guid,yearand a population column.NULL(default) runs the POLIS-only path.- years
Calendar years to keep (POLIS carries 1990-2034 incl. projections). Default
2010:2027.- thresholds
Named list of the implausibility tunables:
ratio_lo,ratio_hia POLIS value below/above this fold of WorldPop is implausible (
bad_vs_worldpop, and against the province's typical ratio forbad_vs_adm1).mad_kscaled-MAD distance from the district's own median (
bad_vs_history) – the signal that catches a single bad year.min_voteshow many signals must fire to call a value suspect.
dens_lopeople per km2 below which WORLDPOP is treated as the implausible comparison source (
wp_implausible). Needs a polygonshape; without one the signal never fires. Guards the case where a low WorldPop estimate would otherwise cause a POLIS value to be rejected.share_lo,share_hibounds on the under-15 share of all-ages used when reporting age-band coherence.
min_level_yearshow many trusted years a district needs before a POLIS:WorldPop level ratio is established from them. A level cannot be inferred from one observation, so below this count the district falls to raw WorldPop instead of being rescaled on a ratio it has no evidence for.
Default
list(ratio_lo = 1/3, ratio_hi = 3, mad_k = 5, min_votes = 1L, dens_lo = 5, share_lo = 0.2, share_hi = 0.7, min_level_years = 2L). Supplying a partial list overrides only the keys given.- reference_date
Date treated as "today" when deciding which boundary versions are current for the orphan-GUID name crosswalk. Defaults to
cfg$reference_date, orSys.Date()when it isNULL.- pop_source
Which population to use as the chosen
<age>_popvalue (the denominator indicators read). One of:"reconciled"(default) a trusted POLIS value, then the district's own interpolated series, then WorldPop rescaled onto the district's level, then raw WorldPop, then the district -> province -> country ladder. See the substitution ladder in Details.
"polis"the POLIS value, with interior gaps interpolated from the district's own series and the rest from the admin ladder; WorldPop is ignored even if supplied – the full POLIS population.
"worldpop"the WorldPop value, else a POLIS value, else the ladder. No levelling: this mode is asking for WorldPop's own numbers.
The output always keeps
<age>_pop_polisand<age>_pop_wpalongside the chosen<age>_pop, so every source stays inspectable whatever the mode.- verbose
Emit cli progress headers. Default
TRUE.
Value
A named list:
adm2district x year, wide: the id columns plus, per age band (
u5/u15/all),<age>_pop(chosen),<age>_pop_polis,<age>_pop_wp,<age>_pop_source(polis/polis_interp/worldpop_levelled/worldpop/district_trend/adm1/adm0),<age>_pop_imputed,<age>_pop_level_ratio(the POLIS:WorldPop ratio used to put a substituted value on the district's level –NAwhen none was needed or none could be formed) and<age>_pop_frozen(the POLIS value repeated verbatim from the previous year). Plusage_order_badandage_source_split(the bands do not share a level – non-empty only where no band had a ratio to lend). Restricted to the boundary valid each year (no double-counting versioned shapes).adm1,adm0province / country roll-ups (sums) of the nine pop columns. The per-district ratio and frozen flags are deliberately not rolled up: neither is meaningful summed across districts.
metaa list (skipped by the file writer):
audit(one row per district x year x age, with every signal flag –bad_vs_worldpop,bad_vs_history,bad_vs_adm1,wp_implausible,frozen,n_votes,polis_suspect),dup_conflicts,orphan_xwalk,params.
Checks and replacement rules
Duplicate (place, year) values are reduced to their median; conflicting
values are recorded in meta$dup_conflicts. Zeros and blanks become NA.
The checks compare each district with its own history (bad_vs_history)
and, when WorldPop is supplied, with WorldPop and province-level ratios
(bad_vs_worldpop, bad_vs_adm1). Thresholds control which values are flagged.
Additional checks record unresolved district mappings (meta$orphan_xwalk),
inconsistent age-band ordering (age_order_bad), age bands using different
sources (age_source_split) and unchanged annual values (<age>_pop_frozen).
Frozen values are flagged but are not replaced on that basis alone.
With polygon boundaries, wp_implausible identifies WorldPop estimates
below the configured population-density threshold.
Replacing missing or rejected values
The reconciled series uses the following sources in order. Interpolation and rescaling use the district's accepted POLIS values to reduce abrupt changes caused by switching between population sources.
polis: a POLIS value accepted by the configured checks.polis_interp: linear interpolation between accepted POLIS years. This step does not extrapolate beyond those years.worldpop_levelled: WorldPop rescaled using the POLIS-to-WorldPop ratio at the nearest accepted year, when enough paired years are available.worldpop: the unscaled WorldPop estimate when no usable POLIS level ratio is available.district_trend/adm1/adm0: estimates from district trends or parent administrative areas when the preceding sources are unavailable.
Missing and rejected values follow the same replacement rules. The output
retains <age>_pop_polis and <age>_pop_wp alongside the selected population
so users can compare sources. Passing the checks does not establish that an
estimate is the true population.
See also
run_pipeline(), which runs this as the population stream;
checks_pop(), which turns the result into a data-quality workbook.
Examples
pop_raw <- data.frame(
PlaceId = "0cda1c45-9529-4188-aaaa-000000000001",
PlaceDisplayName = "SOMEWHERE",
Year = 2020, AgeGroupName = "0 to 15 years", Value = 1000,
check.names = FALSE
)
res <- clean_pop(pop_raw, years = 2020, verbose = FALSE)
names(res)
#> [1] "adm0" "adm1" "adm2" "meta"