Validation#
PolicyEngine closely matches most official statistics on income, consumption and wealth.
Show code cell source
from policyengine_uk import Microsimulation
sim = Microsimulation()
# Below figures taken from the 2020-26 UKMOD country report.
UKMOD_CASELOADS = {
"Best Start Grant and Foods": [32, 16, 63, 73, 79, 78, 76],
"Child Benefit (c)": [12226, 12047, 11368, 11136, 11074, 11036, 10928],
"Child Benefit": [6828, 6720, 6421, 6280, 6241, 6217, 6154],
"Child and Working Tax Credits": [1567, 1060, 814, 658, 380, 0, 0],
"Council Tax Reduction (h)": [5876, 5759, 5257, 5301, 5381, 5424, 5451],
"Employment and Support Allowance (ib)": [734, 647, 454, 368, 189, 0, 0],
"Free School Meals": [2940, 2905, 2956, 2978, 3004, 3038, 3092],
"Healthy Start (food)": [444, 429, 337, 340, 357, 350, 348],
"Housing Benefit (h)": [2478, 2185, 2068, 1873, 1561, 1192, 1183],
"Income Support*": [890, 752, 609, 482, 258, 0, 0],
"Jobseeker's Allowance (cb)": [63, 63, 25, 25, 25, 25, 25],
"Pension Credit": [1379, 1252, 1357, 1354, 1354, 1357, 1366],
"School Clothing Grant": [1200, 1162, 1029, 1063, 1105, 1139, 1126],
"Scottish Carer's Allowance Supplement (i)": [44, 44, 52, 52, 52, 52, 52],
"Scottish Child Payment (c)": [0, 35, 170, 181, 189, 193, 189],
"Scottish Child Winter Heating Assistance (c)": [0, 5, 3, 3, 3, 3, 3],
"Sure Start Maternity Grant": [37, 33, 72, 77, 83, 85, 84],
"Universal Credit": [4038, 4437, 4337, 4855, 5746, 6572, 6508],
"Winter Fuel Allowance (h)": [
11998,
11435,
11239,
11239,
11239,
11239,
11239,
],
"Benefit cap (Housing Benefit)": [47, 47, 36, 22, 14, 2, 2],
"Benefit cap (Universal Credit)": [169, 166, 85, 77, 98, 108, 102],
"Income tax (i)": [28797, 29579, 31467, 32495, 32902, 33134, 33396],
"Basic rate (i)": [22815, 23039, 23776, 24076, 24285, 24296, 24326],
"Higher rate (i)": [3652, 4110, 5160, 5457, 5562, 5740, 5942],
"Additional rate (i)": [219, 273, 425, 816, 831, 859, 892],
"NIC Employees (i)": [22074, 22960, 23024, 22953, 22987, 23088, 23195],
"NIC Self employed (i)": [1194, 2271, 2613, 2601, 2613, 2696, 2721],
"NIC Employers (i)": [22504, 23266, 24183, 24380, 24442, 24490, 24534],
}
UKMOD_EXPENDITURE = {
"Best Start Grant and Foods": [12, 6, 28, 41, 45, 42, 42],
"Child Benefit": [11098, 10899, 10588, 11357, 11898, 11874, 11742],
"Child and Working Tax Credits": [8643, 5767, 4703, 4034, 2343, 0, 0],
"Council Tax Reduction": [7558, 7707, 7503, 7867, 8252, 8600, 8949],
"Employment and Support Allowance (ib)": [
5305,
4579,
3330,
2958,
1672,
0,
0,
],
"Free School Meals": [3968, 3909, 4087, 4501, 4757, 4878, 4968],
"Healthy Start (food)": [126, 167, 133, 134, 140, 136, 136],
"Housing Benefit": [11004, 10127, 9712, 8969, 7409, 5471, 5431],
"Income Support*": [3411, 2873, 2452, 2170, 1268, 0, 0],
"Jobseeker's Allowance (cb)": [233, 234, 99, 109, 115, 116, 116],
"Pension Credit": [4370, 3831, 4355, 4768, 5043, 5124, 5258],
"School Clothing Grant": [284, 275, 248, 279, 303, 320, 316],
"Scottish Carer's Allowance Supplement": [30, 31, 25, 28, 29, 30, 30],
"Scottish Child Payment": [0, 24, 210, 399, 435, 446, 434],
"Scottish Child Winter Heating Assistance": [0, 1, 1, 1, 1, 1, 1],
"Sure Start Maternity Grant": [18, 17, 37, 40, 43, 44, 43],
"Universal Credit": [38122, 39612, 37137, 43736, 53918, 61863, 61072],
"Winter Fuel Allowance": [1990, 1916, 1903, 1903, 1903, 1903, 1903],
"Benefit cap (Housing Benefit)": [190, 308, 116, 95, 46, 1, 1],
"Benefit cap (Universal Credit)": [687, 646, 260, 230, 337, 362, 319],
"Income tax": [149405, 164580, 205344, 235819, 240776, 246492, 254345],
"Basic rate": [60906, 63871, 68330, 71151, 71947, 72366, 73341],
"Higher rate": [61991, 69490, 89864, 88729, 90784, 93507, 97261],
"Additional rate": [21054, 25370, 39717, 65776, 67352, 69688, 72670],
"NIC Employees": [55301, 60554, 65668, 61679, 62486, 63547, 64992],
"NIC Self-employed": [3773, 4152, 5887, 5493, 5591, 5698, 5827],
"NIC Employers": [78664, 86786, 103914, 103760, 105282, 107312, 110101],
}
import numpy as np
for variable in UKMOD_EXPENDITURE:
UKMOD_EXPENDITURE[variable] = np.array(
[value / 1e3 for value in UKMOD_EXPENDITURE[variable]]
)
# UKMOD values are from 2020-26
import pandas as pd
def estimate(variable: str):
return [
sim.calculate(variable, period=year, map_to="household").sum() / 1e9
for year in range(2023, 2026)
]
from policyengine_uk.data.datasets.frs.calibration.loss import (
calibration_parameters,
)
from policyengine_core.parameters import get_parameter
def official_estimate(parameter: str):
return (
np.array(
[
get_parameter(calibration_parameters, parameter)(
f"{year}-01-01"
)
for year in range(2023, 2026)
]
)
/ 1e9
)
df = pd.DataFrame()
data = {
"Income Tax": {
"Official": official_estimate(
"programs.income_tax.budgetary_impact.by_country.UNITED_KINGDOM"
),
"PolicyEngine": estimate("income_tax"),
"UKMOD": UKMOD_EXPENDITURE["Income tax"][3:6],
},
"National Insurance": {
"Official": official_estimate(
"programs.total_NI.budgetary_impact.UNITED_KINGDOM"
),
"PolicyEngine": estimate("total_NI"),
"UKMOD": UKMOD_EXPENDITURE["NIC Employees"][3:6]
+ UKMOD_EXPENDITURE["NIC Self-employed"][3:6]
+ UKMOD_EXPENDITURE["NIC Employers"][3:6],
},
"Universal Credit": {
"Official": official_estimate(
"programs.universal_credit.budgetary_impact.GREAT_BRITAIN"
),
"PolicyEngine": estimate("universal_credit"),
"UKMOD": UKMOD_EXPENDITURE["Universal Credit"][3:6],
},
"Child Benefit": {
"Official": official_estimate(
"programs.child_benefit.budgetary_impact.UNITED_KINGDOM"
),
"PolicyEngine": estimate("child_benefit"),
"UKMOD": UKMOD_EXPENDITURE["Child Benefit"][3:6],
},
"Tax Credits": {
"Official": official_estimate(
"programs.tax_credits.budgetary_impact.UNITED_KINGDOM"
),
"PolicyEngine": estimate("tax_credits"),
"UKMOD": UKMOD_EXPENDITURE["Child and Working Tax Credits"][3:6],
},
"Housing Benefit": {
"Official": official_estimate(
"programs.housing_benefit.budgetary_impact.GREAT_BRITAIN"
),
"PolicyEngine": estimate("housing_benefit"),
"UKMOD": UKMOD_EXPENDITURE["Housing Benefit"][3:6],
},
"Pension Credit": {
"Official": official_estimate(
"programs.pension_credit.budgetary_impact.GREAT_BRITAIN"
),
"PolicyEngine": estimate("pension_credit"),
"UKMOD": UKMOD_EXPENDITURE["Pension Credit"][3:6],
},
}
# DF columns: year, model, variable, value
for variable in data:
for model in data[variable]:
for i, value in enumerate(data[variable][model]):
df = pd.concat(
[
df,
pd.DataFrame(
{
"year": 2023 + i,
"model": model,
"variable": variable,
"value": value,
"error": False,
# Text should be: "[model] estimates aggregate [variable] to be [value] in [year]."
"text": f"{model} estimates aggregate {variable}<br> to be £{value:.1f} billion <br>in {2023 + i}.",
},
index=[0],
),
]
)
for i, value in enumerate(data[variable][model]):
if model != "Official":
df = pd.concat(
[
df,
pd.DataFrame(
{
"year": 2023 + i,
"model": model + " (error)",
"variable": variable,
"value": value - data[variable]["Official"][i],
"error": True,
# Text should be: "[model]'s estimate of aggregate [variable] is [value] billion [higher/lower] than the official estimate in [year]."
"text": f"{model}'s estimate of aggregate {variable}<br> is £{value - data[variable]['Official'][i]:.1f} billion {'higher' if value > data[variable]['Official'][i] else 'lower'} than the official <br>estimate in {2023 + i}.",
},
index=[0],
),
]
)
import plotly.express as px
from policyengine_core.charts import format_fig, BLUE_COLOUR_SCALE, GRAY
fig = (
px.bar(
df[~df.error][::-1].sort_values(["year", "value"]),
animation_frame="year",
y="variable",
x="value",
color="model",
barmode="group",
orientation="h",
custom_data=["text"],
color_discrete_map={
"Official": GRAY,
# "Official (error)": GRAY,
"PolicyEngine": BLUE_COLOUR_SCALE[2],
# "PolicyEngine (error)": BLUE_COLOR_SCALE[3],
"UKMOD": BLUE_COLOUR_SCALE[0],
# "UKMOD (error)": BLUE_COLOR_SCALE[0],
},
)
.update_layout(
title="Errors against official estimates",
xaxis_title="Budgetary impact (£bn)",
yaxis_title="",
legend_title="",
xaxis_range=[-10, 300],
)
.update_traces(hovertemplate="%{customdata[0]}")
)
fig = format_fig(fig)
fig
---------------------------------------------------------------------------
ModuleNotFoundError Traceback (most recent call last)
Cell In[1], line 103
96 def estimate(variable: str):
97 return [
98 sim.calculate(variable, period=year, map_to="household").sum() / 1e9
99 for year in range(2023, 2026)
100 ]
--> 103 from policyengine_uk.data.datasets.frs.calibration.loss import (
104 calibration_parameters,
105 )
106 from policyengine_core.parameters import get_parameter
109 def official_estimate(parameter: str):
ModuleNotFoundError: No module named 'policyengine_uk.data.datasets.frs.calibration.loss'