Add net_wealth variable and mortgage_debt / consumer_debt inputs - #1761
Add net_wealth variable and mortgage_debt / consumer_debt inputs#1761MaxGhenis wants to merge 1 commit into
Conversation
net_wealth is total_wealth less mortgage, consumer, and student-loan debt. Unlike gross total_wealth (property + corporate + savings, never negative), net wealth can be negative when a household's debts exceed its assets, so it ranks the genuinely underwater households into the bottom of the wealth distribution. mortgage_debt and consumer_debt are new household inputs imputed by policyengine-uk-data from the Wealth and Assets Survey. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
vahid-ahmadi
left a comment
There was a problem hiding this comment.
Reviewed. The design is sound and the reasoning in the description is right — gross total_wealth piling ~13% of households at exactly £0 is a real artefact, and netting liabilities is the correct fix. Three things before this lands, one of which is blocking in practice.
The companion PR is closed, so this ships inert
The description says mortgage_debt / consumer_debt are "imputed from the Wealth and Assets Survey by a companion policyengine-uk-data PR". That PR is PolicyEngine/policyengine-uk-data#426, and it was closed on 19 June — two weeks after this one was opened — with:
Migrated as part of the uk-data archive cleanup. The debt variables should be owned by Populace compact UK source stages, with provenance and gates tracked in PolicyEngine/microcosm#145.
So as things stand no dataset will ever populate these inputs, both default to 0, and net_wealth reduces to total_wealth − student_loan_balance. The stated purpose — moving underwater households to the bottom of the distribution and filling the empty lowest wealth decile — does not materialise until populace#145 delivers.
That is not an argument against merging: shipping the variable first is reasonable, and the description already says the defaults are zero until a dataset carries them. But the description points at a dead PR, so anyone reading it will look in the wrong place. Worth updating to reference populace#145, and worth confirming that work is actually live — if it is not, this sits inert indefinitely.
student_loan_balance carries "unknown" semantics, not "zero"
Its own documentation says:
Outstanding student loan balance. When not provided, repayments are left uncapped.
and student_loan_repayment.py:23 reads it as max_(0, ...). So in its existing use, default_value = 0 means no data, and the repayment logic deliberately treats that as "do not cap". Here the same 0 is read as "this household has no student debt".
For households where the balance is genuinely missing rather than genuinely zero, net_wealth will be overstated. Given the whole point is to identify underwater households, that bias runs against the goal. Worth at least a note in the net_wealth documentation, or an explicit decision to treat missing as zero.
Test coverage misses the only non-trivial line
The three cases cover the subtraction, a negative result, and the no-debt case — all on what look like single-person households. But the only line that does anything other than arithmetic is:
student_loan_debt = add(household, period, ["student_loan_balance"])which aggregates a Person-level variable to the household. A two-adult case with a balance on each would pin that, and would catch a regression if the entity or aggregation changed. Everything else in the formula is household-level and cannot really break.
Checked and fine
- Uprating is consistent, which was my main worry given assets and debts are netted.
mortgage_debtandconsumer_debtusegov.economic_assumptions.indices.obr.per_capita.gdp, and every asset component resolves to the same index (main_residence_value,other_residential_property_value,non_residential_property_value,corporate_wealth,savingsall at 1.297 for 2026).residential_property_valueshowsuprating=Nonebut is anaddsaggregate of two uprated components, so it inherits correctly. Assets and debts therefore grow together andnet_wealthwill not drift artificially. student_loan_balanceitself is not uprated, so student debt shrinks in real terms relative to everything else. Small, but it is the one asymmetry in the formula.- Leaving
total_wealthuntouched for the web app's percentage charts is the right call, andquantity_type = STOCKis consistent throughout.
Housekeeping
Last updated 5 June and mergeable currently reports UNKNOWN — it will need a rebase and a fresh CI run before merge. Note CI on dataset-backed jobs was broken between 31 July and 11 August and is now fixed (#1817), so a rerun should be clean.
What
Adds a
net_wealthhousehold variable and themortgage_debtandconsumer_debtinputs that feed it.net_wealth=total_wealth−mortgage_debt−consumer_debt− householdstudent_loan_balance.mortgage_debt(gross outstanding mortgage) andconsumer_debt(non-mortgage, non-student-loan borrowing) are new household inputs.Why
Gross
total_wealth(property + corporate + savings) has no liabilities netted out, so it is never negative. That makes the bottom of the wealth distribution an artefact: ~13% of households own no property/savings and pile up at exactly £0, leaving the lowest wealth decile empty and hiding the genuinely underwater households (debts > assets).net_wealthputs those households where they belong — the bottom of the distribution — so distributional analysis by wealth reflects reality.total_wealthis left unchanged (the web app's percentage wealth-change charts rely on it staying non-negative).Companion
The
mortgage_debt/consumer_debtinputs are imputed from the Wealth and Assets Survey by a companion policyengine-uk-data PR. Until a dataset release carries them, both default to 0 (sonet_wealth==total_wealth).Tests
policyengine_uk/tests/policy/baseline/household/wealth/net_wealth.yamlcovers the subtraction, a negative-net-wealth case, and the no-debt case.