PolicyEngine / PolicyEngine/microcosm

UK bus fares: impute journeys from NTS microdata so the DfT receipts targets are not level-set by the stage (#890 follow-up)

Open
#930 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
0
Forks
4
Avg merge
1d 3h
Merged PRs (30d)
94

Description

Why

#890 (PR-T #904, PR-S #927) sets the level of bus_fare_spending at the UK spine's lcfs stage by raking user households to the DfT BUS05ai receipts (London, England outside London), Scottish Transport Statistics passenger revenue and Translink receipts, and then binds the same facts as calibration targets. The rake exists because the LCFS diary records a fortnight and cannot say who rides a bus in a year (13 % of diary households have a positive fare against the NTS household user share of 74 %), so a take-up style incidence draw from NTS0313/NTS0621 and a positive-regime fill supply the incidence and the rake supplies the level. That makes the design-weight fit of the bus targets near-tautological: the stage and the calibration bind the same number (Vahid's review of #927; María's ruling 2026-09-15 keeps rake and target together as the interim and asks for this follow-up).

What

Impute local-bus use from the National Travel Survey microdata rather than inferring it from the fortnight diary:

  • Donor: NTS microdata (UK Data Service SN 5340, special licence; seven-day travel diaries with region, age, household income, car availability and journeys by mode), pinned and cleaned like the other licensed donors.
  • Stage: impute bus journeys per person (or per household) onto the FRS spine by QRF on the predictors both sides share (region, age, household income, num_vehicles), with identity-keyed draws and the usual support clip and receipts.
  • Pricing: journeys times the published average revenue per journey by area (DfT BUS05ai receipts over BUS01 journeys, both in Chronicle 474a0ae; Scotland and Northern Ireland from their own receipts and journey counts), so bus_fare_spending is journeys × fare and no longer level-set to the receipts.
  • Consequence: the DfT/devolved receipts targets bound in #904 then constrain something the stage did not set; the #890 rake (rake_to_vendored_facts on bus_fare_spending) retires; the incidence draw retires with it (NTS microdata carries incidence). Bus support (bus_subsidy_spending, ETB) is unchanged by this issue.
  • Diagnostics to keep: ONS 07.3.2 (household spend on road passenger transport, GBP 8.0bn CY2024 / 8.6bn CY2025, vendored in ons_household_expenditure_facts.json) as a recorded upper-bound check (it covers coach and taxi as well as bus).

Also considered (recorded in the #927 thread)

  • Rake shape only: NTS9904 (trips by region), NTS0702 (trips by car availability) and BUS01 journeys by local authority would let the existing rake spread receipts across nine regions or by car ownership instead of London / outside-London by income quintile; improves the shape, not the tautology.
  • A different calibration total: ONS 07.3.2 is a broader concept and can only be a check; TfL fare revenue is a company report.

Acceptance

A stage twin whose design-weight bus fare totals sit within the 25 % fence of the #904 targets without a rake, receipts as in experiments/890-fuel-bus-energy-receipts.md, and the #904 targets left as they are. Licensed inputs only through the pinned-donor seam; no publisher number typed into the tree (Chronicle rows via vendored_rows).

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at the pinned-donor seam and the existing rake_to_vendored_facts handling for bus_fare_spending; read experiments/890-fuel-bus-energy-receipts.md and Chronicle 474a0ae for the vendored inputs. Add the NTS microdata stage and pricing path using vendored_rows, while leaving #904 targets unchanged. Done means design-weight totals fall within the 25% fence without a rake and the ONS 07.3.2 upper-bound diagnostic remains recorded.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
data-engineering
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.