Use the IndexNumR package for chained and multilateral indices
Why this matters for PBS/P&D work: every headline inflation number you report — CPI, SPI, WPI — is an index number. If you can’t defend the formula, you can’t defend the number.
Why not just average prices?
A naive “average price change” mixes up two things that must be kept separate:
Price change — did wheat get more expensive?
Weight — how much wheat does a typical household actually buy?
A price index is a weighted average of price relatives, where the weights come from expenditure shares in a reference period.
Note
This is exactly what PBS does in the CPI: fix a basket (from HIES expenditure data), then track how much it would cost over time.
Standard references (cite these in any workshop or report):
ILO, IMF, OECD, UN, World Bank (2020). Consumer Price Index Manual: Concepts and Methods. IMF.
Balk, B.M. (2008). Price and Quantity Index Numbers. Cambridge University Press.
Diewert, W.E. (1976). “Exact and Superlative Index Numbers.” Journal of Econometrics, 4(2), 115–145.
Illustrative dataset
Structured like a PBS CPI bulletin extract: commodity groups, base-period (p0, q0) and current-period (p1, q1) prices/quantities.
Note: this is illustrative sample data built to mirror PBS bulletin structure for teaching — not published PBS figures. Swap in a real bulletin extract for the actual workshop run.
PBS-specific version of this audit: check that commodity descriptions (not numeric codes) match across periods — codes are unstable across the 2007-08 → 2015-16 base-year change.
Laspeyres price index
Fixes the base-period basket (q0) and asks: what would it cost today?
Understates inflation: assumes households had already substituted away from goods that got relatively more expensive
Rarely published in real time — needs current-period quantities, which usually arrive with a lag (e.g. from HIES)
Laspeyres vs Paasche — the substitution gap
bind_rows(tibble(index ="Laspeyres", value = laspeyres$L),tibble(index ="Paasche", value = paasche$P))
# A tibble: 2 × 2
index value
<chr> <dbl>
1 Laspeyres 113.
2 Paasche 113.
Important
Laspeyres ≥ Paasche whenever quantities and prices move in opposite directions (the usual case) — this gap is the substitution bias, formalised by Diewert (1976).
Neither is “wrong” — they answer different questions. This is why a superlative index (below) is preferred when both weight sets are available.
Fisher Ideal Index
The geometric mean of Laspeyres and Paasche — splits the difference symmetrically.
\[
F = \sqrt{L \times P}
\]
fisher <-sqrt(laspeyres$L * paasche$P)fisher
[1] 113.0914
Satisfies more of the axiomatic “index number tests” (time reversal, factor reversal) than either component alone
Used by the US BEA for chain-weighted GDP; recommended in the CPI Manual (2020) as a superlative index
Törnqvist Index
A weighted geometric mean of price relatives, using average expenditure shares across the two periods.
Commodity codes are unstable. PBS revised the CPI basket and codes at the 2007-08 → 2015-16 base-year change. Always join across periods on description text, not numeric codes — then audit with anti_join() to catch anything that fails to match.
Units differ across sources. WFP price bulletins mix KG, 20KG, 500G and Litre units for the same commodity. Reconcile to a common unit before any price averaging, or the index is meaningless.
Provincial coverage gaps. Islamabad sometimes appears unlabeled (e.g. as a bare code) in provincial breakdowns — check factor levels with as_factor() before aggregating by province.
# The pattern to teach every time, before any join:intersect(names(pbs_period_a), names(pbs_period_b))anti_join(pbs_period_a, pbs_period_b, by ="commodity_description")
Chained indices
Fixed-base indices drift as the basket ages. Chaining links period-to-period movements instead of comparing everything back to one distant base.
# Three periods of Laspeyres link relatives (illustrative)link_relatives <-tibble(period =2:4, link_index =c(103.2, 101.8, 104.5))link_relatives |>mutate(chained_index =100*cumprod(link_index /100))
Trade-off: chaining tracks current consumption patterns more closely, but chain drift can appear if prices oscillate (e.g. seasonal food items) rather than trend.
Using IndexNumR
For real multi-period, multi-product panels, don’t hand-roll the loops — IndexNumR (White) implements Laspeyres, Paasche, Fisher, Törnqvist, and GEKS multilateral indices.