Skip to contents

The OBR’s Economic and Fiscal Outlook contains the official UK fiscal forecast: five-year projections for borrowing, debt, receipts, expenditure, and the underlying economy. obr exposes this through get_efo_fiscal() (Table 6.5 aggregates) and get_efo_economy() (sheets 1.6 labour, 1.7 inflation, 1.14 output gap).

This vignette covers what changed in the v0.4.0 schema and how to use it.

The standard schema

From v0.4.0, every data-fetching function in obr returns the same six columns:

Column Type Values
period character "2024-25", "2025Q1", "2025" (depends on period_type)
period_type character "fiscal_year", "quarter", "calendar_year"
series character The variable name, e.g. "CPI", "Net borrowing"
metric_type character "level", "yoy_pct", "index", "pct", "pct_pts"
value double Numeric value
unit character "gbp_bn", "pct", "pct_pts", "index", "count_k", etc.

get_forecasts() adds forecast_date as a leading column. get_pension_projections() adds scenario_type as a trailing column.

The schema is consistent enough that you can rbind() outputs from different functions and still know what each value means.

Index vs YoY: the v0.4.0 fix

Before v0.4.0, get_efo_economy("inflation") returned CPI / RPI / etc. as series with values in a single value column, with no machine-readable indication of whether a row was a level (index points) or a year-on-year growth rate (per cent). The metric_type and unit columns now make the distinction explicit.

library(obr)

inf <- get_efo_economy("inflation")
table(inf$metric_type, inf$unit)
#         index  pct
#   index   372    0
#   yoy_pct   0 1844

To get just the year-on-year inflation rates:

inf_yoy <- inf[inf$metric_type == "yoy_pct", ]
head(inf_yoy[inf_yoy$series == "CPI", ])

To get just any index series (e.g. GDP deflator if the source publishes it as an index):

inf_idx <- inf[inf$metric_type == "index", ]

The classifier defaults bare names like “CPI” / “RPI” to yoy_pct because the OBR inflation sheet reports them as growth rates by convention. Series matching Index, (2015=100), or similar literal patterns are tagged as index instead. You can override per-row by post-processing the returned frame.

Combining EFO with outturn from PFD

Because the schema is uniform across publications, you can compare a fiscal forecast (EFO) against the realised outturn (PFD) without column mapping.

forecast <- get_efo_fiscal()                # 5-year forecast, gbp_bn
forecast <- forecast[forecast$series == "Net borrowing", ]

outturn <- get_psnb()                       # historical outturn, series = "PSNB", gbp_bn
outturn <- outturn[outturn$period >= "2020-21", ]

# Both have period (fiscal_year), value (gbp_bn), unit. Stack them.
combined <- rbind(
  data.frame(source = "outturn",  outturn[,  c("period", "value", "unit")]),
  data.frame(source = "forecast", forecast[, c("period", "value", "unit")])
)

Comparing two vintages

Pin a vintage explicitly to compare how the OBR’s view changed between fiscal events.

oct24 <- get_efo_fiscal(vintage = "October 2024")
mar26 <- get_efo_fiscal(vintage = "March 2026")

# Net borrowing forecast for 2027-28 from each vintage
oct24[oct24$series == "Net borrowing" & oct24$period == "2027-28", "value"]
mar26[mar26$series == "Net borrowing" & mar26$period == "2027-28", "value"]

See vignette("vintages") for the full vintage layer.

What unit covers

Unit code Meaning Typical series
gbp_bn Pounds sterling, billions PSNB, PSND, TME, receipts forecasts
pct Percentage (rate, share, growth rate) Inflation, unemployment rate, debt/GDP
pct_pts Percentage points Output gap (pp of potential)
index Index level (typically rebased to 100 at a year) CPI Index, GDP deflator (when level)
count_k Count, thousands Incapacity benefit claimants

unit is plain character. It does not enforce arithmetic safety: if you sum a gbp_bn value with a pct value, R will compute it without complaint. The column is there to remind you and to allow programmatic filtering.

Provenance

Every returned object carries provenance metadata describing which OBR publication and vintage produced it.

obr_provenance(get_efo_fiscal())
# $publication: "EFO"
# $vintage:     "March 2026"
# $source_url:  ...
# $retrieved:   timestamp
# $file_md5:    fingerprint of the underlying spreadsheet
# $package_version: obr version

This lets you audit which OBR publication produced any number in your analysis. See vignette("vintages").