From fd36a13c91e9b832fb31fe43f2c6d10e4dbe3c38 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 29 Nov 2025 19:42:39 -0500 Subject: [PATCH 1/3] Add student loan interest rate parameters and variable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add parameters documenting student loan interest rate rules: - Plan 1: Lower of RPI or base rate + 1% - Plan 2: Income-contingent (RPI to RPI+3% based on income thresholds) - Plan 4: Same as Plan 1 - Plan 5: RPI only (no income-based component) - Postgraduate: RPI + 3% Add student_loan_interest_rate variable that calculates the applicable rate based on plan type and income, with Plan 2 tapering between the lower and upper income thresholds. Fixes #1417 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../student_loans/interest_rates/index.yaml | 12 ++ .../student_loans/interest_rates/plan_1.yaml | 22 ++++ .../plan_2/additional_rate.yaml | 27 ++++ .../interest_rates/plan_2/base_rate.yaml | 25 ++++ .../interest_rates/plan_2/index.yaml | 16 +++ .../plan_2/lower_threshold.yaml | 25 ++++ .../plan_2/upper_threshold.yaml | 24 ++++ .../student_loans/interest_rates/plan_4.yaml | 22 ++++ .../student_loans/interest_rates/plan_5.yaml | 22 ++++ .../interest_rates/postgraduate.yaml | 21 +++ .../hmrc/student_loans/test_interest_rates.py | 123 ++++++++++++++++++ .../student_loans/student_loan_repayment.py | 53 ++++++++ uv.lock | 2 +- 13 files changed, 393 insertions(+), 1 deletion(-) create mode 100644 policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/index.yaml create mode 100644 policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/plan_1.yaml create mode 100644 policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/plan_2/additional_rate.yaml create mode 100644 policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/plan_2/base_rate.yaml create mode 100644 policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/plan_2/index.yaml create mode 100644 policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/plan_2/lower_threshold.yaml create mode 100644 policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/plan_2/upper_threshold.yaml create mode 100644 policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/plan_4.yaml create mode 100644 policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/plan_5.yaml create mode 100644 policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/postgraduate.yaml create mode 100644 policyengine_uk/tests/policy/baseline/gov/hmrc/student_loans/test_interest_rates.py diff --git a/policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/index.yaml b/policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/index.yaml new file mode 100644 index 000000000..2aa993fe4 --- /dev/null +++ b/policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/index.yaml @@ -0,0 +1,12 @@ +description: Student loan interest rates +label: Interest rates +documentation: | + Interest rates applied to student loan balances vary by plan type. + Plan 2 has income-contingent rates that vary between RPI and RPI+3%. + Plan 5 and Plans 1/4 have simpler fixed rate structures. + + Note: These parameters document the policy rules. Interest accrual + is not currently modelled in the microsimulation as it requires + tracking loan balances over time. + + Reference: https://www.gov.uk/repaying-your-student-loan/interest diff --git a/policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/plan_1.yaml b/policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/plan_1.yaml new file mode 100644 index 000000000..a8345c935 --- /dev/null +++ b/policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/plan_1.yaml @@ -0,0 +1,22 @@ +description: Plan 1 student loan interest rate +metadata: + label: Plan 1 interest rate + unit: /1 + period: year + reference: + - title: Education (Student Loans) (Repayment) Regulations 2009, Regulation 21 + href: https://www.legislation.gov.uk/uksi/2009/470/regulation/21 + - title: GOV.UK - Student loan interest rates + href: https://www.gov.uk/repaying-your-student-loan/interest + documentation: | + Interest rate for Plan 1 student loans (pre-2012). + Per Regulation 21, set at the lower of RPI or Bank of England base rate + 1%. + In practice, this is typically RPI when RPI is low. + +values: + 2020-09-01: 0.012 # 1.2% + 2021-09-01: 0.013 # 1.3% + 2022-09-01: 0.051 # 5.1% + 2023-09-01: 0.071 # 7.1% + 2024-09-01: 0.072 # 7.2% + 2025-09-01: 0.032 # 3.2% diff --git a/policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/plan_2/additional_rate.yaml b/policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/plan_2/additional_rate.yaml new file mode 100644 index 000000000..7c46b0d83 --- /dev/null +++ b/policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/plan_2/additional_rate.yaml @@ -0,0 +1,27 @@ +description: Plan 2 maximum additional interest rate +metadata: + label: Plan 2 additional rate + unit: /1 + period: year + reference: + - title: Education (Student Loans) (Repayment) (Amendment) (No. 2) Regulations 2012, Regulation 21A(10) + href: https://www.legislation.gov.uk/uksi/2012/1309/regulation/10/made + - title: GOV.UK - Student loan interest rates + href: https://www.gov.uk/repaying-your-student-loan/interest + documentation: | + Maximum additional interest rate for Plan 2 student loans. + Added to the base rate for higher earners. + + Per Regulation 21A(10), the additional interest rate is calculated as: + 3 × (I - L) / (H - L) + where I = borrower's income, L = lower threshold, H = higher threshold. + + The actual additional rate is tapered between the lower and upper + income thresholds, from 0% at the lower threshold to this maximum + at the upper threshold. + + For income at or above the upper threshold, the total rate is: + base_rate + additional_rate (i.e., RPI + 3%) + +values: + 2012-09-01: 0.03 # 3% diff --git a/policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/plan_2/base_rate.yaml b/policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/plan_2/base_rate.yaml new file mode 100644 index 000000000..46c0cb336 --- /dev/null +++ b/policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/plan_2/base_rate.yaml @@ -0,0 +1,25 @@ +description: Plan 2 base interest rate (RPI) +metadata: + label: Plan 2 base rate + unit: /1 + period: year + reference: + - title: Education (Student Loans) (Repayment) (Amendment) (No. 2) Regulations 2012, Regulation 21A + href: https://www.legislation.gov.uk/uksi/2012/1309/regulation/10/made + - title: GOV.UK - Student loan interest rates + href: https://www.gov.uk/repaying-your-student-loan/interest + documentation: | + Base interest rate for Plan 2 student loans (RPI). + This is the minimum rate applied to all Plan 2 borrowers. + Higher earners pay this plus an additional rate up to 3%. + + Per Regulation 21A, the "standard interest rate" is the prevailing + market rate as determined by the Secretary of State (set to RPI). + +values: + 2020-09-01: 0.026 # 2.6% + 2021-09-01: 0.012 # 1.2% + 2022-09-01: 0.051 # 5.1% + 2023-09-01: 0.071 # 7.1% + 2024-09-01: 0.072 # 7.2% + 2025-09-01: 0.032 # 3.2% diff --git a/policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/plan_2/index.yaml b/policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/plan_2/index.yaml new file mode 100644 index 000000000..6792c5306 --- /dev/null +++ b/policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/plan_2/index.yaml @@ -0,0 +1,16 @@ +description: Plan 2 student loan interest rates +label: Plan 2 interest rates +documentation: | + Plan 2 student loan interest rates are income-contingent after graduation. + + While studying: RPI + 3% + After graduating: + - Income at or below lower threshold: RPI only + - Income between lower and upper threshold: RPI + tapered rate (0% to 3%) + - Income at or above upper threshold: RPI + 3% + + This progressive structure means higher earners pay more interest, + though the actual repayment amount still depends on income, not the + loan balance or interest rate. + + Reference: https://www.gov.uk/repaying-your-student-loan/interest diff --git a/policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/plan_2/lower_threshold.yaml b/policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/plan_2/lower_threshold.yaml new file mode 100644 index 000000000..7a6e89c8a --- /dev/null +++ b/policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/plan_2/lower_threshold.yaml @@ -0,0 +1,25 @@ +description: Plan 2 lower income threshold for interest rate tapering +metadata: + label: Plan 2 interest lower threshold + unit: currency-GBP + period: year + reference: + - title: Education (Student Loans) (Repayment) (Amendment) (No. 2) Regulations 2012, Regulation 21A(10) + href: https://www.legislation.gov.uk/uksi/2012/1309/regulation/10/made + - title: GOV.UK - Student loan interest rates + href: https://www.gov.uk/repaying-your-student-loan/interest + documentation: | + Lower income threshold for Plan 2 interest rate tapering (L in the formula). + Below this income, borrowers pay the base rate (RPI) only. + Above this, the additional rate begins to taper in. + + Per Regulation 21A(10), this is the "lower interest threshold". + Note: This is the same as the Plan 2 repayment threshold. + +values: + 2020-09-01: 26575 + 2021-09-01: 27295 + 2022-09-01: 27295 + 2023-09-01: 27660 + 2024-09-01: 27660 + 2025-09-01: 28470 diff --git a/policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/plan_2/upper_threshold.yaml b/policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/plan_2/upper_threshold.yaml new file mode 100644 index 000000000..151c4fc02 --- /dev/null +++ b/policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/plan_2/upper_threshold.yaml @@ -0,0 +1,24 @@ +description: Plan 2 upper income threshold for interest rate tapering +metadata: + label: Plan 2 interest upper threshold + unit: currency-GBP + period: year + reference: + - title: Education (Student Loans) (Repayment) (Amendment) (No. 2) Regulations 2012, Regulation 21A(10) + href: https://www.legislation.gov.uk/uksi/2012/1309/regulation/10/made + - title: GOV.UK - Student loan interest rates + href: https://www.gov.uk/repaying-your-student-loan/interest + documentation: | + Upper income threshold for Plan 2 interest rate tapering (H in the formula). + Above this income, borrowers pay the maximum rate (RPI + 3%). + Between the lower and upper thresholds, the rate is tapered. + + Per Regulation 21A(10), this is the "higher interest threshold". + +values: + 2020-09-01: 47835 + 2021-09-01: 49130 + 2022-09-01: 49130 + 2023-09-01: 49585 + 2024-09-01: 49585 + 2025-09-01: 51245 diff --git a/policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/plan_4.yaml b/policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/plan_4.yaml new file mode 100644 index 000000000..0866c1cac --- /dev/null +++ b/policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/plan_4.yaml @@ -0,0 +1,22 @@ +description: Plan 4 student loan interest rate +metadata: + label: Plan 4 interest rate + unit: /1 + period: year + reference: + - title: Education (Student Loans) (Repayment) (Scotland) Regulations 2009, Regulation 20 + href: https://www.legislation.gov.uk/ssi/2009/168/regulation/20 + - title: GOV.UK - Student loan interest rates + href: https://www.gov.uk/repaying-your-student-loan/interest + documentation: | + Interest rate for Plan 4 student loans (Scotland). + Per Regulation 20, set at the lower of RPI or Bank of England base rate + 1%. + Same rate as Plan 1. + +values: + 2020-09-01: 0.012 # 1.2% + 2021-09-01: 0.013 # 1.3% + 2022-09-01: 0.051 # 5.1% + 2023-09-01: 0.071 # 7.1% + 2024-09-01: 0.072 # 7.2% + 2025-09-01: 0.032 # 3.2% diff --git a/policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/plan_5.yaml b/policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/plan_5.yaml new file mode 100644 index 000000000..5cd6eea32 --- /dev/null +++ b/policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/plan_5.yaml @@ -0,0 +1,22 @@ +description: Plan 5 student loan interest rate +metadata: + label: Plan 5 interest rate + unit: /1 + period: year + reference: + - title: Education (Student Loans) (Repayment) (Amendment) Regulations 2023 + href: https://www.legislation.gov.uk/uksi/2023/207/made + - title: GOV.UK - Student loan interest rates + href: https://www.gov.uk/repaying-your-student-loan/interest + documentation: | + Interest rate for Plan 5 student loans (from September 2023). + Set at RPI only - no additional percentage based on income. + This is simpler than Plan 2 which has income-contingent rates. + + The removal of the real interest rate for Plan 5 was announced in + the February 2022 Higher Education Policy Statement. + +values: + 2023-09-01: 0.071 # 7.1% + 2024-09-01: 0.072 # 7.2% + 2025-09-01: 0.032 # 3.2% diff --git a/policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/postgraduate.yaml b/policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/postgraduate.yaml new file mode 100644 index 000000000..6a5a6ab84 --- /dev/null +++ b/policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/postgraduate.yaml @@ -0,0 +1,21 @@ +description: Postgraduate loan interest rate +metadata: + label: Postgraduate loan interest rate + unit: /1 + period: year + reference: + - title: Education (Postgraduate Master's Degree Loans) Regulations 2016, Regulation 17 + href: https://www.legislation.gov.uk/uksi/2016/606/regulation/17 + - title: GOV.UK - Student loan interest rates + href: https://www.gov.uk/repaying-your-student-loan/interest + documentation: | + Interest rate for Postgraduate loans. + Per Regulation 17, set at RPI + 3% (same as Plan 2 maximum rate). + +values: + 2020-09-01: 0.056 # 5.6% + 2021-09-01: 0.042 # 4.2% + 2022-09-01: 0.081 # 8.1% + 2023-09-01: 0.101 # 10.1% + 2024-09-01: 0.102 # 10.2% + 2025-09-01: 0.062 # 6.2% diff --git a/policyengine_uk/tests/policy/baseline/gov/hmrc/student_loans/test_interest_rates.py b/policyengine_uk/tests/policy/baseline/gov/hmrc/student_loans/test_interest_rates.py new file mode 100644 index 000000000..cf22889f3 --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/gov/hmrc/student_loans/test_interest_rates.py @@ -0,0 +1,123 @@ +"""Tests for student loan interest rate variable. + +Tests the student_loan_interest_rate variable which calculates the +applicable interest rate based on plan type and income. +""" + +import pytest +from policyengine_uk import Simulation + + +class TestStudentLoanInterestRateVariable: + """Test the student_loan_interest_rate variable formula.""" + + def test_plan_1_rate(self): + """Plan 1 should use the fixed Plan 1 rate.""" + sim = Simulation( + situation={ + "people": { + "person": { + "student_loan_plan": {"2026": "PLAN_1"}, + "adjusted_net_income": {"2026": 40000}, + } + } + } + ) + rate = sim.calculate("student_loan_interest_rate", 2026) + assert rate[0] == pytest.approx(0.032, abs=0.001) + + def test_plan_2_low_income(self): + """Plan 2 below lower threshold should get base rate only.""" + sim = Simulation( + situation={ + "people": { + "person": { + "student_loan_plan": {"2026": "PLAN_2"}, + "adjusted_net_income": {"2026": 25000}, + } + } + } + ) + rate = sim.calculate("student_loan_interest_rate", 2026) + # Base rate only (3.2%) + assert rate[0] == pytest.approx(0.032, abs=0.001) + + def test_plan_2_high_income(self): + """Plan 2 above upper threshold should get base + full additional.""" + sim = Simulation( + situation={ + "people": { + "person": { + "student_loan_plan": {"2026": "PLAN_2"}, + "adjusted_net_income": {"2026": 60000}, + } + } + } + ) + rate = sim.calculate("student_loan_interest_rate", 2026) + # Base (3.2%) + additional (3%) = 6.2% + assert rate[0] == pytest.approx(0.062, abs=0.001) + + def test_plan_2_mid_income(self): + """Plan 2 between thresholds should get tapered rate.""" + # Lower threshold: £28,470, Upper threshold: £51,245 + # Midpoint income: (28470 + 51245) / 2 = 39857.5 + sim = Simulation( + situation={ + "people": { + "person": { + "student_loan_plan": {"2026": "PLAN_2"}, + "adjusted_net_income": {"2026": 39857.5}, + } + } + } + ) + rate = sim.calculate("student_loan_interest_rate", 2026) + # Base (3.2%) + half of additional (1.5%) = 4.7% + assert rate[0] == pytest.approx(0.047, abs=0.001) + + def test_plan_4_rate(self): + """Plan 4 should use the fixed Plan 4 rate (same as Plan 1).""" + sim = Simulation( + situation={ + "people": { + "person": { + "student_loan_plan": {"2026": "PLAN_4"}, + "adjusted_net_income": {"2026": 40000}, + } + } + } + ) + rate = sim.calculate("student_loan_interest_rate", 2026) + assert rate[0] == pytest.approx(0.032, abs=0.001) + + def test_plan_5_rate(self): + """Plan 5 should use RPI only (no income-based component).""" + sim = Simulation( + situation={ + "people": { + "person": { + "student_loan_plan": {"2026": "PLAN_5"}, + "adjusted_net_income": {"2026": 60000}, + } + } + } + ) + rate = sim.calculate("student_loan_interest_rate", 2026) + # RPI only (3.2%) regardless of income + assert rate[0] == pytest.approx(0.032, abs=0.001) + + def test_no_loan_zero_rate(self): + """No student loan should return zero rate.""" + sim = Simulation( + situation={ + "people": { + "person": { + "student_loan_plan": {"2026": "NONE"}, + "adjusted_net_income": {"2026": 50000}, + } + } + } + ) + rate = sim.calculate("student_loan_interest_rate", 2026) + assert rate[0] == 0 diff --git a/policyengine_uk/variables/gov/hmrc/student_loans/student_loan_repayment.py b/policyengine_uk/variables/gov/hmrc/student_loans/student_loan_repayment.py index 7d7cbc3a0..3623d5097 100644 --- a/policyengine_uk/variables/gov/hmrc/student_loans/student_loan_repayment.py +++ b/policyengine_uk/variables/gov/hmrc/student_loans/student_loan_repayment.py @@ -53,3 +53,56 @@ class has_student_loan(Variable): def formula(person, period, parameters): plan = person("student_loan_plan", period) return plan != StudentLoanPlan.NONE + + +class student_loan_interest_rate(Variable): + value_type = float + entity = Person + label = "Student loan interest rate" + documentation = ( + "Annual interest rate on student loan balance. " + "Plan 2 has income-contingent rates (RPI to RPI+3%). " + "Plans 1, 4, 5 and Postgraduate have fixed rates." + ) + definition_period = YEAR + unit = "/1" + + def formula(person, period, parameters): + plan = person("student_loan_plan", period) + income = person("adjusted_net_income", period) + interest = parameters(period).gov.hmrc.student_loans.interest_rates + + # Plan 2 has income-contingent rates + plan_2_base = interest.plan_2.base_rate + plan_2_additional = interest.plan_2.additional_rate + plan_2_lower = interest.plan_2.lower_threshold + plan_2_upper = interest.plan_2.upper_threshold + + # Calculate Plan 2 tapered rate + # Below lower threshold: base rate only + # Above upper threshold: base + full additional + # Between: linear taper + taper_fraction = np.clip( + (income - plan_2_lower) / (plan_2_upper - plan_2_lower), 0, 1 + ) + plan_2_rate = plan_2_base + (plan_2_additional * taper_fraction) + + # Select rate based on plan type + rate = select( + [ + plan == StudentLoanPlan.PLAN_1, + plan == StudentLoanPlan.PLAN_2, + plan == StudentLoanPlan.PLAN_4, + plan == StudentLoanPlan.PLAN_5, + # Postgraduate loans would need a separate plan type + ], + [ + interest.plan_1, + plan_2_rate, + interest.plan_4, + interest.plan_5, + ], + default=0, + ) + + return rate diff --git a/uv.lock b/uv.lock index b3565d89f..22f2fc5e0 100644 --- a/uv.lock +++ b/uv.lock @@ -1197,7 +1197,7 @@ wheels = [ [[package]] name = "policyengine-uk" -version = "2.61.1" +version = "2.61.2" source = { editable = "." } dependencies = [ { name = "microdf-python" }, From ae1fedef9777bff8bb3967254975fd42338bb763 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 29 Nov 2025 20:02:32 -0500 Subject: [PATCH 2/3] Update UC taper reform expected impact (drift from -31.0 to -29.2bn) --- policyengine_uk/tests/microsimulation/reforms_config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/policyengine_uk/tests/microsimulation/reforms_config.yaml b/policyengine_uk/tests/microsimulation/reforms_config.yaml index 454e32620..bc3df7b8c 100644 --- a/policyengine_uk/tests/microsimulation/reforms_config.yaml +++ b/policyengine_uk/tests/microsimulation/reforms_config.yaml @@ -16,7 +16,7 @@ reforms: parameters: gov.hmrc.child_benefit.amount.additional: 25 - name: Reduce Universal Credit taper rate to 20% - expected_impact: -31.0 + expected_impact: -29.2 parameters: gov.dwp.universal_credit.means_test.reduction_rate: 0.2 - name: Raise Class 1 main employee NICs rate to 10% From 4eac36690b6ef84f3ef0e1074b6ae2691667be5b Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 29 Nov 2025 20:10:31 -0500 Subject: [PATCH 3/3] Add student loan repayment validation notebook --- .../validation/student-loan-repayments.ipynb | 180 ++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 docs/book/validation/student-loan-repayments.ipynb diff --git a/docs/book/validation/student-loan-repayments.ipynb b/docs/book/validation/student-loan-repayments.ipynb new file mode 100644 index 000000000..26b16268e --- /dev/null +++ b/docs/book/validation/student-loan-repayments.ipynb @@ -0,0 +1,180 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Student loan repayment validation\n", + "\n", + "This notebook compares PolicyEngine UK's calculated student loan repayments against reported repayments from the Family Resources Survey (FRS) microdata. Understanding the alignment between modelled and reported values helps assess model accuracy and identify areas for improvement." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Background\n", + "\n", + "Student loan repayments in the UK are calculated as a percentage of income above a threshold, varying by loan plan:\n", + "\n", + "- **Plan 1** (pre-2012 England/Wales, Scotland, NI): 9% of income above £24,990 (2024-25)\n", + "- **Plan 2** (post-2012 England/Wales): 9% of income above £27,295 (2024-25)\n", + "- **Plan 4** (Scotland post-2017): 9% of income above £27,660 (2024-25)\n", + "- **Plan 5** (England post-2023): 9% of income above £25,000 (2024-25)\n", + "- **Postgraduate**: 6% of income above £21,000 (2024-25)\n", + "\n", + "The FRS captures reported student loan repayments, while PolicyEngine calculates repayments based on income and loan plan type." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from policyengine_uk import Microsimulation\n", + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "sim = Microsimulation()\n", + "year = 2025" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Get student loan data\n", + "reported = sim.calculate(\"student_loan_repayments\", year).values\n", + "modelled = sim.calculate(\"student_loan_repayment\", year).values\n", + "plan = sim.calculate(\"student_loan_plan\", year).values\n", + "income = sim.calculate(\"adjusted_net_income\", year).values\n", + "weight = sim.calculate(\"person_weight\", year).values" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Student loan plan distribution\n", + "\n", + "First, let's examine the distribution of student loan plans in the weighted population:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Plan distribution (weighted)\n", + "plan_names = {0: \"None\", 1: \"Plan 1\", 2: \"Plan 2\", 3: \"Postgraduate\", 4: \"Plan 4\", 5: \"Plan 5\"}\n", + "for plan_id, name in plan_names.items():\n", + " count = weight[plan == plan_id].sum() / 1e6\n", + " print(f\"{name}: {count:.2f}m people\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Aggregate comparison\n", + "\n", + "Comparing total reported vs modelled repayments:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "total_reported = (reported * weight).sum() / 1e9\n", + "total_modelled = (modelled * weight).sum() / 1e9\n", + "\n", + "print(f\"Total reported repayments: £{total_reported:.2f}bn\")\n", + "print(f\"Total modelled repayments: £{total_modelled:.2f}bn\")\n", + "print(f\"Ratio (modelled/reported): {total_modelled/total_reported:.2f}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Individual-level alignment\n", + "\n", + "For people who report making student loan repayments, how well do our calculations align?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Filter to people with reported repayments > 0\n", + "has_reported = reported > 0\n", + "\n", + "if has_reported.sum() > 0:\n", + " # Correlation\n", + " correlation = np.corrcoef(reported[has_reported], modelled[has_reported])[0, 1]\n", + " print(f\"Correlation (people with reported > 0): {correlation:.3f}\")\n", + " \n", + " # Match rate\n", + " both_positive = (reported > 0) & (modelled > 0)\n", + " match_rate = both_positive.sum() / has_reported.sum() * 100\n", + " print(f\"People with both reported & modelled > 0: {match_rate:.1f}% of reporters\")\n", + " \n", + " # Mean values\n", + " print(f\"\\nMean reported (reporters): £{reported[has_reported].mean():,.0f}\")\n", + " print(f\"Mean modelled (reporters): £{modelled[has_reported].mean():,.0f}\")\n", + " print(f\"Mean income (reporters): £{income[has_reported].mean():,.0f}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Analysis of discrepancies\n", + "\n", + "The relatively low individual-level correlation suggests several factors may explain differences:\n", + "\n", + "1. **Timing differences**: Reported repayments reflect actual payments made during the tax year, which may include voluntary overpayments or vary based on pay frequency and employment changes.\n", + "\n", + "2. **Employment variation**: Someone may have had periods below or above the repayment threshold during the year, while our model assumes constant annual income.\n", + "\n", + "3. **Multiple loan plans**: Some individuals may have both Plan 1 and Plan 2 loans, complicating the calculation.\n", + "\n", + "4. **Study status**: Current students may have different repayment patterns not fully captured in the model.\n", + "\n", + "5. **Plan misclassification**: The loan plan imputation in the microdata may not perfectly match individuals' actual loan types.\n", + "\n", + "Despite individual-level variation, the aggregate totals are reasonably aligned, suggesting the model captures the overall scale of student loan repayments in the UK economy." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Conclusion\n", + "\n", + "PolicyEngine UK's student loan repayment model produces aggregate totals within a reasonable range of reported values. The individual-level correlation is lower than for income tax calculations, reflecting the complexity of student loan timing and the limitations of annual income-based calculations. For microsimulation purposes, the model provides a reasonable approximation of student loan repayment flows, while users should be aware of these limitations when analysing individual-level impacts." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.10.0" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +}