The goal of pipapi is to provide a high level API to the computations and methods that power the Poverty and Inequality Platform (PIP).
World Bank staff who have read access to the PIP data can use the functions from this package directly, without hitting the PIP API.
You can install the development version from GitHub with:
# install.packages("devtools")
devtools::install_github("PIP-Technical-Team/pipapi")The main function from the pipapi package is the pip() function. See
?pip for more information.
In order to use pip() you’ll need to have access to a PIP
data_folder. The folder structure looks like this:
data-folder-root/
├─ _aux/
│ ├─ pop_regions.fst
│ ├─ pop.fst
│ ├─ ...
├─ estimations/
│ ├─ prod_svy_estimation.fst
│ ├─ prod_ref_estimation.fst
├─ survey_data/
│ ├─ survey_1.fst
│ ├─ ...
│ ├─ survey_n.fst# Create a list of look-up tables from the root data folder
lkups <- create_lkups("<data-folder>")Pass the lkups list to the main pip() function to compute poverty
and inequality statistics in your R session.
library(pipapi)
pip(country = "AGO",
year = 2000,
povline = 1.9,
lkup = lkups)
#> region_code country_code reporting_year survey_acronym survey_coverage
#> 1: SSA AGO 2000 HBS national
#> survey_year welfare_type survey_comparability comparable_spell poverty_line
#> 1: 2000.21 consumption 0 2000 1.9
#> headcount poverty_gap poverty_severity mean median mld gini
#> 1: 0.3637448 0.1636806 0.09982393 4.100014 2.593394 0.5125765 0.5195689
#> polarization watts decile1 decile2 decile3 decile4
#> 1: 0.4643401 0.2811239 0.00983246 0.02195307 0.03342455 0.04495307
#> decile5 decile6 decile7 decile8 decile9 decile10
#> 1: 0.05662774 0.07048758 0.08808485 0.1134946 0.158687 0.4024552
#> survey_mean_lcu survey_mean_ppp predicted_mean_ppp cpi cpi_data_level
#> 1: 11.23264 4.100014 NA 0.03385145 national
#> ppp ppp_data_level reporting_pop pop_data_level reporting_gdp
#> 1: 80.9318 national 16395473 national 2195.631
#> gdp_data_level reporting_pce pce_data_level is_interpolated
#> 1: national NA national FALSE
#> is_used_for_aggregation distribution_type estimation_type
#> 1: FALSE micro surveyBecause both PIP data structures and {pipapi} calculation code evolve over time, a single {pipapi} version cannot safely serve every historical vintage in /Data. Instead, the system separates responsibilities across three components to enforce frozen code-data pairings:
| Component | Role | Output / Responsibility |
|---|---|---|
find_pipapi_version |
Evidence harness | Verifies which {pipapi} commit SHA and renv.lock environment reproduce a vintage folder's estimates, producing the coverage.csv evidence table. Used retrospectively once to recover bindings for historical 2022–2026 releases, and used prospectively to record/verify the target {pipapi} environment for new releases. |
{pipschema} |
Offline governance | 1. schema.yml: Classifies every vintage folder in /Data by its structural contents (pre-lineup vs. new-lineup), never by folder-name dates.2. pairing.yml: Joins schema.yml with coverage.csv to freeze an append-only mapping of each release (YYYYMMDD) and its folders to (schema, pipapi_version, pipapi_sha). |
{pipapi} + ITSES-POVERTYSCOREAPI |
Runtime execution | Runs one container per pairing. Each container installs its pinned {pipapi} git tag, mounts /Data read-only, reads /Data/pairing.yml at startup, and loads strictly the vintage folders assigned to its pairing. |
Modern {pipapi} containers (>= 1.5.15) read /Data/pairing.yml at startup so the folder list is driven by the data mount rather than hardcoded inside the Docker image:
vintages <- pipapi::read_pairing_vintages(
pairing_path = "/Data/pairing.yml",
schema = "new-lineup",
pipapi_version = "1.5.15"
)
lkups <- pipapi::create_versioned_lkups(
data_dir = "/Data",
vintage_folders = vintages,
schema = "new-lineup"
)
pipapi::start_api(api_version = "v1", port = 8080, host = "0.0.0.0", lkups = lkups)This startup flow provides three guarantees:
- Strict folder filtering (
vintage_folders/build_vintage_pattern()): Scans and loads only the vintage folders bound to the container's pairing, ignoring all other folders in/Data. - Explicit schema behavior (
schema = "new-lineup"or"pre-lineup"): Replaces the legacy> 2025-05-01folder-date heuristic (use_new_lineup_version()). - Per-vintage fault isolation (
tryCatch): If an individual vintage folder is unreadable or corrupted,create_versioned_lkups()logs acli::cli_warn()and skips that folder instead of aborting container startup (and warns explicitly iflatest_releasefalls back to an older vintage).
| Pairing | Schema | {pipapi} Tag (SHA) |
Releases Served | Startup Mechanism |
|---|---|---|---|---|
pre-lineup + 1.0.0 |
pre-lineup |
v1.0.0 (f081d28f) |
20220609 (1 folder) |
Frozen 2022 code; main.R passes exact vintage_pattern regex |
pre-lineup + 1.3.22 |
pre-lineup |
v1.3.22 (089c3fc8) |
20220909–20240627 (5 releases, 10 folders) |
Frozen 2024 code; main.R passes exact vintage_pattern regex |
new-lineup + 1.5.15 |
new-lineup |
v1.5.15 (48101230) |
20250930–20260922 (3 releases, 6 folders) |
Dynamic read_pairing_vintages() + schema = "new-lineup" |
When a new PIP data release (YYYYMMDD_*_PROD) is ready, follow Scenario A (standard data release) or Scenario B (new {pipapi} version or new data schema):
Use this when the table/column structure is unchanged (new-lineup) and the current {pipapi} version (1.5.15) requires no code changes.
- Update
{pipschema}contracts:- Run
pipschema::assign_schemas()on the data directory to classify the new folders intoinst/schemas/schema.yml. - Run
pipschema::build_pairing()andpipschema::write_pairing()to append the new release toinst/pairing/pairing.yml(existing bindings remain frozen).
- Run
- Publish to
/Data:- Commit the new vintage folders alongside the updated
schema.ymlandpairing.ymlat the root of the Data repository (ITSES-POVERTYSCORE-DATA) and refresh thecache-v2files if enabled.
- Commit the new vintage folders alongside the updated
- Restart the active container:
- Once synced to
/Dataon the VM, restart theapi-1-5container. On startup,main.Rreads/Data/pairing.yml, discovers the new release, and promotes it tolatest_release. No{pipapi}code change or Docker image rebuild is required.
- Once synced to
Use this when calculation logic in {pipapi} or the vintage folder structure changes, so prior releases stay frozen on their original code.
- Tag
{pipapi}: Commit the code changes in{pipapi}and push an annotated release tag (e.g.,v1.6.0) to GitHub. - Update
{pipschema}& publish to/Data: If the folder structure changed, add the new schema inpipschema(definitions.yml). Runassign_schemas(),build_pairing(), andwrite_pairing()to bind the new release to1.6.0, then copyschema.ymlandpairing.ymlto the root of/Data. - Tag the Azure deploy repo (
ITSES-POVERTYSCOREAPI): On branchrelease/1, updatePIPAPI_COMMITinPIP_CustomImage/DockerfileandPAIRING_PIPAPI_VERSION(andPAIRING_SCHEMAif changed) inPIP_CustomImage/R/main.R, commit, and push tagv1.6.0. - Deploy side-by-side: Start the new
api-1-6container and route the new release to its port innginx, leavingapi-1-5,api-1-3, andapi-1-0untouched.