Repository navigation
Draw each cloned household's Output Area from its own FRS region - #517
Merged
Merged
Conversation
clone_and_assign sampled a household's OA by population from anywhere in its country, then wrote the OA's region, LA and constituency onto the household without touching `region`. In release 1.57.4, 88.5% of English households (86% of English weight) carry an OA in a different region from the one the FRS recorded, and the median English LA draws 93% of its weight from households surveyed in another region. policyengine.py groups households by `constituency_code_oa` / `la_code_oa` for its constituency and local-authority impacts and for area-scoped simulations, so every English area was reporting an England-wide mix. assign_random_geography now takes the households' regions and samples within the region (Wales and Scotland are one region each; a household with no region below the country falls back to its country). Collision avoidance draws from the same region. A region with no OAs in the crosswalk, a region that contradicts the country, or an unrecognised region value raises. Called without regions it draws exactly as before: seed 42 reproduces the released `oa_code` column. Adds Hypothesis to the dev extras for the property tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
21 of 52 tasks
From the independent review of f8b21d3: - A region that holds every OA of its country (Wales, Scotland) now shares the country's stratum, so mixed known and UNKNOWN households draw exactly as a country-only call. Before, the two strata consumed the RNG in a different household order. With one clone, Welsh and Scottish households keep exactly their released OAs. - clone_and_assign derives each household's country with the sampler's own region normaliser, so bytes, ONS codes and untidy text no longer give a contradictory country (an unrecognised region now raises instead of silently counting as England). - The built-dataset test checks known regions exactly and households with no region below the country against England, instead of failing them. - New tests: uniform draws in a zero-population region (the mutant that survived), the review's mixed UNKNOWN/London counterexample, and untidy regions through clone_and_assign. The property strategies now generate UNKNOWN in every country, regions as names/codes/bytes/untidy text, and countries the crosswalk does not cover. - Docs: create_datasets.py exports the long geography weights; it does not call publish_local_h5s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review nit: create_datasets.py defaults to 10 clones (2 with TESTING=1), and PE_UK_DATA_OA_CLONES overrides it; push.yaml and pull_request.yaml set it to 1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis
marked this pull request as ready for review
October 4, 2026 10:43
Contributor
Author
|
Hand-off to the UK hub (owning session fba13616)
|
Contributor
Author
|
Queued in release PR #544 for the 10/8 uk-data batch. It lands only on Max's go (d833). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
clone_and_assigngives each cloned household an Output Area sampled by population from anywhere in its country. It then writes that OA'sregion_code_oa,la_code_oaandconstituency_code_oaonto the household and leavesregionalone. An English household's OA could therefore sit in any English region, whatever region the FRS recorded.Measured on the released enhanced FRS 2024-25 (aggregates only):
Wales and Scotland were unaffected, because each is one region in the crosswalk. Northern Ireland has no crosswalk rows, so its 5,659 households get no OA, before and after this PR.
Who reads these columns
origin/main6a9c878):ConstituencyImpact,LocalAuthorityImpactand the constituency/LA entries ofbuild_uk_region_registry(RowFilterStrategy) all group household rows byconstituency_code_oa/la_code_oa, weighted byhousehold_weight.compute_longwise_uk_geography_impactsdoes the grouping.simulation_output_geographic.py) calls the same function onla_code_oa.write_long_geography_weightsruns increate_datasets.pyand writeslocal_geography_weights.csv.gzfrom these columns.matrix_builder.pyandpublish_local_h5s.pyread them, but nothing in the build calls either.db/etl.pyreads only the crosswalk.create_constituency_target_matrix,create_local_authority_target_matrix) use a country mask and never read the OA columns.assign_household_geography,constrain_to_region=Trueby default) and is unaffected.So the bias lands in the area outputs: every English constituency's and LA's figure was built from an England-wide mix of households.
Fix
assign_random_geographytakeshousehold_regions(FRS region names or crosswalk region codes) and samples each household's OA, population-weighted, from its own region.UNKNOWNregion falls back to the country.PE_UK_DATA_OA_CLONES=1).clone_and_assignpasseshousehold.regionand derives each household's country with the same region normaliser the sampler uses (names, ONS codes, bytes, untidy text). An unrecognised region raises instead of silently counting as England.oa_code(checked on 1.57.4) andla_code_oaandconstituency_code_oa(checked on 1.56.16) exactly.hypothesisto the dev extras for the property tests.Invariants (
tests/test_oa_region_assignment.py)Hypothesis property tests over generated crosswalks (1-6 regions, zero-population OAs and strata, countries present or absent), households whose region arrives as a name, ONS code, bytes or untidy text,
UNKNOWNregions in every country, 1-4 clones and any seed:UNKNOWNregion stays in its country.clone_and_assignpreserves total household weight and each source household's weight.regionis never rewritten.UNKNOWNhouseholds shuffled among known ones.Example tests cover:
UNKNOWN/empty/None/NaNregions;UNKNOWNand a London household where London is England's only region);clone_and_assign.Checks on the real crosswalk:
test_built_dataset_oa_region_matches_frs_regionasserts invariant 1 on the enhanced FRS that CI builds: exact for known regions, English for households with no region below the country. It ran and passed in this PR'sTestjob atf8b21d3.Mutation check (head
c842656): all seven mutants are killed. Each row names the first failing test.clone_and_assignstops passingregion(the original bug)clone_and_assignpropertyclone_and_assignclone_and_assignpropertyUNKNOWNread as LondonEffect on outputs
Method
compute_longwise_uk_geography_impacts(6a9c878).f8b21d3sampler.c842656draws identically on the households of both releases (seeds 42, 0 and 7 checked), so the results hold for the current head.Households counted in each region's LAs vs that region's own households
Baseline mean household net income, £/year, released seed:
After the fix, the households in a region's LAs are exactly that region's households, in every draw.
Expected area outputs, mean over 200 draws (English areas)
Correlation of each area's 200-draw mean average household income change with its region's own figure:
External check (released seed)
Each LA's modelled mean household net income was correlated with its ONS net income before housing costs (FYE 2020, the mean of its MSOA estimates; the file the LA calibration targets use). Across 317 LAs the correlation rises from 0.04 to 0.23.
What this does not fix
The FRS records nothing finer than region, so after this PR an area's expected figure is its region's figure, not its own.
Release builds give each household one OA (
PE_UK_DATA_OA_CLONES=1), so an LA has a median of about 100 sample households and a constituency about 70. A single area's figure moves by about £77 to £115 (standard deviation across draws) for these reforms. That is larger than the gaps between regions.This PR removes the systematic error, not that noise. Comparing these outputs with the area-calibrated weight matrices this repo still builds is a separate follow-up.
Review
f8b21d3): REQUEST CHANGES, with three findings and a surviving mutant, all addressed inc842656.UNKNOWNhouseholds were mixed. Fixed by sharing the stratum.UNKNOWNfallback. Fixed.clone_and_assignand the sampler read untidy region values differently. Fixed with one normaliser.origin/mainexactly: 80 cases on the committed crosswalk and 100 on a synthetic one.c842656): APPROVE WITH NITS, with two documentation nits fixed in1c5b20c(docs only).c842656.1c5b20c): APPROVE. It checked the clone-count text againstcreate_datasets.py, both workflows and the crosswalk.Release
Merging to
mainrunspush.yaml, which builds, uploads and tags a data release. This PR waits for the batched uk-data release rather than merging on its own.axiom: n/a: data-pipeline geography, no policy rule changes.
🤖 Generated with Claude Code