root <- file.path(tempdir(), "polio_pipeline")
init_polis_pipeline(
root,
regions = c("EMRO", "AFRO"), # WHO regions the pipeline is scoped to
start_year = 2015,
pop_years = 2010:2027,
pop_source = "reconciled" # "reconciled" | "polis" | "worldpop"
)A real POLIS analysis is more than one run_pipeline() call: it has raw downloads, processed shapefiles, WorldPop rasters, a population reconciliation, and the cleaned polished_* outputs — each with a conventional home on disk, and a cfg that ties them together. init_polis_pipeline() stamps out that whole project in one call: the directory layout, a wired .Rprofile, a .gitignore, and runnable 2a_download_data.R / 2b_process_data.R starter scripts.
It is the heavier sibling of init_polis_project():
init_polis_project() |
init_polis_pipeline() |
|
|---|---|---|
| Layout | generic raw/processed/validation/cache/logs zones |
domain-numbered 01_data (shapefiles / population / polis / vaccination), 02_scripts, 03_outputs
|
Writes a .Rprofile? |
no | yes — the cfg manifest |
| Writes starter scripts? | no | yes — download + process |
| Use it when | you just need tidy zones for one stream | you want the full reproducible download → clean pipeline |
One call
The layout it produces
cat(sub(root, "<project>", list.dirs(root), fixed = TRUE), sep = "\n")
#> <project>
#> <project>/01_data
#> <project>/01_data/1a_shapefiles
#> <project>/01_data/1a_shapefiles/processed
#> <project>/01_data/1a_shapefiles/raw
#> <project>/01_data/1b_population
#> <project>/01_data/1b_population/polis_pop
#> <project>/01_data/1b_population/polis_pop/processed
#> <project>/01_data/1b_population/polis_pop/raw
#> <project>/01_data/1b_population/worldpop
#> <project>/01_data/1b_population/worldpop/processed
#> <project>/01_data/1b_population/worldpop/raw
#> <project>/01_data/1b_population/worldpop/raw/all
#> <project>/01_data/1b_population/worldpop/raw/u15
#> <project>/01_data/1b_population/worldpop/raw/u5
#> <project>/01_data/1c_polis
#> <project>/01_data/1c_polis/processed
#> <project>/01_data/1c_polis/processed/cache
#> <project>/01_data/1c_polis/processed/checks
#> <project>/01_data/1c_polis/processed/data
#> <project>/01_data/1c_polis/raw
#> <project>/01_data/1d_vaccination
#> <project>/01_data/1d_vaccination/processed
#> <project>/01_data/1d_vaccination/raw
#> <project>/02_scripts
#> <project>/03_outputs
#> <project>/03_outputs/plots
#> <project>/03_outputs/tablesEach 01_data domain has a raw/ (inputs) and processed/ (derived) half:
| Domain | Holds | Produced by |
|---|---|---|
1a_shapefiles |
WHO polio GDB layers → spatial_global_adm{0,1,2}.qs2
|
process_spatial() (2b) |
1b_population/worldpop |
annual rasters → extracted adm2 tables | 2a download + 2b extract |
1b_population/polis_pop |
raw_population.qs2 → cleaned denominators |
get_polis_data() + clean_pop()
|
1c_polis |
raw_*.qs2 → polished_*.qs2 (+ checks/, cache/) |
get_polis_data() + run_pipeline()
|
1d_vaccination |
coverage rasters (optional downstream models) | your own scripts |
Pass domains = to scaffold a subset (e.g. c("shapefiles", "polis")).
The wired .Rprofile
The generated .Rprofile is the project manifest — the data-directory roots are defined once as the single source of truth, and cfg is built from them, so the scripts and the cleaners share exactly the same paths:
cat(readLines(file.path(root, ".Rprofile")), sep = "\n")
#> # .Rprofile -- generated by polished::init_polis_pipeline()
#> # The data-directory roots below are the single source of truth shared by `cfg`
#> # and the 02_scripts/. Edit `regions` / `start_year` / `pop_years` /
#> # `pop_source` to taste.
#>
#> if (file.exists("renv/activate.R")) source("renv/activate.R")
#>
#> if (
#> requireNamespace("polished", quietly = TRUE) &&
#> requireNamespace("here", quietly = TRUE)
#> ) {
#> shapes_raw <- here::here("01_data", "1a_shapefiles", "raw")
#> shapes_proc <- here::here("01_data", "1a_shapefiles", "processed")
#> wp_raw <- here::here("01_data", "1b_population", "worldpop", "raw")
#> wp_proc <- here::here("01_data", "1b_population", "worldpop", "processed")
#> pop_raw <- here::here("01_data", "1b_population", "polis_pop", "raw")
#> pop_proc <- here::here("01_data", "1b_population", "polis_pop", "processed")
#> polis_raw <- here::here("01_data", "1c_polis", "raw")
#> polis_proc <- here::here("01_data", "1c_polis", "processed")
#>
#> cfg <- polished::polis_config(
#> start_year = 2015,
#> regions = c("EMRO", "AFRO"),
#> pop_years = 2010:2027,
#> pop_source = "reconciled",
#> shape = file.path(shapes_proc, "spatial_global_adm2.qs2"),
#> # clean_pop() consumes the adm2-by-year tables 2b extracts from the
#> # WorldPop rasters (it also accepts raster directories directly).
#> worldpop = list(
#> all = file.path(wp_proc, "global_worldpop_total_pop.qs2"),
#> u5 = file.path(wp_proc, "global_worldpop_u5_pop.qs2"),
#> u15 = file.path(wp_proc, "global_worldpop_u15_pop.qs2")
#> ),
#> inputs = list(
#> afp = file.path(polis_raw, "raw_afp.qs2"),
#> hum_spec = file.path(polis_raw, "raw_hum_spec.qs2"),
#> es = file.path(polis_raw, "raw_es.qs2"),
#> activity = file.path(polis_raw, "raw_activity.qs2"),
#> subactivity = file.path(polis_raw, "raw_sub_activity.qs2"),
#> lqas = file.path(polis_raw, "raw_lqas.qs2"),
#> im = file.path(polis_raw, "raw_im.qs2"),
#> population = file.path(pop_raw, "raw_population.qs2")
#> ),
#> output_dir = polis_proc,
#> cache_dir = file.path(polis_proc, "cache")
#> )
#> }The four knobs you passed to init_polis_pipeline() are interpolated into the polis_config() call, so editing them later is just editing the .Rprofile:
-
regions— scopes the surveillance streams (AFP/ES/SIA/…). Population is foundational and stays global regardless. -
start_year— earliest onset/collection year retained. -
pop_years— the population/WorldPop window. -
pop_source— which valueclean_pop()chooses as<age>_pop:"reconciled"(POLIS, with WorldPop fallback on missing/implausible values),"polis"(POLIS only), or"worldpop"(WorldPop only). All three of_pop/_pop_polis/_pop_wpare always kept, so this only changes the default column — you can pick a different denominator downstream without re-running.
Because cfg is built outside any if (interactive()) guard (and behind a requireNamespace() check), it is available both in RStudio and under Rscript, so the starter scripts run headless.
The starter scripts
list.files(file.path(root, "02_scripts"))
#> [1] "2a_download_data.R" "2b_process_data.R"2a_download_data.R pulls every input: the POLIS population reference, the seven POLIS surveillance tables (get_polis_data()), and the WorldPop rasters (sntutils::download_worldpop*). Only missing files are (re)fetched. The raw WHO polio GDB shapefile layers are the one thing it does not download — drop those into 01_data/1a_shapefiles/raw/ yourself.
2b_process_data.R runs in three sections, in order:
-
Spatial —
process_spatial()cleans the GDB layers tospatial_global_adm2.qs2(everything else keys off it). -
WorldPop — sums the annual rasters to adm2 tables (cached on disk; re-extracted only if missing), which
clean_pop()then consumes. -
POLIS —
run_pipeline(cfg = cfg)cleans every stream (AFP / ES / SIA / LQAS / IM / virus / indicators and population viaclean_pop()) and writespolished_*outputs pluschecks_*workbooks.
Running it end to end
# 0. drop the WHO polio GDB layers into 01_data/1a_shapefiles/raw/
# 1. download every input
source("02_scripts/2a_download_data.R")
# 2. process: shapefile -> worldpop extract -> run_pipeline over all streams
source("02_scripts/2b_process_data.R")
# 3. read a country / period slice of the cleaned outputs back
afg <- load_polished(country = "AFG")
names(afg)load_polished() defaults output_dir to the active config, so once the .Rprofile has run no path argument is needed — just the filter.
Reproducible package versions with renv
Pass renv = TRUE to wire the project for renv, so everyone installs the same package versions:
init_polis_pipeline("my_project", regions = "EMRO", renv = TRUE)That runs renv::scaffold() — writing renv/activate.R, a starter renv.lock, and a single clean autoloader line in the generated .Rprofile (source("renv/activate.R"), no duplicate). The reproducibility loop is then:
- install the packages the scripts need (
polished,sntutils,sf,qs2,terra,exactextractr, …); -
renv::snapshot()— pins those exact versions intorenv.lock(renv finds them from thepkg::funcalls in2a/2band the.Rprofile); - commit
renv.lock; - a collaborator runs
renv::restore()to reproduce the library exactly.
With renv = FALSE (the default) the .Rprofile still carries a guarded autoloader — if (file.exists("renv/activate.R")) source("renv/activate.R") — so the project is renv-ready: run renv::init() yourself later and nothing else in the scaffold changes.
Conventions baked in
-
Idempotent. Re-running never clobbers an edited
.Rprofileor script; existing files are kept unless you passoverwrite = TRUE. Directory creation is always safe to repeat. -
.heresentinel. A.heremarker is written sohere::here()in the.Rprofileresolves to the project root even before it is a git repo or RStudio project. -
Sensitive raw data stays out of git. The generated
.gitignoreexcludes01_data/**/raw/— raw POLIS tables are individual-level line-lists (and large), so they should not be committed. Re-download them with2ainstead. -
One source of truth for paths. The
.Rprofiledefines the roots; the scripts andcfgreference them, so there is never a second, drifting copy.
For a lighter, single-stream workspace (no .Rprofile, no scripts) see init_polis_project() and the End-to-end pipeline article.
