Every EFO revises the previous forecast. The OBR’s Forecast Evaluation Report and IFS Green Budget routinely decompose those revisions into three components:
- Policy - measures announced at the fiscal event itself.
- Classifications and one-offs - reclassifications by ONS, valuation adjustments, court rulings.
- Underlying - everything else, mostly changes to the macroeconomic determinants (real GDP, inflation, employment).
The OBR publishes this decomposition as the Forecast
Revisions Database (FRD). obr exposes it through
get_forecast_revisions(). This vignette walks through how
to read it and how it pairs with the wide forecast panel for
forecast-evaluation work.
The chunks that download data are shown as code only.
The decomposition
library(obr)
rev <- get_forecast_revisions(unit = "gbp_bn")
unique(rev$component)
# "Total"
# "Policy"
# "of which: receipts"
# "of which: spending"
# "Classifications and one-offs"
# "Underlying"
# "of which: receipts"
# "of which: debt interest"
# "of which: non-interest spending"Total for a given (forecast_date,
fiscal_year) cell equals the sum of Policy +
Classifications and one-offs + Underlying (up
to rounding). The of which: rows further decompose Policy
and Underlying.
Top-level revisions only
For the standard “what changed?” chart, filter to top-level components.
top <- rev[rev$component %in% c(
"Total", "Policy",
"Classifications and one-offs", "Underlying"
), ]
# Most recent vintage's revision for each fiscal year
latest <- max(top$forecast_date)
top[top$forecast_date == latest, ]Plot this with ggplot2’s
geom_col(position = "stack") and you reproduce the OBR’s
“drivers of revision” chart.
Per cent of GDP
Pass unit = "pct_gdp" for the same decomposition
expressed as a share of GDP. This is the right scaling for comparing
revisions across fiscal events with materially different nominal-GDP
levels.
rev_pct <- get_forecast_revisions(unit = "pct_gdp")Pairing with the forecast panel
For forecast-evaluation work it helps to see the levels alongside the
revisions. obr_forecast_panel() pivots the Historical
Forecasts Database from long to wide: rows are forecast vintages
(chronologically ordered), columns are fiscal years.
panel <- obr_forecast_panel("PSNB")
# Every PSNB forecast for 2027-28
panel[, c("forecast_date", "2027-28")]Each row of the panel is one EFO; each column is a fiscal year. Reading down a column gives you the time-series of forecasts the OBR made for the same target year - exactly the input to a Diebold-Mariano test or a forecast-error decomposition.
Worked example: PSNB 2024-25 across the full vintage history
panel <- obr_forecast_panel("PSNB")
# Forecast value for FY 2024-25 across every vintage
psnb_2425 <- panel[, c("forecast_date", "2024-25")]
psnb_2425 <- psnb_2425[!is.na(psnb_2425[["2024-25"]]), ]
psnb_2425For each vintage that forecast 2024-25 (March 2022 onward, when 2024-25 entered the five-year horizon), you get one row. The first vintage forecast PSNB at around GBP 37bn; by November 2025, the last forecast before the outturn arrives, the projection had reached around GBP 150bn. The FRD attributes most of that drift to the underlying component (weaker tax base) with a smaller contribution from policy.
Provenance for cited results
For a paper or report, cite the vintage and MD5 of the underlying file so readers can reproduce.
prov <- obr_provenance(rev)
sprintf("OBR Forecast Revisions Database, vintage %s. File MD5: %s. Retrieved %s.",
prov$vintage,
prov$file_md5,
format(prov$retrieved, "%Y-%m-%d"))Two researchers running the same code six months apart on the same
vintage will see different retrieved timestamps but
identical file_md5 values - which is the audit-trail
property OBR analysts care about.