# Working with EFO forecasts

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.

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")`.