Today, Chancellor of the Exchequer Jeremy Hunt announced the Government's Spring Budget 2024. This budget includes a variety of reforms, delineated by the Treasury here.
In this post, PolicyEngine will model the budgetary, societal, and distributional impacts of the budget's reforms to National Insurance, the High Income Child Benefit Charge, and the fuel duty. To view the impacts of these reforms upon society in PolicyEngine's interactive reform modeling tool, visit this link.
Spring Budget 2024 reforms
The Spring 2024 Budget includes reforms to various different segments of the tax code. Of these, PolicyEngine will be modelling the following in this article:National Insurance Contributions (NICs)
- Lowering the main rate of Class 1 NICs from 10p to 8p
- Cutting the main rate of Class 4 self-employed NICs from 8p to 6p
High Income Child Benefit Charge
- Increasing the income threshold for the charge from £60,000 to £80,000
- Increasing the charge's taper range from £60,000 to £80,000
Fuel Duty
- Extending the fuel duty's 5p rate cut for 12 months
- Forgoing the fiscal year 2024-2025 RPI increase for the fuel duty
When modelling these reforms, PolicyEngine assumes they take effect on 1 January 2024 and evaluates their impacts over calendar year 2024, differing slightly from Treasury's estimates, which are aligned with the fiscal calendar.
Other reforms
The Chancellor's Spring Budget 2024 also codifies a number of reforms not included in the PolicyEngine model, such as a freeze on the alcohol duty, changes to the non-domiciled taxation regime, and a duty on vaping products. These reforms will not be modelled in this article.Reform outcomes
Table 1 below presents PolicyEngine's estimate of each policy reform provision's key household, budgetary, and societal impact metrics. Cumulatively, the reforms are modelled to cost HM Treasury a total of £14.1 billion for calendar year 2024 and raise the net income of 77.4% of British households, particularly those with higher pre-tax income. The policies also present ambiguous impacts upon the poverty rate, while raising inequality by 0.4%.Table 1: Household and societal impacts of each Spring Budget 2023 reform provision
from policyengine_uk import Microsimulation
from policyengine_uk.model_api import *
reform = {
"gov.hmrc.fuel_duty.petrol_and_diesel": {
"year:2024:5": 0.5295,
},
"gov.hmrc.income_tax.charges.CB_HITC.phase_out_start": {
"year:2024:5": 60_000,
},
"gov.hmrc.income_tax.charges.CB_HITC.phase_out_rate": {
"year:2024:5": 0.05,
},
"gov.hmrc.national_insurance.class_1.rates.employee.main": {
"year:2024:5": 0.08,
},
"gov.hmrc.national_insurance.class_4.rates.main": {
"year:2024:5": 0.06,
},
}
import pandas as pd
DataFrame with columns: [provision name, provision cost in 2024, percent better off, provision poverty impact in 2024, inequality impact in 2024]
reforms = []
fuel_duty_freeze = {
"gov.hmrc.fuel_duty.petrol_and_diesel": {
"year:2024:5": 0.5295,
}
}
cb_hitc_cut = {
"gov.hmrc.income_tax.charges.CB_HITC.phase_out_start": {
"year:2024:5": 60_000,
},
"gov.hmrc.income_tax.charges.CB_HITC.phase_out_rate": {
"year:2024:5": 0.05,
},
}
ni_class_1_cut = {
"gov.hmrc.national_insurance.class_1.rates.employee.main": {
"year:2024:5": 0.08,
},
}
ni_class_4_cut = {
"gov.hmrc.national_insurance.class_4.rates.main": {
"year:2024:5": 0.06,
},
}
complete_reform = {
**fuel_duty_freeze,
**cb_hitc_cut,
**ni_class_1_cut,
**ni_class_4_cut,
}
reforms = [
fuel_duty_freeze,
cb_hitc_cut,
ni_class_1_cut,
ni_class_4_cut,
complete_reform,
]
provisions = [
"Fuel duty freeze",
"CB HITC cut",
"NI class 1 cut",
"NI class 4 cut",
"Combined",
]
baseline = Microsimulation()
def get_impacts(reform_dict: dict) -> dict:
reform = Reform.from_dict(reform_dict, country_id="uk")
reformed = Microsimulation(reform=reform)
budgetary_impact = (
baseline.calculate("household_net_income", period=2024).sum()
- reformed.calculate("household_net_income", 2024).sum()
)
poverty_baseline = baseline.calculate(
"in_poverty", map_to="person", period=2024
).mean()
poverty_reform = reformed.calculate(
"in_poverty", map_to="person", period=2024
).mean()
poverty_rate_change = (
poverty_reform - poverty_baseline
) / poverty_baseline
gini_baseline = baseline.calculate(
"equiv_household_net_income", map_to="person", period=2024
).gini()
gini_reform = reformed.calculate(
"equiv_household_net_income", map_to="person", period=2024
).gini()
gini_rate_change = (gini_reform - gini_baseline) / gini_baseline
hnet_reformed = reformed.calculate(
"household_net_income", period=2024, map_to="person"
)
hnet_baseline = baseline.calculate(
"household_net_income", period=2024, map_to="person"
)
percent_better_off = (hnet_reformed > hnet_baseline).mean()
# Poverty headcount relative change
# Inequality gini index relative change
# Percent better off
return dict(
budgetary_impact=budgetary_impact,
reform_id=reform.api_id,
poverty_rate_change=poverty_rate_change,
gini_rate_change=gini_rate_change,
percent_better_off=percent_better_off,
)
budgetary_impacts = []
reform_ids = []
poverty_rate_changes = []
gini_rate_changes = []
percentages_better_off = []
for i in range(len(provisions)):
impacts = get_impacts(reforms[i])
budgetary_impacts.append(impacts["budgetary_impact"])
reform_ids.append(impacts["reform_id"])
poverty_rate_changes.append(impacts["poverty_rate_change"])
gini_rate_changes.append(impacts["gini_rate_change"])
percentages_better_off.append(impacts["percent_better_off"])
df = pd.DataFrame(
{
"Reform": provisions,
"Budgetary impact": budgetary_impacts,
"Percent gaining": percentages_better_off,
"Poverty change": poverty_rate_changes,
"Inequality change": gini_rate_changes,
"PolicyEngine": reform_ids,
}
)
df["Budgetary impact"] = df["Budgetary impact"].apply(
lambda x: f"{x/1e9:+,.1f}"
)
df["PolicyEngine"] = df["PolicyEngine"].apply(
lambda x: f"#{x}"
)
df["Percent gaining"] = df["Percent gaining"].apply(lambda x: f"{x:+.1%}")
df["Poverty change"] = df["Poverty change"].apply(lambda x: f"{x:+.1%}")
df["Inequality change"] = df["Inequality change"].apply(lambda x: f"{x:+.1%}")
from IPython.display import Markdown
Markdown(df.to_markdown(index=False))
<IPython.core.display.Markdown object>
reformed = Microsimulation()
Budgetary impacts
The PolicyEngine microsimulation model projects that the six reforms covered in this article will cost a combined £14.1 billion in 2024. This is only slightly higher than the Treasury's projected cost of £13.7 billion for the same six reforms in fiscal year 2024-2025. Some of the variance in these projections are attributable to HM Treasury's inclusion of behavioural responses to policy reforms, as well as Treasury's modelling over a the fiscal year, as opposed to the calendar year.
Table 2 below presents PolicyEngine's broader estimates over 2024, 2025, and 2026, as compared to HM Treasury's estimates over the corresponding fiscal years (2024-2025, etc.) for the six policies investigated in this article.
from policyengine_uk import Microsimulation
from policyengine_uk.model_api import *
import pandas as pd
DataFrame with columns: [provision name, provision cost in 2024, percent better off, provision poverty impact in 2024, inequality impact in 2024]
rows = ["PolicyEngine", "HM Treasury", "Difference"]
years = ["2024", "2025", "2026"]
baseline = Microsimulation()
Assign values for HM Treasury from report
treasury_values_bn = {
"2024": {
"ni_class_1_cut": -9.360,
"ni_class_4_cut": -0.710,
"cb_hitc_cut": -0.540,
"fuel_duty_freeze": -3.090,
},
"2025": {
"ni_class_1_cut": -9.295,
"ni_class_4_cut": -0.850,
"cb_hitc_cut": -0.635,
"fuel_duty_freeze": -0.820,
},
"2026": {
"ni_class_1_cut": -9.490,
"ni_class_4_cut": -0.735,
"cb_hitc_cut": -0.640,
"fuel_duty_freeze": -0.830,
},
}
treasury_totals_bn = {}
for year in treasury_values_bn:
treasury_totals_bn[year] = sum(treasury_values_bn[year].values())
Get impacts of entire policy reform for each year and place into dict
def get_budget_impact_year(reform_dict: dict, year: str) -> float:
reform = Reform.from_dict(reform_dict, country_id="uk")
reformed = Microsimulation(reform=reform)
budgetary_impact = (
baseline.calculate("household_net_income", period=year).sum()
- reformed.calculate("household_net_income", year).sum()
)
return budgetary_impact
pe_totals_bn = {}
for year in years:
pe_totals_bn[year] = get_budget_impact_year(complete_reform, year) / 1e9
Calculate difference between values
diff_totals_bn = {}
for year in years:
diff_totals_bn[year] = treasury_totals_bn[year] - pe_totals_bn[year]
chart_data = []
for i in range(len(years)):
chart_data.append([])
chart_data[i].append(pe_totals_bn[years[i]])
chart_data[i].append(treasury_totals_bn[years[i]])
chart_data[i].append(diff_totals_bn[years[i]])
df = pd.DataFrame(
{
"Estimates": rows,
"2024 (£bn)": chart_data[0],
"2025 (£bn)": chart_data[1],
"2026 (£bn)": chart_data[2],
}
)
df["2024 (£bn)"] = df["2024 (£bn)"].apply(lambda x: f"{x:+,.1f}")
df["2025 (£bn)"] = df["2025 (£bn)"].apply(lambda x: f"{x:+,.1f}")
df["2026 (£bn)"] = df["2026 (£bn)"].apply(lambda x: f"{x:+,.1f}")
Note - I am unsure how to format the sole percentage row
from IPython.display import Markdown
Markdown(df.to_markdown(index=False))
<IPython.core.display.Markdown object>
Distributional impacts
The six modeled reforms of the Spring Budget 2023 would increase the net income of households by 1.2% on average. Under this reform, top-earning deciles would disproportionately benefit. Decile 9, the biggest beneficiary in relative terms, would experience a 1.9% gain in disposable income, while deciles 1 through 3 receive between 0.3% and 0.4%. In absolute terms, this equates to a £46 increase in post-tax income for earners in the lowest decile and a £1,402 increase for those in the top decile (£1,386 for those in decile 9). Similarly, 99.6% of earners in decile 9 and 77.4% of earners in decile 10 experience an increase in net income under these reforms, while 36.2% of earners in the lowest decile gain.from policyengine_uk import Simulation
from policyengine_core.reforms import Reform
from policyengine_core.periods import instant
def modify_parameters(parameters):
parameters.gov.hmrc.fuel_duty.petrol_and_diesel.update(
start=instant("2024-01-01"), stop=instant("2028-12-31"), value=0.5295
)
parameters.gov.hmrc.income_tax.charges.CB_HITC.phase_out_rate.update(
start=instant("2024-01-01"), stop=instant("2028-12-31"), value=0.05
)
parameters.gov.hmrc.income_tax.charges.CB_HITC.phase_out_start.update(
start=instant("2024-01-01"), stop=instant("2028-12-31"), value=60000
)
parameters.gov.hmrc.national_insurance.class_1.rates.employee.main.update(
start=instant("2024-01-01"), stop=instant("2028-12-31"), value=0.08
)
parameters.gov.hmrc.national_insurance.class_4.rates.main.update(
start=instant("2024-01-01"), stop=instant("2028-12-31"), value=0.06
)
return parameters
class reform(Reform):
def apply(self):
self.modify_parameters(modify_parameters)
single_situation = {
"people": {"you": {"age": {"2024": 40}}},
"benunits": {"your immediate family": {"members": ["you"]}},
"households": {
"your household": {
"members": ["you"],
"BRMA": {"2024": "MAIDSTONE"},
"local_authority": {"2024": "MAIDSTONE"},
"region": {"2024": "LONDON"},
}
},
"axes": [
[{"name": "employment_income", "count": 200, "min": 0, "max": 200000}]
],
}
married_situation = {
"people": {
"you": {"age": {"2024": 40}},
"your partner": {"age": {"2024": 40}},
"your first child": {"age": {"2024": 10}},
"your second child": {"age": {"2024": 10}},
},
"benunits": {
"your immediate family": {
"members": [
"you",
"your partner",
"your first child",
"your second child",
]
}
},
"households": {
"your household": {
"members": [
"you",
"your partner",
"your first child",
"your second child",
],
"BRMA": {"2024": "MAIDSTONE"},
"local_authority": {"2024": "MAIDSTONE"},
"region": {"2024": "LONDON"},
}
},
"axes": [
[{"name": "employment_income", "count": 200, "min": 0, "max": 200000}]
],
}
baseline_single_simulation = Simulation(situation=single_situation)
reform_single_simulation = Simulation(
reform=reform,
situation=single_situation,
)
baseline_married_simulation = Simulation(situation=married_situation)
reform_married_simulation = Simulation(
reform=reform,
situation=married_situation,
)
Household impacts
For a single person with only employment income, the six reforms evaluated in this article produce a benefit if they earn at least £12,750, producing a maximum benefit of £754 if the individual earns £50,270 per year. This assumes the individual consumes no fuel impacted by the fuel duty freeze; if the same person were to spend the UK average of £1,176 in 2024 on petrol, they would save a further £96, no matter what their pre-tax income.from policyengine_core.charts import *
single_diff = pd.DataFrame(
{
"Net income": (
reform_single_simulation.calculate("household_net_income", 2024)
- baseline_single_simulation.calculate(
"household_net_income", 2024
)
),
"Employment income": reform_single_simulation.calculate(
"employment_income", 2024
),
}
)
fig = px.line(
single_diff,
y="Net income",
x="Employment income",
color_discrete_sequence=[BLUE],
title="Change to household net income by employment income for a single person",
).update_layout(
xaxis_title="Employment income",
yaxis_title="Change to net income",
yaxis_tickformat=",.0f",
yaxis_tickprefix="£",
xaxis_tickformat=",.0f",
xaxis_tickprefix="£",
showlegend=False,
)
format_fig(fig)
For the same hypothetical person, if their income falls within the range specified above, the tax reforms analysed here would yield a 2% drop in their marginal tax rate; all others would see no change. View this household in the interactive PolicyEngine tax calculator here.
For a married couple with two children, the impacts of this reform package are slightly more complext. Again, these reforms would produce no increase to post-tax income until the head of household earns £12,750 per year, after which the families net income will rise, until reaching a peak of £2,874 when the head makes £60,000. After this point, the post-tax benefit drops as the High Income Child Benefit Charge phases in, until the end of its taper at £80,000, at which all households see a net income increase of £754. These figures do not include the impact of the fuel duty freeze. View this household in the interactive PolicyEngine tax calculator here.
married_diff = pd.DataFrame(
{
"Net income": (
reform_married_simulation.calculate("household_net_income", 2024)
- baseline_married_simulation.calculate(
"household_net_income", 2024
)
),
"Employment income": reform_married_simulation.calculate(
"employment_income", 2024, map_to="household"
),
}
)
fig = px.line(
married_diff,
y="Net income",
x="Employment income",
color_discrete_sequence=[BLUE],
title="Change to household net income by employment income for a married couple with two children",
).update_layout(
xaxis_title="Employment income",
yaxis_title="Change to net income",
yaxis_tickformat=",.0f",
yaxis_tickprefix="£",
xaxis_tickformat=",.0f",
xaxis_tickprefix="£",
)
format_fig(fig)