Skip to contents

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_2425

For 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.