diff --git a/gcp/social_card_tags.py b/gcp/social_card_tags.py
index c2cd8d5cb..fca3d8723 100644
--- a/gcp/social_card_tags.py
+++ b/gcp/social_card_tags.py
@@ -65,11 +65,15 @@ def get_image(path: str, query_params: dict, social_cards: dict = {}):
if len(list(image_folder.glob(f"{filename}_with_title.*"))) > 0:
filename = f"{filename}_with_title"
# React builds the image filename as 'original_name.hash.extension'. Find it by searching for the original name
-
+
image_files = list(image_folder.glob(f"{filename}.*"))
if len(image_files) > 0:
# In order of preference: file ending with .png, then jpeg, then jpg
- for extension in [".png", ".jpeg", ".jpg"]: # Twitter doesn't show the title so we include alternative versions.
+ for extension in [
+ ".png",
+ ".jpeg",
+ ".jpg",
+ ]: # Twitter doesn't show the title so we include alternative versions.
for image_file in image_files:
if image_file.name.endswith(extension):
return f"https://policyengine.org/static/media/{image_file.name}"
@@ -115,7 +119,7 @@ def add_social_card_tags(
title = get_title(path, query_params)
description = get_description(path, query_params)
image_url = get_image(path, query_params, social_cards)
- print('Image URL:', image_url)
+ print("Image URL:", image_url)
# Use beautiful soup to add the tags for Twitter and Facebook
soup = BeautifulSoup(html_file, "html.parser")
diff --git a/src/posts/northern-ireland-feasibility.ipynb b/src/posts/northern-ireland-feasibility.ipynb
index b56f7c815..4a6dc59c4 100644
--- a/src/posts/northern-ireland-feasibility.ipynb
+++ b/src/posts/northern-ireland-feasibility.ipynb
@@ -75,8 +75,7 @@
"]\n",
"\n",
"responses[\"Text\"] = [\n",
- " f\"{responses.iloc[i]['Percent']:.0f}%\"\n",
- " for i in range(len(responses))\n",
+ " f\"{responses.iloc[i]['Percent']:.0f}%\" for i in range(len(responses))\n",
"]\n",
"\n",
"fig = px.bar(\n",
@@ -113,7 +112,7 @@
" orientation=\"h\",\n",
" yanchor=\"bottom\",\n",
" y=-0.3,\n",
- " )\n",
+ " ),\n",
")\n",
"\n",
"format_fig(fig).update_layout(\n",
@@ -178,16 +177,25 @@
"from policyengine_core.charts import format_fig\n",
"from policyengine_core.model_api import *\n",
"import pandas as pd\n",
+ "\n",
"# Silence warnings\n",
"import warnings\n",
+ "\n",
"warnings.filterwarnings(\"ignore\")\n",
"import plotly.io as pio\n",
+ "\n",
"pio.renderers.default = \"notebook\"\n",
"\n",
+ "\n",
"def filter_to_ni(sim: Microsimulation):\n",
" weights = sim.calculate(\"household_weight\")\n",
" country = sim.calculate(\"country\")\n",
- " sim.set_input(\"household_weight\", 2023, np.where(country == \"NORTHERN_IRELAND\", weights, 0))\n",
+ " sim.set_input(\n",
+ " \"household_weight\",\n",
+ " 2023,\n",
+ " np.where(country == \"NORTHERN_IRELAND\", weights, 0),\n",
+ " )\n",
+ "\n",
"\n",
"baseline = Microsimulation()\n",
"filter_to_ni(baseline)\n",
@@ -198,16 +206,24 @@
" child_amount: float = 200 * 12,\n",
" senior_amount: float = 100 * 12,\n",
"):\n",
- " in_ni = baseline.calculate(\"country\", map_to=\"person\") == \"NORTHERN_IRELAND\"\n",
+ " in_ni = (\n",
+ " baseline.calculate(\"country\", map_to=\"person\") == \"NORTHERN_IRELAND\"\n",
+ " )\n",
" age = baseline.calculate(\"age\")\n",
" retired = baseline.calculate(\"is_SP_age\")\n",
" num_adults = ((age >= 18) * (~retired))[in_ni].sum()\n",
" num_children = ((age >= 16) * (age < 18))[in_ni].sum()\n",
" num_seniors = (retired)[in_ni].sum()\n",
- " return adult_amount * num_adults + child_amount * num_children + senior_amount * num_seniors\n",
+ " return (\n",
+ " adult_amount * num_adults\n",
+ " + child_amount * num_children\n",
+ " + senior_amount * num_seniors\n",
+ " )\n",
+ "\n",
"\n",
"from policyengine_uk.model_api import *\n",
"\n",
+ "\n",
"def ubi_ni_reform(\n",
" personal_allowance: float = 12_570,\n",
" higher_rate_threshold: float = 50_270,\n",
@@ -217,20 +233,37 @@
" child_bi: float = 0,\n",
" adult_bi: float = 0,\n",
" senior_bi: float = 0,\n",
- "\n",
") -> Reform:\n",
" TIME_PERIOD = \"year:2023:10\"\n",
- " return Reform.from_dict({\n",
- " \"gov.hmrc.income_tax.rates.uk[0].rate\": { TIME_PERIOD: basic_rate },\n",
- " \"gov.hmrc.income_tax.rates.uk[1].rate\": { TIME_PERIOD: higher_rate },\n",
- " \"gov.hmrc.income_tax.rates.uk[2].rate\": { TIME_PERIOD: additional_rate },\n",
- " \"gov.hmrc.income_tax.rates.uk[1].threshold\": { TIME_PERIOD: float(higher_rate_threshold) },\n",
- " \"gov.hmrc.income_tax.allowances.personal_allowance.amount\": { TIME_PERIOD: float(personal_allowance) },\n",
- " \"gov.contrib.ubi_center.basic_income.amount.by_age.child\": { TIME_PERIOD: child_bi / 52 },\n",
- " \"gov.contrib.ubi_center.basic_income.amount.child_min_age\": { TIME_PERIOD: 16 },\n",
- " \"gov.contrib.ubi_center.basic_income.amount.by_age.working_age\": { TIME_PERIOD: adult_bi / 52 },\n",
- " \"gov.contrib.ubi_center.basic_income.amount.by_age.senior\": { TIME_PERIOD: senior_bi / 52 },\n",
- " }, country_id=\"uk\")\n",
+ " return Reform.from_dict(\n",
+ " {\n",
+ " \"gov.hmrc.income_tax.rates.uk[0].rate\": {TIME_PERIOD: basic_rate},\n",
+ " \"gov.hmrc.income_tax.rates.uk[1].rate\": {TIME_PERIOD: higher_rate},\n",
+ " \"gov.hmrc.income_tax.rates.uk[2].rate\": {\n",
+ " TIME_PERIOD: additional_rate\n",
+ " },\n",
+ " \"gov.hmrc.income_tax.rates.uk[1].threshold\": {\n",
+ " TIME_PERIOD: float(higher_rate_threshold)\n",
+ " },\n",
+ " \"gov.hmrc.income_tax.allowances.personal_allowance.amount\": {\n",
+ " TIME_PERIOD: float(personal_allowance)\n",
+ " },\n",
+ " \"gov.contrib.ubi_center.basic_income.amount.by_age.child\": {\n",
+ " TIME_PERIOD: child_bi / 52\n",
+ " },\n",
+ " \"gov.contrib.ubi_center.basic_income.amount.child_min_age\": {\n",
+ " TIME_PERIOD: 16\n",
+ " },\n",
+ " \"gov.contrib.ubi_center.basic_income.amount.by_age.working_age\": {\n",
+ " TIME_PERIOD: adult_bi / 52\n",
+ " },\n",
+ " \"gov.contrib.ubi_center.basic_income.amount.by_age.senior\": {\n",
+ " TIME_PERIOD: senior_bi / 52\n",
+ " },\n",
+ " },\n",
+ " country_id=\"uk\",\n",
+ " )\n",
+ "\n",
"\n",
"def get_revenue_slow_estimate(\n",
" personal_allowance: float,\n",
@@ -242,7 +275,7 @@
" reformed = Microsimulation(\n",
" reform=ubi_ni_reform(\n",
" personal_allowance=personal_allowance,\n",
- " higher_rate_threshold = 50_270 + 12_570 - personal_allowance,\n",
+ " higher_rate_threshold=50_270 + 12_570 - personal_allowance,\n",
" basic_rate=0.2 + basic_rate_addition,\n",
" higher_rate=0.4 + higher_rate_addition,\n",
" additional_rate=0.45 + additional_rate_addition,\n",
@@ -253,7 +286,11 @@
" )\n",
" filter_to_ni(baseline)\n",
" filter_to_ni(reformed)\n",
- " return baseline.calculate(\"household_net_income\").sum() - reformed.calculate(\"household_net_income\").sum()\n",
+ " return (\n",
+ " baseline.calculate(\"household_net_income\").sum()\n",
+ " - reformed.calculate(\"household_net_income\").sum()\n",
+ " )\n",
+ "\n",
"\n",
"def get_revenue_quick_estimate(\n",
" personal_allowance: float,\n",
@@ -265,16 +302,33 @@
" weight = income.weights\n",
" add_rate_income = np.maximum(0, income - 125_140)\n",
" higher_rate_income = np.maximum(0, income - 50_270) - add_rate_income\n",
- " basic_rate_income_b = np.maximum(0, income - higher_rate_income - add_rate_income - 12_570)\n",
- " basic_rate_income_r = np.maximum(0, income - higher_rate_income - add_rate_income - personal_allowance)\n",
+ " basic_rate_income_b = np.maximum(\n",
+ " 0, income - higher_rate_income - add_rate_income - 12_570\n",
+ " )\n",
+ " basic_rate_income_r = np.maximum(\n",
+ " 0, income - higher_rate_income - add_rate_income - personal_allowance\n",
+ " )\n",
" basic_rate_increased_tax = (\n",
" basic_rate_income_r * (0.2 + basic_rate_addition)\n",
" - basic_rate_income_b * 0.2\n",
" )\n",
- " higher_rate_increased_tax = baseline.calculate(\"higher_rate_earned_income\").values * higher_rate_addition\n",
- " additional_rate_increased_tax = baseline.calculate(\"add_rate_earned_income\").values * additional_rate_addition\n",
- " increased_tax = basic_rate_increased_tax + higher_rate_increased_tax + additional_rate_increased_tax\n",
- " return (increased_tax * weight).sum() * 0.83 # Rough benefit response ratio\n",
+ " higher_rate_increased_tax = (\n",
+ " baseline.calculate(\"higher_rate_earned_income\").values\n",
+ " * higher_rate_addition\n",
+ " )\n",
+ " additional_rate_increased_tax = (\n",
+ " baseline.calculate(\"add_rate_earned_income\").values\n",
+ " * additional_rate_addition\n",
+ " )\n",
+ " increased_tax = (\n",
+ " basic_rate_increased_tax\n",
+ " + higher_rate_increased_tax\n",
+ " + additional_rate_increased_tax\n",
+ " )\n",
+ " return (\n",
+ " increased_tax * weight\n",
+ " ).sum() * 0.83 # Rough benefit response ratio\n",
+ "\n",
"\n",
"def solve_rates(\n",
" adult_ubi: float = 0,\n",
@@ -287,16 +341,24 @@
" basic_rate_addition: float = 0,\n",
" ) -> float:\n",
" if use_quick_estimate:\n",
- " adjusted_revenue = get_revenue_quick_estimate(personal_allowance, basic_rate_addition, basic_rate_addition * higher_rate_ratio, basic_rate_addition * additional_rate_ratio)\n",
+ " adjusted_revenue = get_revenue_quick_estimate(\n",
+ " personal_allowance,\n",
+ " basic_rate_addition,\n",
+ " basic_rate_addition * higher_rate_ratio,\n",
+ " basic_rate_addition * additional_rate_ratio,\n",
+ " )\n",
" else:\n",
- " adjusted_revenue = get_revenue_slow_estimate(personal_allowance, basic_rate_addition, basic_rate_addition * higher_rate_ratio, basic_rate_addition * additional_rate_ratio)\n",
- " ubi_cost = get_ubi_cost(\n",
- " adult_ubi, \n",
- " adult_ubi * 0.5,\n",
- " adult_ubi\n",
- " )\n",
- " return ubi_cost * 0.75 - adjusted_revenue # Assume 25% of the UBI is paid for by Westminster\n",
- " \n",
+ " adjusted_revenue = get_revenue_slow_estimate(\n",
+ " personal_allowance,\n",
+ " basic_rate_addition,\n",
+ " basic_rate_addition * higher_rate_ratio,\n",
+ " basic_rate_addition * additional_rate_ratio,\n",
+ " )\n",
+ " ubi_cost = get_ubi_cost(adult_ubi, adult_ubi * 0.5, adult_ubi)\n",
+ " return (\n",
+ " ubi_cost * 0.75 - adjusted_revenue\n",
+ " ) # Assume 25% of the UBI is paid for by Westminster\n",
+ "\n",
" return bisect(adjusted_reform_cost, -1, 1, xtol=1e-3)\n",
"\n",
"\n",
@@ -310,20 +372,33 @@
" for higher_add_ratio in list((0, 1, 2)):\n",
" headline_ubi_rates.append(ubi_rate)\n",
" personal_allowances.append(pa)\n",
- " basic_rate_additions.append(solve_rates(adult_ubi=ubi_rate, personal_allowance=pa, higher_rate_ratio=higher_add_ratio, additional_rate_ratio=higher_add_ratio))\n",
+ " basic_rate_additions.append(\n",
+ " solve_rates(\n",
+ " adult_ubi=ubi_rate,\n",
+ " personal_allowance=pa,\n",
+ " higher_rate_ratio=higher_add_ratio,\n",
+ " additional_rate_ratio=higher_add_ratio,\n",
+ " )\n",
+ " )\n",
" higher_add_addition_ratios.append(higher_add_ratio)\n",
"\n",
"\n",
- "df = pd.DataFrame({\n",
- " \"Adult UBI\": headline_ubi_rates,\n",
- " \"Personal Allowance\": personal_allowances,\n",
- " \"Basic rate addition\": basic_rate_additions,\n",
- " \"Higher/additional rate increase ratio\": higher_add_addition_ratios,\n",
- "})\n",
+ "df = pd.DataFrame(\n",
+ " {\n",
+ " \"Adult UBI\": headline_ubi_rates,\n",
+ " \"Personal Allowance\": personal_allowances,\n",
+ " \"Basic rate addition\": basic_rate_additions,\n",
+ " \"Higher/additional rate increase ratio\": higher_add_addition_ratios,\n",
+ " }\n",
+ ")\n",
"df[\"Personal Allowance reduction\"] = 12_570 - df[\"Personal Allowance\"]\n",
- "df[\"Higher rate addition\"] = df[\"Basic rate addition\"] * df[\"Higher/additional rate increase ratio\"]\n",
+ "df[\"Higher rate addition\"] = (\n",
+ " df[\"Basic rate addition\"] * df[\"Higher/additional rate increase ratio\"]\n",
+ ")\n",
"df[\"Basic rate addition (x100)\"] = df[\"Basic rate addition\"] * 100\n",
- "df[\"Additional rate addition\"] = df[\"Basic rate addition\"] * df[\"Higher/additional rate increase ratio\"]\n",
+ "df[\"Additional rate addition\"] = (\n",
+ " df[\"Basic rate addition\"] * df[\"Higher/additional rate increase ratio\"]\n",
+ ")\n",
"df[\"UBI/Increase ratio combination\"] = [\n",
" f\"{higher_add_ratio:.0%}\" + \" \" * (ubi // 100)\n",
" for ubi, higher_add_ratio in zip(\n",
@@ -348,22 +423,26 @@
"df[\"Higher rate\"] = df[\"Higher rate addition\"] + 0.4\n",
"df[\"Add. rate\"] = df[\"Additional rate addition\"] + 0.45\n",
"\n",
- "df[[\n",
- " \"Adult UBI\",\n",
- " \"Personal Allowance\",\n",
- " \"Basic rate addition\",\n",
- " \"Higher/additional rate increase ratio\",\n",
- "]].to_csv(\"table.csv\", index=False)\n",
+ "df[\n",
+ " [\n",
+ " \"Adult UBI\",\n",
+ " \"Personal Allowance\",\n",
+ " \"Basic rate addition\",\n",
+ " \"Higher/additional rate increase ratio\",\n",
+ " ]\n",
+ "].to_csv(\"table.csv\", index=False)\n",
"df[\"Feasible MTR\"] = np.where(\n",
- " # £150k+ MTR under 100%\n",
- " (df[\"Add. rate\"] + 0.0325 < 1)\n",
- " # £100k-150k MTR under 100%\n",
- " & (df[\"Higher rate\"] * (\n",
- " np.where(df[\"Personal Allowance\"] > 0, 1.5, 1)\n",
- " ) + 0.0325 < 1),\n",
- " \"Yes\",\n",
- " \"No\",\n",
- " )"
+ " # £150k+ MTR under 100%\n",
+ " (df[\"Add. rate\"] + 0.0325 < 1)\n",
+ " # £100k-150k MTR under 100%\n",
+ " & (\n",
+ " df[\"Higher rate\"] * (np.where(df[\"Personal Allowance\"] > 0, 1.5, 1))\n",
+ " + 0.0325\n",
+ " < 1\n",
+ " ),\n",
+ " \"Yes\",\n",
+ " \"No\",\n",
+ ")"
]
},
{
@@ -394,9 +473,9 @@
"df = df[df[\"Feasible MTR\"] == \"Yes\"]\n",
"fig = px.line(\n",
" df,\n",
- " x=\"Personal Allowance\", \n",
- " y=\"Basic rate\", \n",
- " color=\"UBI/Increase ratio combination\", \n",
+ " x=\"Personal Allowance\",\n",
+ " y=\"Basic rate\",\n",
+ " color=\"UBI/Increase ratio combination\",\n",
" custom_data=[df.Label],\n",
" color_discrete_sequence=BLUE_COLOR_SEQUENCE[:3],\n",
")\n",
@@ -410,7 +489,7 @@
" legend_title=\"Higher/add. rate increase ratio\",\n",
")\n",
"\n",
- "CUSTOM_HOVERTEMPLATE=\"%{customdata[0]}\"\n",
+ "CUSTOM_HOVERTEMPLATE = \"%{customdata[0]}\"\n",
"fig.update_traces(hovertemplate=CUSTOM_HOVERTEMPLATE)\n",
"\n",
"for i, data in enumerate(fig.data):\n",
@@ -420,24 +499,30 @@
" else:\n",
" data[\"showlegend\"] = False\n",
"\n",
- "fig.add_trace(go.Scatter(\n",
- " y=np.linspace(0.18, 0.35, 3),\n",
- " x=[1_000] * 3,\n",
- " mode=\"text\",\n",
- " name=\"Label\",\n",
- " text=[\"£200/mo\", \"£300/mo\", \"£400/mo\"],\n",
- " textposition=\"bottom center\",\n",
- " showlegend=False,\n",
- "))\n",
- "\n",
- "fig_json = format_fig(fig).update_layout(\n",
- " title=\"Figure 2: Feasible UBI policies for Northern Ireland\",\n",
- " legend=dict(\n",
- " orientation=\"h\",\n",
- " yanchor=\"top\",\n",
- " y=1.1,\n",
+ "fig.add_trace(\n",
+ " go.Scatter(\n",
+ " y=np.linspace(0.18, 0.35, 3),\n",
+ " x=[1_000] * 3,\n",
+ " mode=\"text\",\n",
+ " name=\"Label\",\n",
+ " text=[\"£200/mo\", \"£300/mo\", \"£400/mo\"],\n",
+ " textposition=\"bottom center\",\n",
+ " showlegend=False,\n",
+ " )\n",
+ ")\n",
+ "\n",
+ "fig_json = (\n",
+ " format_fig(fig)\n",
+ " .update_layout(\n",
+ " title=\"Figure 2: Feasible UBI policies for Northern Ireland\",\n",
+ " legend=dict(\n",
+ " orientation=\"h\",\n",
+ " yanchor=\"top\",\n",
+ " y=1.1,\n",
+ " ),\n",
" )\n",
- ").to_json()\n",
+ " .to_json()\n",
+ ")\n",
"fig_json"
]
},
@@ -529,8 +614,14 @@
" additional_rate_ratio=row[\"Higher/additional rate increase ratio\"],\n",
" use_quick_estimate=False,\n",
" )\n",
- " row[\"Higher rate addition\"] = row[\"Basic rate addition\"] * row[\"Higher/additional rate increase ratio\"]\n",
- " row[\"Additional rate addition\"] = row[\"Basic rate addition\"] * row[\"Higher/additional rate increase ratio\"]\n",
+ " row[\"Higher rate addition\"] = (\n",
+ " row[\"Basic rate addition\"]\n",
+ " * row[\"Higher/additional rate increase ratio\"]\n",
+ " )\n",
+ " row[\"Additional rate addition\"] = (\n",
+ " row[\"Basic rate addition\"]\n",
+ " * row[\"Higher/additional rate increase ratio\"]\n",
+ " )\n",
" row[\"Higher rate\"] = row[\"Higher rate addition\"] + 0.4\n",
" row[\"Add. rate\"] = row[\"Additional rate addition\"] + 0.45\n",
" row[\"Basic rate\"] = row[\"Basic rate addition\"] + 0.2\n",
@@ -541,7 +632,7 @@
" additional_rate=model_1_row[\"Add. rate\"],\n",
" personal_allowance=model_1_row[\"Personal Allowance\"],\n",
" adult_bi=model_1_row[\"Adult UBI\"],\n",
- " higher_rate_threshold=50_270. - model_1_row[\"Personal Allowance\"],\n",
+ " higher_rate_threshold=50_270.0 - model_1_row[\"Personal Allowance\"],\n",
" child_bi=model_1_row[\"Adult UBI\"] * 0.5,\n",
" senior_bi=model_1_row[\"Adult UBI\"],\n",
")\n",
@@ -552,7 +643,7 @@
" additional_rate=model_2_row[\"Add. rate\"],\n",
" personal_allowance=model_2_row[\"Personal Allowance\"],\n",
" adult_bi=model_2_row[\"Adult UBI\"],\n",
- " higher_rate_threshold=50_270. - model_2_row[\"Personal Allowance\"],\n",
+ " higher_rate_threshold=50_270.0 - model_2_row[\"Personal Allowance\"],\n",
" child_bi=model_2_row[\"Adult UBI\"] * 0.5,\n",
" senior_bi=model_2_row[\"Adult UBI\"],\n",
")\n",
@@ -563,7 +654,7 @@
" additional_rate=model_3_row[\"Add. rate\"],\n",
" personal_allowance=model_3_row[\"Personal Allowance\"],\n",
" adult_bi=model_3_row[\"Adult UBI\"],\n",
- " higher_rate_threshold=50_270. - model_3_row[\"Personal Allowance\"],\n",
+ " higher_rate_threshold=50_270.0 - model_3_row[\"Personal Allowance\"],\n",
" child_bi=model_3_row[\"Adult UBI\"] * 0.5,\n",
" senior_bi=model_3_row[\"Adult UBI\"],\n",
")\n",
@@ -581,14 +672,35 @@
" filter_to_ni(simulation)\n",
"\n",
"summary_table = pd.DataFrame()\n",
- "summary_table[\"Monthly UBI amount for adults\"] = [model_1_row[\"Adult UBI\"] / 12, model_2_row[\"Adult UBI\"] / 12, model_3_row[\"Adult UBI\"] / 12]\n",
+ "summary_table[\"Monthly UBI amount for adults\"] = [\n",
+ " model_1_row[\"Adult UBI\"] / 12,\n",
+ " model_2_row[\"Adult UBI\"] / 12,\n",
+ " model_3_row[\"Adult UBI\"] / 12,\n",
+ "]\n",
"summary_table[\"Monthly UBI amount for children\"] = [0, 0, 0]\n",
- "summary_table[\"Personal Allowance\"] = [model_1_row[\"Personal Allowance\"], model_2_row[\"Personal Allowance\"], model_3_row[\"Personal Allowance\"]]\n",
- "summary_table[\"Basic rate increase\"] = [model_1_row[\"Basic rate addition\"], model_2_row[\"Basic rate addition\"], model_3_row[\"Basic rate addition\"]]\n",
- "summary_table[\"Higher rate increase\"] = [model_1_row[\"Higher rate addition\"], model_2_row[\"Higher rate addition\"], model_3_row[\"Higher rate addition\"]]\n",
- "summary_table[\"Additional rate increase\"] = [model_1_row[\"Additional rate addition\"], model_2_row[\"Additional rate addition\"], model_3_row[\"Additional rate addition\"]]\n",
+ "summary_table[\"Personal Allowance\"] = [\n",
+ " model_1_row[\"Personal Allowance\"],\n",
+ " model_2_row[\"Personal Allowance\"],\n",
+ " model_3_row[\"Personal Allowance\"],\n",
+ "]\n",
+ "summary_table[\"Basic rate increase\"] = [\n",
+ " model_1_row[\"Basic rate addition\"],\n",
+ " model_2_row[\"Basic rate addition\"],\n",
+ " model_3_row[\"Basic rate addition\"],\n",
+ "]\n",
+ "summary_table[\"Higher rate increase\"] = [\n",
+ " model_1_row[\"Higher rate addition\"],\n",
+ " model_2_row[\"Higher rate addition\"],\n",
+ " model_3_row[\"Higher rate addition\"],\n",
+ "]\n",
+ "summary_table[\"Additional rate increase\"] = [\n",
+ " model_1_row[\"Additional rate addition\"],\n",
+ " model_2_row[\"Additional rate addition\"],\n",
+ " model_3_row[\"Additional rate addition\"],\n",
+ "]\n",
"summary_table[\"Net cost\"] = [\n",
- " sim.calculate(\"household_net_income\").sum() - baseline.calculate(\"household_net_income\").sum()\n",
+ " sim.calculate(\"household_net_income\").sum()\n",
+ " - baseline.calculate(\"household_net_income\").sum()\n",
" for sim in [model_1_sim, model_2_sim, model_3_sim]\n",
"]\n",
"summary_table[\"Revenue raised\"] = [\n",
@@ -596,19 +708,27 @@
" for sim in [model_1_sim, model_2_sim, model_3_sim]\n",
"]\n",
"summary_table[\"Benefit outlay increase\"] = [\n",
- " sim.calculate(\"household_benefits\").sum() - baseline.calculate(\"household_benefits\").sum()\n",
+ " sim.calculate(\"household_benefits\").sum()\n",
+ " - baseline.calculate(\"household_benefits\").sum()\n",
" for sim in [model_1_sim, model_2_sim, model_3_sim]\n",
"]\n",
"summary_table[\"Poverty impact\"] = [\n",
- " sim.calculate(\"in_poverty\", map_to=\"person\").sum() / baseline.calculate(\"in_poverty\", map_to=\"person\").sum() - 1\n",
+ " sim.calculate(\"in_poverty\", map_to=\"person\").sum()\n",
+ " / baseline.calculate(\"in_poverty\", map_to=\"person\").sum()\n",
+ " - 1\n",
" for sim in [model_1_sim, model_2_sim, model_3_sim]\n",
"]\n",
"summary_table[\"Inequality impact\"] = [\n",
- " sim.calculate(\"equiv_household_net_income\", map_to=\"person\").gini() / baseline.calculate(\"equiv_household_net_income\", map_to=\"person\").gini() - 1\n",
+ " sim.calculate(\"equiv_household_net_income\", map_to=\"person\").gini()\n",
+ " / baseline.calculate(\"equiv_household_net_income\", map_to=\"person\").gini()\n",
+ " - 1\n",
" for sim in [model_1_sim, model_2_sim, model_3_sim]\n",
"]\n",
"summary_table[\"Percent of population gaining\"] = [\n",
- " (sim.calculate(\"household_net_income\", map_to=\"person\") > baseline.calculate(\"household_net_income\", map_to=\"person\")).mean()\n",
+ " (\n",
+ " sim.calculate(\"household_net_income\", map_to=\"person\")\n",
+ " > baseline.calculate(\"household_net_income\", map_to=\"person\")\n",
+ " ).mean()\n",
" for sim in [model_1_sim, model_2_sim, model_3_sim]\n",
"]\n",
"\n",
@@ -616,27 +736,56 @@
"\n",
"# Formatting\n",
"\n",
- "summary_table[\"Monthly UBI amount for adults\"] = summary_table[\"Monthly UBI amount for adults\"].map(lambda x: f\"£{x:,.0f}\")\n",
- "summary_table[\"Monthly UBI amount for children\"] = summary_table[\"Monthly UBI amount for children\"].map(lambda x: f\"£{x:,.0f}\")\n",
- "summary_table[\"Personal Allowance\"] = summary_table[\"Personal Allowance\"].map(lambda x: f\"£{x:,.0f}\")\n",
- "summary_table[\"Basic rate increase\"] = summary_table[\"Basic rate increase\"].map(lambda x: f\"{x * 100:.1f}p\")\n",
- "summary_table[\"Higher rate increase\"] = summary_table[\"Higher rate increase\"].map(lambda x: f\"{x * 100:.1f}p\")\n",
- "summary_table[\"Additional rate increase\"] = summary_table[\"Additional rate increase\"].map(lambda x: f\"{x * 100:.1f}p\")\n",
- "summary_table[\"Westminster contribution\"] = summary_table[\"Net cost\"] / summary_table[\"Benefit outlay increase\"]\n",
- "summary_table[\"Westminster contribution\"] = summary_table[\"Westminster contribution\"].map(lambda x: f\"{x:.1%}\")\n",
- "summary_table[\"Net cost\"] = summary_table[\"Net cost\"].map(lambda x: f\"£{x/1e9:,.1f}bn\")\n",
- "summary_table[\"Revenue raised\"] = summary_table[\"Revenue raised\"].map(lambda x: f\"£{x/1e9:,.1f}bn\")\n",
- "summary_table[\"Benefit outlay increase\"] = summary_table[\"Benefit outlay increase\"].map(lambda x: f\"£{x/1e9:,.1f}bn\")\n",
- "summary_table[\"Poverty impact\"] = summary_table[\"Poverty impact\"].map(lambda x: f\"{x * 100:+.1f}%\")\n",
- "summary_table[\"Inequality impact\"] = summary_table[\"Inequality impact\"].map(lambda x: f\"{x * 100:+.1f}%\")\n",
- "summary_table[\"Percent of population gaining\"] = summary_table[\"Percent of population gaining\"].map(lambda x: f\"{x * 100:.0f}%\")\n",
+ "summary_table[\"Monthly UBI amount for adults\"] = summary_table[\n",
+ " \"Monthly UBI amount for adults\"\n",
+ "].map(lambda x: f\"£{x:,.0f}\")\n",
+ "summary_table[\"Monthly UBI amount for children\"] = summary_table[\n",
+ " \"Monthly UBI amount for children\"\n",
+ "].map(lambda x: f\"£{x:,.0f}\")\n",
+ "summary_table[\"Personal Allowance\"] = summary_table[\"Personal Allowance\"].map(\n",
+ " lambda x: f\"£{x:,.0f}\"\n",
+ ")\n",
+ "summary_table[\"Basic rate increase\"] = summary_table[\n",
+ " \"Basic rate increase\"\n",
+ "].map(lambda x: f\"{x * 100:.1f}p\")\n",
+ "summary_table[\"Higher rate increase\"] = summary_table[\n",
+ " \"Higher rate increase\"\n",
+ "].map(lambda x: f\"{x * 100:.1f}p\")\n",
+ "summary_table[\"Additional rate increase\"] = summary_table[\n",
+ " \"Additional rate increase\"\n",
+ "].map(lambda x: f\"{x * 100:.1f}p\")\n",
+ "summary_table[\"Westminster contribution\"] = (\n",
+ " summary_table[\"Net cost\"] / summary_table[\"Benefit outlay increase\"]\n",
+ ")\n",
+ "summary_table[\"Westminster contribution\"] = summary_table[\n",
+ " \"Westminster contribution\"\n",
+ "].map(lambda x: f\"{x:.1%}\")\n",
+ "summary_table[\"Net cost\"] = summary_table[\"Net cost\"].map(\n",
+ " lambda x: f\"£{x/1e9:,.1f}bn\"\n",
+ ")\n",
+ "summary_table[\"Revenue raised\"] = summary_table[\"Revenue raised\"].map(\n",
+ " lambda x: f\"£{x/1e9:,.1f}bn\"\n",
+ ")\n",
+ "summary_table[\"Benefit outlay increase\"] = summary_table[\n",
+ " \"Benefit outlay increase\"\n",
+ "].map(lambda x: f\"£{x/1e9:,.1f}bn\")\n",
+ "summary_table[\"Poverty impact\"] = summary_table[\"Poverty impact\"].map(\n",
+ " lambda x: f\"{x * 100:+.1f}%\"\n",
+ ")\n",
+ "summary_table[\"Inequality impact\"] = summary_table[\"Inequality impact\"].map(\n",
+ " lambda x: f\"{x * 100:+.1f}%\"\n",
+ ")\n",
+ "summary_table[\"Percent of population gaining\"] = summary_table[\n",
+ " \"Percent of population gaining\"\n",
+ "].map(lambda x: f\"{x * 100:.0f}%\")\n",
"\n",
"policy_ids = [\n",
" reform.api_id\n",
" for reform in [model_1_reform, model_2_reform, model_3_reform]\n",
"]\n",
"summary_table[\"PolicyEngine link\"] = [\n",
- " f\"#{policy_id}\" for policy_id in policy_ids\n",
+ " f'#{policy_id}'\n",
+ " for policy_id in policy_ids\n",
"]\n",
"summary_table.index = [\"Model 1\", \"Model 2\", \"Model 3\"]\n",
"\n",
@@ -681,20 +830,42 @@
"from policyengine_core.charts import BLUE_COLOUR_SCALE\n",
"\n",
"dfs = []\n",
- "for sim, policy_name in zip([model_1_sim, model_2_sim, model_3_sim], [\"£200/month\", \"£300/month\", \"£400/month\"]):\n",
- " for group, name in zip([\"is_child\", \"is_WA_adult\", \"is_SP_age\", \"people\"], [\"Child\", \"Working-age\", \"Senior\", \"All\"]):\n",
- " df = pd.DataFrame({\n",
- " \"Poverty rate change\": (\n",
- " [sim.calc(\"in_poverty_bhc\", map_to=\"person\")[sim.calc(group) > 0].mean()\n",
- " / baseline.calc(\"in_poverty_bhc\", map_to=\"person\")[sim.calc(group) > 0].mean() - 1]\n",
- " ),\n",
- " \"Deep poverty rate change\": (\n",
- " [sim.calc(\"in_deep_poverty_bhc\", map_to=\"person\")[sim.calc(group) > 0].mean()\n",
- " / baseline.calc(\"in_deep_poverty_bhc\", map_to=\"person\")[sim.calc(group) > 0].mean() - 1]\n",
- " ),\n",
- " \"Age group\": [name],\n",
- " \"Policy\": [policy_name],\n",
- " })\n",
+ "for sim, policy_name in zip(\n",
+ " [model_1_sim, model_2_sim, model_3_sim],\n",
+ " [\"£200/month\", \"£300/month\", \"£400/month\"],\n",
+ "):\n",
+ " for group, name in zip(\n",
+ " [\"is_child\", \"is_WA_adult\", \"is_SP_age\", \"people\"],\n",
+ " [\"Child\", \"Working-age\", \"Senior\", \"All\"],\n",
+ " ):\n",
+ " df = pd.DataFrame(\n",
+ " {\n",
+ " \"Poverty rate change\": (\n",
+ " [\n",
+ " sim.calc(\"in_poverty_bhc\", map_to=\"person\")[\n",
+ " sim.calc(group) > 0\n",
+ " ].mean()\n",
+ " / baseline.calc(\"in_poverty_bhc\", map_to=\"person\")[\n",
+ " sim.calc(group) > 0\n",
+ " ].mean()\n",
+ " - 1\n",
+ " ]\n",
+ " ),\n",
+ " \"Deep poverty rate change\": (\n",
+ " [\n",
+ " sim.calc(\"in_deep_poverty_bhc\", map_to=\"person\")[\n",
+ " sim.calc(group) > 0\n",
+ " ].mean()\n",
+ " / baseline.calc(\n",
+ " \"in_deep_poverty_bhc\", map_to=\"person\"\n",
+ " )[sim.calc(group) > 0].mean()\n",
+ " - 1\n",
+ " ]\n",
+ " ),\n",
+ " \"Age group\": [name],\n",
+ " \"Policy\": [policy_name],\n",
+ " }\n",
+ " )\n",
" dfs += [df]\n",
"poverty_df = pd.concat(dfs)\n",
"fig = px.bar(\n",
@@ -709,7 +880,9 @@
" title=\"Figure 3: Poverty rate change by age group\",\n",
")\n",
"for i in range(len(fig.data)):\n",
- " fig.data[i][\"text\"] = [f\"{x:+.1%}\" if x is not None else \"\" for x in fig.data[i][\"y\"]]\n",
+ " fig.data[i][\"text\"] = [\n",
+ " f\"{x:+.1%}\" if x is not None else \"\" for x in fig.data[i][\"y\"]\n",
+ " ]\n",
"format_fig(fig).to_json()"
]
},
@@ -743,10 +916,12 @@
"source": [
"dfs = []\n",
"from policyengine_core.charts import BLUE_COLOUR_SCALE\n",
- "for reformed, policy_name in zip([model_1_sim, model_2_sim, model_3_sim], [\"£200/month\", \"£300/month\", \"£400/month\"]):\n",
- " equiv_income = baseline.calc(\n",
- " \"equiv_household_net_income\", map_to=\"person\"\n",
- " )\n",
+ "\n",
+ "for reformed, policy_name in zip(\n",
+ " [model_1_sim, model_2_sim, model_3_sim],\n",
+ " [\"£200/month\", \"£300/month\", \"£400/month\"],\n",
+ "):\n",
+ " equiv_income = baseline.calc(\"equiv_household_net_income\", map_to=\"person\")\n",
" reform_equiv_income = reformed.calc(\n",
" \"equiv_household_net_income\", map_to=\"person\"\n",
" )\n",
@@ -775,15 +950,17 @@
" top_one_pct_share_change = (\n",
" reform_top_one_pct_share / baseline_top_one_pct_share - 1\n",
" )\n",
- " df = pd.DataFrame({\n",
- " \"Metric\": [\"Gini coefficient\", \"Top-10% share\", \"Top-1% share\"],\n",
- " \"Inequality change\": [\n",
- " reform_gini / baseline_gini - 1,\n",
- " reform_top_ten_pct_share / baseline_top_ten_pct_share - 1,\n",
- " reform_top_one_pct_share / baseline_top_one_pct_share - 1,\n",
- " ],\n",
- " \"Policy\": [policy_name] * 3,\n",
- " })\n",
+ " df = pd.DataFrame(\n",
+ " {\n",
+ " \"Metric\": [\"Gini coefficient\", \"Top-10% share\", \"Top-1% share\"],\n",
+ " \"Inequality change\": [\n",
+ " reform_gini / baseline_gini - 1,\n",
+ " reform_top_ten_pct_share / baseline_top_ten_pct_share - 1,\n",
+ " reform_top_one_pct_share / baseline_top_one_pct_share - 1,\n",
+ " ],\n",
+ " \"Policy\": [policy_name] * 3,\n",
+ " }\n",
+ " )\n",
" dfs += [df]\n",
"inequality_df = pd.concat(dfs)\n",
"fig = px.bar(\n",
@@ -797,7 +974,9 @@
" yaxis_tickformat=\".0%\",\n",
")\n",
"for i in range(len(fig.data)):\n",
- " fig.data[i][\"text\"] = [f\"{x:+.1%}\" if x is not None else \"\" for x in fig.data[i][\"y\"]]\n",
+ " fig.data[i][\"text\"] = [\n",
+ " f\"{x:+.1%}\" if x is not None else \"\" for x in fig.data[i][\"y\"]\n",
+ " ]\n",
"format_fig(fig).update_layout(\n",
" title=\"Figure 4: Inequality impact by metric and UBI policy\",\n",
").to_json()"
@@ -888,7 +1067,7 @@
" orientation=\"h\",\n",
" yanchor=\"bottom\",\n",
" y=-0.3,\n",
- " )\n",
+ " ),\n",
").to_json()"
]
},
@@ -982,7 +1161,7 @@
" orientation=\"h\",\n",
" yanchor=\"bottom\",\n",
" y=-0.3,\n",
- " )\n",
+ " ),\n",
").to_json()"
]
},
@@ -1067,7 +1246,7 @@
" orientation=\"h\",\n",
" yanchor=\"bottom\",\n",
" y=-0.3,\n",
- " )\n",
+ " ),\n",
").to_json()"
]
},
@@ -1123,12 +1302,16 @@
" \"household_owns_tv\": True,\n",
" }\n",
" },\n",
- " \"axes\": [[{\n",
- " \"name\": \"employment_income\",\n",
- " \"min\": 0,\n",
- " \"max\": 100_000,\n",
- " \"count\": 100,\n",
- " }]],\n",
+ " \"axes\": [\n",
+ " [\n",
+ " {\n",
+ " \"name\": \"employment_income\",\n",
+ " \"min\": 0,\n",
+ " \"max\": 100_000,\n",
+ " \"count\": 100,\n",
+ " }\n",
+ " ]\n",
+ " ],\n",
"}\n",
"\n",
"MARRIED_TWO_CHILDREN = {\n",
@@ -1160,12 +1343,16 @@
" \"household_owns_tv\": True,\n",
" }\n",
" },\n",
- " \"axes\": [[{\n",
- " \"name\": \"employment_income\",\n",
- " \"min\": 0,\n",
- " \"max\": 100_000,\n",
- " \"count\": 100,\n",
- " }]],\n",
+ " \"axes\": [\n",
+ " [\n",
+ " {\n",
+ " \"name\": \"employment_income\",\n",
+ " \"min\": 0,\n",
+ " \"max\": 100_000,\n",
+ " \"count\": 100,\n",
+ " }\n",
+ " ]\n",
+ " ],\n",
"}\n",
"\n",
"PENSIONER_COUPLE_NO_CHILDREN = {\n",
@@ -1175,7 +1362,7 @@
" },\n",
" \"adult_2\": {\n",
" \"age\": 71,\n",
- " }\n",
+ " },\n",
" },\n",
" \"benunits\": {\n",
" \"benunit\": {\n",
@@ -1191,24 +1378,50 @@
" \"household_owns_tv\": True,\n",
" }\n",
" },\n",
- " \"axes\": [[{\n",
- " \"name\": \"employment_income\",\n",
- " \"min\": 0,\n",
- " \"max\": 100_000,\n",
- " \"count\": 100,\n",
- " }]],\n",
+ " \"axes\": [\n",
+ " [\n",
+ " {\n",
+ " \"name\": \"employment_income\",\n",
+ " \"min\": 0,\n",
+ " \"max\": 100_000,\n",
+ " \"count\": 100,\n",
+ " }\n",
+ " ]\n",
+ " ],\n",
"}\n",
"dfs = []\n",
- "for reform, policy_name in zip([model_1_reform, model_2_reform, model_3_reform], [\"£200/month\", \"£300/month\", \"£400/month\"]):\n",
+ "for reform, policy_name in zip(\n",
+ " [model_1_reform, model_2_reform, model_3_reform],\n",
+ " [\"£200/month\", \"£300/month\", \"£400/month\"],\n",
+ "):\n",
" for household_type, household_name in zip(\n",
- " [SINGLE_NO_CHILDREN, MARRIED_TWO_CHILDREN, PENSIONER_COUPLE_NO_CHILDREN],\n",
- " [\"Single adult, no children\", \"Married couple, two children\", \"Pensioner couple, no children\"],\n",
+ " [\n",
+ " SINGLE_NO_CHILDREN,\n",
+ " MARRIED_TWO_CHILDREN,\n",
+ " PENSIONER_COUPLE_NO_CHILDREN,\n",
+ " ],\n",
+ " [\n",
+ " \"Single adult, no children\",\n",
+ " \"Married couple, two children\",\n",
+ " \"Pensioner couple, no children\",\n",
+ " ],\n",
" ):\n",
" baseline_sim = Simulation(situation=household_type)\n",
" sim = Simulation(situation=household_type, reform=reform)\n",
- " net_income = sim.calculate(\"household_net_income\", 2023) - baseline_sim.calculate(\"household_net_income\", 2023)\n",
- " earnings = sim.calculate(\"employment_income\", map_to=\"household\", period=2023)\n",
- " df = pd.DataFrame({\"Policy\": policy_name, \"Net income\": net_income, \"Earnings\": earnings, \"Household type\": household_name})\n",
+ " net_income = sim.calculate(\n",
+ " \"household_net_income\", 2023\n",
+ " ) - baseline_sim.calculate(\"household_net_income\", 2023)\n",
+ " earnings = sim.calculate(\n",
+ " \"employment_income\", map_to=\"household\", period=2023\n",
+ " )\n",
+ " df = pd.DataFrame(\n",
+ " {\n",
+ " \"Policy\": policy_name,\n",
+ " \"Net income\": net_income,\n",
+ " \"Earnings\": earnings,\n",
+ " \"Household type\": household_name,\n",
+ " }\n",
+ " )\n",
" dfs.append(df)\n",
"\n",
"df = pd.concat(dfs)"
@@ -1242,17 +1455,38 @@
"H2 = \"Married couple, two children\"\n",
"H3 = \"Pensioner couple, no children\"\n",
"\n",
+ "\n",
"def plot(household_name: str, title):\n",
" fig = px.line(\n",
- " df[df[\"Household type\"] == household_name], \n",
- " x=\"Earnings\", \n",
- " y=\"Net income\", \n",
+ " df[df[\"Household type\"] == household_name],\n",
+ " x=\"Earnings\",\n",
+ " y=\"Net income\",\n",
" color=\"Policy\",\n",
" color_discrete_sequence=BLUE_COLOUR_SCALE,\n",
" ).update_layout(\n",
" yaxis_tickmode=\"array\",\n",
- " yaxis_tickvals=[-20_000, -15_000, -10_000, -5_000, 0, 5_000, 10_000, 15_000, 20_000],\n",
- " yaxis_ticktext=[\"-£20,000\", \"-£15,000\", \"-£10,000\", \"-£5,000\", \"£0\", \"+£5,000\", \"+£10,000\", \"+£15,000\", \"+£20,000\"],\n",
+ " yaxis_tickvals=[\n",
+ " -20_000,\n",
+ " -15_000,\n",
+ " -10_000,\n",
+ " -5_000,\n",
+ " 0,\n",
+ " 5_000,\n",
+ " 10_000,\n",
+ " 15_000,\n",
+ " 20_000,\n",
+ " ],\n",
+ " yaxis_ticktext=[\n",
+ " \"-£20,000\",\n",
+ " \"-£15,000\",\n",
+ " \"-£10,000\",\n",
+ " \"-£5,000\",\n",
+ " \"£0\",\n",
+ " \"+£5,000\",\n",
+ " \"+£10,000\",\n",
+ " \"+£15,000\",\n",
+ " \"+£20,000\",\n",
+ " ],\n",
" yaxis_range=(-25_000, 25_000),\n",
" xaxis_tickformat=\",.0f\",\n",
" xaxis_tickprefix=\"£\",\n",
@@ -1261,17 +1495,22 @@
" title=title,\n",
" )\n",
" fig.add_shape(\n",
- " type=\"line\",\n",
- " xref=\"paper\",\n",
- " yref=\"y\",\n",
- " x0=0,\n",
- " y0=0,\n",
- " x1=1,\n",
- " y1=0,\n",
- " line=dict(color=\"grey\", width=1),\n",
- " )\n",
+ " type=\"line\",\n",
+ " xref=\"paper\",\n",
+ " yref=\"y\",\n",
+ " x0=0,\n",
+ " y0=0,\n",
+ " x1=1,\n",
+ " y1=0,\n",
+ " line=dict(color=\"grey\", width=1),\n",
+ " )\n",
" return format_fig(fig)\n",
- "plot(H1, title=\"Figure 11: Change to annual net income by UBI policy and income (single adult, no children)\").to_json()"
+ "\n",
+ "\n",
+ "plot(\n",
+ " H1,\n",
+ " title=\"Figure 11: Change to annual net income by UBI policy and income (single adult, no children)\",\n",
+ ").to_json()"
]
},
{
@@ -1302,7 +1541,10 @@
}
],
"source": [
- "plot(H2, title=\"Figure 12: Change to annual net income by UBI policy and income (married couple, two children)\").to_json()"
+ "plot(\n",
+ " H2,\n",
+ " title=\"Figure 12: Change to annual net income by UBI policy and income (married couple, two children)\",\n",
+ ").to_json()"
]
},
{
@@ -1337,7 +1579,10 @@
}
],
"source": [
- "plot(H3, title=\"Figure 13: Change to annual net income by UBI policy and income (pensioner couple, no children)\").to_json()"
+ "plot(\n",
+ " H3,\n",
+ " title=\"Figure 13: Change to annual net income by UBI policy and income (pensioner couple, no children)\",\n",
+ ").to_json()"
]
},
{
@@ -1398,12 +1643,16 @@
" \"household_owns_tv\": True,\n",
" }\n",
" },\n",
- " \"axes\": [[{\n",
- " \"name\": \"employment_income\",\n",
- " \"min\": 0,\n",
- " \"max\": 150_000,\n",
- " \"count\": 100,\n",
- " }]],\n",
+ " \"axes\": [\n",
+ " [\n",
+ " {\n",
+ " \"name\": \"employment_income\",\n",
+ " \"min\": 0,\n",
+ " \"max\": 150_000,\n",
+ " \"count\": 100,\n",
+ " }\n",
+ " ]\n",
+ " ],\n",
"}\n",
"\n",
"MARRIED_TWO_CHILDREN = {\n",
@@ -1435,12 +1684,16 @@
" \"household_owns_tv\": True,\n",
" }\n",
" },\n",
- " \"axes\": [[{\n",
- " \"name\": \"employment_income\",\n",
- " \"min\": 0,\n",
- " \"max\": 150_000,\n",
- " \"count\": 100,\n",
- " }]],\n",
+ " \"axes\": [\n",
+ " [\n",
+ " {\n",
+ " \"name\": \"employment_income\",\n",
+ " \"min\": 0,\n",
+ " \"max\": 150_000,\n",
+ " \"count\": 100,\n",
+ " }\n",
+ " ]\n",
+ " ],\n",
"}\n",
"\n",
"PENSIONER_COUPLE_NO_CHILDREN = {\n",
@@ -1450,7 +1703,7 @@
" },\n",
" \"adult_2\": {\n",
" \"age\": 71,\n",
- " }\n",
+ " },\n",
" },\n",
" \"benunits\": {\n",
" \"benunit\": {\n",
@@ -1466,25 +1719,49 @@
" \"household_owns_tv\": True,\n",
" }\n",
" },\n",
- " \"axes\": [[{\n",
- " \"name\": \"employment_income\",\n",
- " \"min\": 0,\n",
- " \"max\": 150_000,\n",
- " \"count\": 100,\n",
- " }]],\n",
+ " \"axes\": [\n",
+ " [\n",
+ " {\n",
+ " \"name\": \"employment_income\",\n",
+ " \"min\": 0,\n",
+ " \"max\": 150_000,\n",
+ " \"count\": 100,\n",
+ " }\n",
+ " ]\n",
+ " ],\n",
"}\n",
"dfs = []\n",
- "for reform, policy_name in zip([None, model_1_reform, model_2_reform, model_3_reform], [\"Baseline\", \"£200/month\", \"£300/month\", \"£400/month\"]):\n",
+ "for reform, policy_name in zip(\n",
+ " [None, model_1_reform, model_2_reform, model_3_reform],\n",
+ " [\"Baseline\", \"£200/month\", \"£300/month\", \"£400/month\"],\n",
+ "):\n",
" for household_type, household_name in zip(\n",
- " [SINGLE_NO_CHILDREN, MARRIED_TWO_CHILDREN, PENSIONER_COUPLE_NO_CHILDREN],\n",
- " [\"Single adult, no children\", \"Married couple, two children\", \"Pensioner couple, no children\"],\n",
+ " [\n",
+ " SINGLE_NO_CHILDREN,\n",
+ " MARRIED_TWO_CHILDREN,\n",
+ " PENSIONER_COUPLE_NO_CHILDREN,\n",
+ " ],\n",
+ " [\n",
+ " \"Single adult, no children\",\n",
+ " \"Married couple, two children\",\n",
+ " \"Pensioner couple, no children\",\n",
+ " ],\n",
" ):\n",
" baseline_sim = Simulation(situation=household_type)\n",
" sim = Simulation(situation=household_type, reform=reform)\n",
" mtr = sim.calculate(\"marginal_tax_rate\", 2023)\n",
" mtr = mtr.reshape((100, -1)).T[0]\n",
- " earnings = sim.calculate(\"employment_income\", map_to=\"household\", period=2023)\n",
- " df = pd.DataFrame({\"Policy\": policy_name, \"Marginal tax rate\": mtr, \"Earnings\": earnings, \"Household type\": household_name})\n",
+ " earnings = sim.calculate(\n",
+ " \"employment_income\", map_to=\"household\", period=2023\n",
+ " )\n",
+ " df = pd.DataFrame(\n",
+ " {\n",
+ " \"Policy\": policy_name,\n",
+ " \"Marginal tax rate\": mtr,\n",
+ " \"Earnings\": earnings,\n",
+ " \"Household type\": household_name,\n",
+ " }\n",
+ " )\n",
" dfs.append(df)\n",
"\n",
"mtr_df = pd.concat(dfs)"
@@ -1513,22 +1790,26 @@
"source": [
"import plotly.io as pio\n",
"\n",
- "fig = px.line(\n",
- " mtr_df[mtr_df[\"Household type\"] == \"Married couple, two children\"], \n",
- " x=\"Earnings\", \n",
- " y=\"Marginal tax rate\", \n",
- " color=\"Policy\",\n",
- " color_discrete_sequence=[DARK_GRAY] + BLUE_COLOUR_SCALE,\n",
- ").update_layout(\n",
- " yaxis_tickformat=\".0%\",\n",
- " xaxis_tickformat=\",.0f\",\n",
- " xaxis_tickprefix=\"£\",\n",
- " yaxis_range=(-0.01, 1),\n",
- " yaxis_title=\"Marginal tax rate\",\n",
- " xaxis_title=\"Employment income\",\n",
- " title=\"Marginal tax rate by UBI policy and income (married couple, two children)\",\n",
- ").update_traces(\n",
- " line_shape=\"hv\",\n",
+ "fig = (\n",
+ " px.line(\n",
+ " mtr_df[mtr_df[\"Household type\"] == \"Married couple, two children\"],\n",
+ " x=\"Earnings\",\n",
+ " y=\"Marginal tax rate\",\n",
+ " color=\"Policy\",\n",
+ " color_discrete_sequence=[DARK_GRAY] + BLUE_COLOUR_SCALE,\n",
+ " )\n",
+ " .update_layout(\n",
+ " yaxis_tickformat=\".0%\",\n",
+ " xaxis_tickformat=\",.0f\",\n",
+ " xaxis_tickprefix=\"£\",\n",
+ " yaxis_range=(-0.01, 1),\n",
+ " yaxis_title=\"Marginal tax rate\",\n",
+ " xaxis_title=\"Employment income\",\n",
+ " title=\"Marginal tax rate by UBI policy and income (married couple, two children)\",\n",
+ " )\n",
+ " .update_traces(\n",
+ " line_shape=\"hv\",\n",
+ " )\n",
")\n",
"format_fig(fig).to_json()"
]
@@ -1555,18 +1836,40 @@
"import pandas as pd\n",
"\n",
"dfs = []\n",
- "for reform, policy_name in zip([model_1_reform, model_2_reform, model_3_reform], [\"£200/month\", \"£300/month\", \"£400/month\"]):\n",
+ "for reform, policy_name in zip(\n",
+ " [model_1_reform, model_2_reform, model_3_reform],\n",
+ " [\"£200/month\", \"£300/month\", \"£400/month\"],\n",
+ "):\n",
" for household_type, household_name in zip(\n",
- " [SINGLE_NO_CHILDREN, MARRIED_TWO_CHILDREN, PENSIONER_COUPLE_NO_CHILDREN],\n",
- " [\"Single adult, no children\", \"Married couple, two children\", \"Pensioner couple, no children\"],\n",
+ " [\n",
+ " SINGLE_NO_CHILDREN,\n",
+ " MARRIED_TWO_CHILDREN,\n",
+ " PENSIONER_COUPLE_NO_CHILDREN,\n",
+ " ],\n",
+ " [\n",
+ " \"Single adult, no children\",\n",
+ " \"Married couple, two children\",\n",
+ " \"Pensioner couple, no children\",\n",
+ " ],\n",
" ):\n",
" baseline_sim = Simulation(situation=household_type)\n",
" sim = Simulation(situation=household_type, reform=reform)\n",
- " mtr = sim.calculate(\"marginal_tax_rate\", 2023) - baseline_sim.calculate(\"marginal_tax_rate\", 2023)\n",
+ " mtr = sim.calculate(\n",
+ " \"marginal_tax_rate\", 2023\n",
+ " ) - baseline_sim.calculate(\"marginal_tax_rate\", 2023)\n",
" mtr = mtr.reshape((100, -1)).T[0]\n",
- " wage = - mtr\n",
- " earnings = sim.calculate(\"employment_income\", map_to=\"household\", period=2023)\n",
- " df = pd.DataFrame({\"Policy\": policy_name, \"Effective wage change\": wage, \"Earnings\": earnings, \"Household type\": household_name})\n",
+ " wage = -mtr\n",
+ " earnings = sim.calculate(\n",
+ " \"employment_income\", map_to=\"household\", period=2023\n",
+ " )\n",
+ " df = pd.DataFrame(\n",
+ " {\n",
+ " \"Policy\": policy_name,\n",
+ " \"Effective wage change\": wage,\n",
+ " \"Earnings\": earnings,\n",
+ " \"Household type\": household_name,\n",
+ " }\n",
+ " )\n",
" dfs.append(df)\n",
"\n",
"wage_change_df = pd.concat(dfs)"
@@ -1595,35 +1898,42 @@
"source": [
"import plotly.io as pio\n",
"\n",
+ "\n",
"def plot_wage(household_name: str):\n",
- " fig = px.line(\n",
- " wage_change_df[wage_change_df[\"Household type\"] == household_name], \n",
- " x=\"Earnings\", \n",
- " y=\"Effective wage change\", \n",
- " color=\"Policy\",\n",
- " color_discrete_sequence=BLUE_COLOUR_SCALE,\n",
- " ).update_layout(\n",
- " yaxis_tickformat=\"+.0%\",\n",
- " xaxis_tickformat=\",.0f\",\n",
- " xaxis_tickprefix=\"£\",\n",
- " yaxis_range=(-1, 1),\n",
- " yaxis_title=\"Change to effective marginal wage\",\n",
- " xaxis_title=\"Employment income\",\n",
- " title=\"Figure 15: Change to effective marginal wage by UBI policy and income (married couple, two children)\",\n",
- " ).update_traces(\n",
- " line_shape=\"hv\",\n",
+ " fig = (\n",
+ " px.line(\n",
+ " wage_change_df[wage_change_df[\"Household type\"] == household_name],\n",
+ " x=\"Earnings\",\n",
+ " y=\"Effective wage change\",\n",
+ " color=\"Policy\",\n",
+ " color_discrete_sequence=BLUE_COLOUR_SCALE,\n",
+ " )\n",
+ " .update_layout(\n",
+ " yaxis_tickformat=\"+.0%\",\n",
+ " xaxis_tickformat=\",.0f\",\n",
+ " xaxis_tickprefix=\"£\",\n",
+ " yaxis_range=(-1, 1),\n",
+ " yaxis_title=\"Change to effective marginal wage\",\n",
+ " xaxis_title=\"Employment income\",\n",
+ " title=\"Figure 15: Change to effective marginal wage by UBI policy and income (married couple, two children)\",\n",
+ " )\n",
+ " .update_traces(\n",
+ " line_shape=\"hv\",\n",
+ " )\n",
" )\n",
" fig.add_shape(\n",
- " type=\"line\",\n",
- " xref=\"paper\",\n",
- " yref=\"y\",\n",
- " x0=0,\n",
- " y0=0,\n",
- " x1=1,\n",
- " y1=0,\n",
- " line=dict(color=\"grey\", width=1),\n",
- " )\n",
+ " type=\"line\",\n",
+ " xref=\"paper\",\n",
+ " yref=\"y\",\n",
+ " x0=0,\n",
+ " y0=0,\n",
+ " x1=1,\n",
+ " y1=0,\n",
+ " line=dict(color=\"grey\", width=1),\n",
+ " )\n",
" return format_fig(fig)\n",
+ "\n",
+ "\n",
"plot_wage(H2).to_json()"
]
},
@@ -1701,26 +2011,82 @@
"\n",
"# Creating data dictionary\n",
"data = {\n",
- " 'Variable': [\n",
- " 'Income from dividends (£bn)',\n",
- " 'Employment income (£bn)',\n",
- " 'Pension income (£bn)',\n",
- " 'Rental income (£bn)',\n",
- " 'Savings interest income (£bn)',\n",
- " 'Self-employment income (£bn)',\n",
- " 'Income Tax (£bn)',\n",
- " 'National Insurance (total) (£bn)',\n",
- " 'Population (m)',\n",
- " 'Households (m)'\n",
+ " \"Variable\": [\n",
+ " \"Income from dividends (£bn)\",\n",
+ " \"Employment income (£bn)\",\n",
+ " \"Pension income (£bn)\",\n",
+ " \"Rental income (£bn)\",\n",
+ " \"Savings interest income (£bn)\",\n",
+ " \"Self-employment income (£bn)\",\n",
+ " \"Income Tax (£bn)\",\n",
+ " \"National Insurance (total) (£bn)\",\n",
+ " \"Population (m)\",\n",
+ " \"Households (m)\",\n",
+ " ],\n",
+ " \"Target total\": [\n",
+ " 1.40,\n",
+ " 19.00,\n",
+ " 2.23,\n",
+ " 0.56,\n",
+ " 0.15,\n",
+ " 2.20,\n",
+ " 3.03,\n",
+ " 3.01,\n",
+ " 1.90,\n",
+ " 0.77,\n",
+ " ],\n",
+ " \"Original FRS total\": [\n",
+ " 0.10,\n",
+ " 19.17,\n",
+ " 2.52,\n",
+ " 0.23,\n",
+ " 0.10,\n",
+ " 2.29,\n",
+ " 2.84,\n",
+ " 3.28,\n",
+ " 1.86,\n",
+ " 0.74,\n",
+ " ],\n",
+ " \"Calibrated FRS total\": [\n",
+ " 1.39,\n",
+ " 18.45,\n",
+ " 2.37,\n",
+ " 0.56,\n",
+ " 0.15,\n",
+ " 2.15,\n",
+ " 2.97,\n",
+ " 2.98,\n",
+ " 1.78,\n",
+ " 0.77,\n",
+ " ],\n",
+ " \"Original FRS error (%)\": [\n",
+ " -93.00,\n",
+ " 0.91,\n",
+ " 13.03,\n",
+ " -58.07,\n",
+ " -30.40,\n",
+ " 4.10,\n",
+ " -6.42,\n",
+ " 8.83,\n",
+ " -1.68,\n",
+ " -3.83,\n",
+ " ],\n",
+ " \"Calibrated FRS error (%)\": [\n",
+ " -0.59,\n",
+ " -2.87,\n",
+ " 6.10,\n",
+ " -0.62,\n",
+ " -0.96,\n",
+ " -2.41,\n",
+ " -1.98,\n",
+ " -1.02,\n",
+ " -6.01,\n",
+ " -0.07,\n",
" ],\n",
- " 'Target total': [1.40, 19.00, 2.23, 0.56, 0.15, 2.20, 3.03, 3.01, 1.90, 0.77],\n",
- " 'Original FRS total': [0.10, 19.17, 2.52, 0.23, 0.10, 2.29, 2.84, 3.28, 1.86, 0.74],\n",
- " 'Calibrated FRS total': [1.39, 18.45, 2.37, 0.56, 0.15, 2.15, 2.97, 2.98, 1.78, 0.77],\n",
- " 'Original FRS error (%)': [-93.00, 0.91, 13.03, -58.07, -30.40, 4.10, -6.42, 8.83, -1.68, -3.83],\n",
- " 'Calibrated FRS error (%)': [-0.59, -2.87, 6.10, -0.62, -0.96, -2.41, -1.98, -1.02, -6.01, -0.07]\n",
"}\n",
"\n",
"from IPython.display import Markdown\n",
+ "\n",
"df = pd.DataFrame(data)\n",
"Markdown(df.to_markdown(index=False))"
]
@@ -1778,16 +2144,25 @@
"from policyengine_core.charts import format_fig\n",
"from policyengine_core.model_api import *\n",
"import pandas as pd\n",
+ "\n",
"# Silence warnings\n",
"import warnings\n",
+ "\n",
"warnings.filterwarnings(\"ignore\")\n",
"import plotly.io as pio\n",
+ "\n",
"pio.renderers.default = \"notebook\"\n",
"\n",
+ "\n",
"def filter_to_ni(sim: Microsimulation):\n",
" weights = sim.calculate(\"household_weight\")\n",
" country = sim.calculate(\"country\")\n",
- " sim.set_input(\"household_weight\", 2023, np.where(country == \"NORTHERN_IRELAND\", weights, 0))\n",
+ " sim.set_input(\n",
+ " \"household_weight\",\n",
+ " 2023,\n",
+ " np.where(country == \"NORTHERN_IRELAND\", weights, 0),\n",
+ " )\n",
+ "\n",
"\n",
"baseline = Microsimulation()\n",
"filter_to_ni(baseline)\n",
@@ -1798,16 +2173,24 @@
" child_amount: float = 200 * 12,\n",
" senior_amount: float = 100 * 12,\n",
"):\n",
- " in_ni = baseline.calculate(\"country\", map_to=\"person\") == \"NORTHERN_IRELAND\"\n",
+ " in_ni = (\n",
+ " baseline.calculate(\"country\", map_to=\"person\") == \"NORTHERN_IRELAND\"\n",
+ " )\n",
" age = baseline.calculate(\"age\")\n",
" retired = baseline.calculate(\"is_SP_age\")\n",
" num_adults = ((age >= 16) * (~retired))[in_ni].sum()\n",
" num_children = (age < 16)[in_ni].sum()\n",
" num_seniors = (retired)[in_ni].sum()\n",
- " return adult_amount * num_adults + child_amount * num_children + senior_amount * num_seniors\n",
+ " return (\n",
+ " adult_amount * num_adults\n",
+ " + child_amount * num_children\n",
+ " + senior_amount * num_seniors\n",
+ " )\n",
+ "\n",
"\n",
"from policyengine_uk.model_api import *\n",
"\n",
+ "\n",
"def ubi_ni_reform(\n",
" personal_allowance: float = 12_570,\n",
" higher_rate_threshold: float = 50_270,\n",
@@ -1817,20 +2200,35 @@
" child_bi: float = 0,\n",
" adult_bi: float = 0,\n",
" senior_bi: float = 0,\n",
- "\n",
") -> Reform:\n",
" TIME_PERIOD = \"year:2023:10\"\n",
- " return Reform.from_dict({\n",
- " \"gov.hmrc.income_tax.rates.uk[0].rate\": { TIME_PERIOD: basic_rate },\n",
- " \"gov.hmrc.income_tax.rates.uk[1].rate\": { TIME_PERIOD: higher_rate },\n",
- " \"gov.hmrc.income_tax.rates.uk[2].rate\": { TIME_PERIOD: additional_rate },\n",
- " \"gov.hmrc.income_tax.rates.uk[1].threshold\": { TIME_PERIOD: float(higher_rate_threshold) },\n",
- " \"gov.hmrc.income_tax.allowances.personal_allowance.amount\": { TIME_PERIOD: float(personal_allowance) },\n",
- " \"gov.contrib.ubi_center.basic_income.amount.by_age.child\": { TIME_PERIOD: child_bi / 52 },\n",
- " # \"gov.contrib.ubi_center.basic_income.amount.child_min_age\": { TIME_PERIOD: 16 },\n",
- " \"gov.contrib.ubi_center.basic_income.amount.by_age.working_age\": { TIME_PERIOD: adult_bi / 52 },\n",
- " \"gov.contrib.ubi_center.basic_income.amount.by_age.senior\": { TIME_PERIOD: senior_bi / 52 },\n",
- " }, country_id=\"uk\")\n",
+ " return Reform.from_dict(\n",
+ " {\n",
+ " \"gov.hmrc.income_tax.rates.uk[0].rate\": {TIME_PERIOD: basic_rate},\n",
+ " \"gov.hmrc.income_tax.rates.uk[1].rate\": {TIME_PERIOD: higher_rate},\n",
+ " \"gov.hmrc.income_tax.rates.uk[2].rate\": {\n",
+ " TIME_PERIOD: additional_rate\n",
+ " },\n",
+ " \"gov.hmrc.income_tax.rates.uk[1].threshold\": {\n",
+ " TIME_PERIOD: float(higher_rate_threshold)\n",
+ " },\n",
+ " \"gov.hmrc.income_tax.allowances.personal_allowance.amount\": {\n",
+ " TIME_PERIOD: float(personal_allowance)\n",
+ " },\n",
+ " \"gov.contrib.ubi_center.basic_income.amount.by_age.child\": {\n",
+ " TIME_PERIOD: child_bi / 52\n",
+ " },\n",
+ " # \"gov.contrib.ubi_center.basic_income.amount.child_min_age\": { TIME_PERIOD: 16 },\n",
+ " \"gov.contrib.ubi_center.basic_income.amount.by_age.working_age\": {\n",
+ " TIME_PERIOD: adult_bi / 52\n",
+ " },\n",
+ " \"gov.contrib.ubi_center.basic_income.amount.by_age.senior\": {\n",
+ " TIME_PERIOD: senior_bi / 52\n",
+ " },\n",
+ " },\n",
+ " country_id=\"uk\",\n",
+ " )\n",
+ "\n",
"\n",
"def get_revenue_slow_estimate(\n",
" personal_allowance: float,\n",
@@ -1842,7 +2240,7 @@
" reformed = Microsimulation(\n",
" reform=ubi_ni_reform(\n",
" personal_allowance=personal_allowance,\n",
- " higher_rate_threshold = 50_270 + 12_570 - personal_allowance,\n",
+ " higher_rate_threshold=50_270 + 12_570 - personal_allowance,\n",
" basic_rate=0.2 + basic_rate_addition,\n",
" higher_rate=0.4 + higher_rate_addition,\n",
" additional_rate=0.45 + additional_rate_addition,\n",
@@ -1853,7 +2251,11 @@
" )\n",
" filter_to_ni(baseline)\n",
" filter_to_ni(reformed)\n",
- " return baseline.calculate(\"household_net_income\").sum() - reformed.calculate(\"household_net_income\").sum()\n",
+ " return (\n",
+ " baseline.calculate(\"household_net_income\").sum()\n",
+ " - reformed.calculate(\"household_net_income\").sum()\n",
+ " )\n",
+ "\n",
"\n",
"def get_revenue_quick_estimate(\n",
" personal_allowance: float,\n",
@@ -1865,16 +2267,33 @@
" weight = income.weights\n",
" add_rate_income = np.maximum(0, income - 125_140)\n",
" higher_rate_income = np.maximum(0, income - 50_270) - add_rate_income\n",
- " basic_rate_income_b = np.maximum(0, income - higher_rate_income - add_rate_income - 12_570)\n",
- " basic_rate_income_r = np.maximum(0, income - higher_rate_income - add_rate_income - personal_allowance)\n",
+ " basic_rate_income_b = np.maximum(\n",
+ " 0, income - higher_rate_income - add_rate_income - 12_570\n",
+ " )\n",
+ " basic_rate_income_r = np.maximum(\n",
+ " 0, income - higher_rate_income - add_rate_income - personal_allowance\n",
+ " )\n",
" basic_rate_increased_tax = (\n",
" basic_rate_income_r * (0.2 + basic_rate_addition)\n",
" - basic_rate_income_b * 0.2\n",
" )\n",
- " higher_rate_increased_tax = baseline.calculate(\"higher_rate_earned_income\").values * higher_rate_addition\n",
- " additional_rate_increased_tax = baseline.calculate(\"add_rate_earned_income\").values * additional_rate_addition\n",
- " increased_tax = basic_rate_increased_tax + higher_rate_increased_tax + additional_rate_increased_tax\n",
- " return (increased_tax * weight).sum() * 0.91 # Rough benefit response ratio\n",
+ " higher_rate_increased_tax = (\n",
+ " baseline.calculate(\"higher_rate_earned_income\").values\n",
+ " * higher_rate_addition\n",
+ " )\n",
+ " additional_rate_increased_tax = (\n",
+ " baseline.calculate(\"add_rate_earned_income\").values\n",
+ " * additional_rate_addition\n",
+ " )\n",
+ " increased_tax = (\n",
+ " basic_rate_increased_tax\n",
+ " + higher_rate_increased_tax\n",
+ " + additional_rate_increased_tax\n",
+ " )\n",
+ " return (\n",
+ " increased_tax * weight\n",
+ " ).sum() * 0.91 # Rough benefit response ratio\n",
+ "\n",
"\n",
"def solve_rates(\n",
" adult_ubi: float = 0,\n",
@@ -1885,14 +2304,17 @@
" def adjusted_reform_cost(\n",
" basic_rate_addition: float = 0,\n",
" ) -> float:\n",
- " adjusted_revenue = get_revenue_quick_estimate(personal_allowance, basic_rate_addition, basic_rate_addition * higher_rate_ratio, basic_rate_addition * additional_rate_ratio)\n",
- " ubi_cost = get_ubi_cost(\n",
- " adult_ubi, \n",
- " adult_ubi * 0.5,\n",
- " adult_ubi\n",
+ " adjusted_revenue = get_revenue_quick_estimate(\n",
+ " personal_allowance,\n",
+ " basic_rate_addition,\n",
+ " basic_rate_addition * higher_rate_ratio,\n",
+ " basic_rate_addition * additional_rate_ratio,\n",
" )\n",
- " return ubi_cost * 0.75 - adjusted_revenue # Assume 25% of the UBI is paid for by Westminster\n",
- " \n",
+ " ubi_cost = get_ubi_cost(adult_ubi, adult_ubi * 0.5, adult_ubi)\n",
+ " return (\n",
+ " ubi_cost * 0.75 - adjusted_revenue\n",
+ " ) # Assume 25% of the UBI is paid for by Westminster\n",
+ "\n",
" return bisect(adjusted_reform_cost, -1, 1, xtol=1e-3)\n",
"\n",
"\n",
@@ -1906,20 +2328,33 @@
" for higher_add_ratio in list((0, 1, 2)):\n",
" headline_ubi_rates.append(ubi_rate)\n",
" personal_allowances.append(pa)\n",
- " basic_rate_additions.append(solve_rates(adult_ubi=ubi_rate, personal_allowance=pa, higher_rate_ratio=higher_add_ratio, additional_rate_ratio=higher_add_ratio))\n",
+ " basic_rate_additions.append(\n",
+ " solve_rates(\n",
+ " adult_ubi=ubi_rate,\n",
+ " personal_allowance=pa,\n",
+ " higher_rate_ratio=higher_add_ratio,\n",
+ " additional_rate_ratio=higher_add_ratio,\n",
+ " )\n",
+ " )\n",
" higher_add_addition_ratios.append(higher_add_ratio)\n",
"\n",
"\n",
- "df = pd.DataFrame({\n",
- " \"Adult UBI\": headline_ubi_rates,\n",
- " \"Personal Allowance\": personal_allowances,\n",
- " \"Basic rate addition\": basic_rate_additions,\n",
- " \"Higher/additional rate increase ratio\": higher_add_addition_ratios,\n",
- "})\n",
+ "df = pd.DataFrame(\n",
+ " {\n",
+ " \"Adult UBI\": headline_ubi_rates,\n",
+ " \"Personal Allowance\": personal_allowances,\n",
+ " \"Basic rate addition\": basic_rate_additions,\n",
+ " \"Higher/additional rate increase ratio\": higher_add_addition_ratios,\n",
+ " }\n",
+ ")\n",
"df[\"Personal Allowance reduction\"] = 12_570 - df[\"Personal Allowance\"]\n",
- "df[\"Higher rate addition\"] = df[\"Basic rate addition\"] * df[\"Higher/additional rate increase ratio\"]\n",
+ "df[\"Higher rate addition\"] = (\n",
+ " df[\"Basic rate addition\"] * df[\"Higher/additional rate increase ratio\"]\n",
+ ")\n",
"df[\"Basic rate addition (x100)\"] = df[\"Basic rate addition\"] * 100\n",
- "df[\"Additional rate addition\"] = df[\"Basic rate addition\"] * df[\"Higher/additional rate increase ratio\"]\n",
+ "df[\"Additional rate addition\"] = (\n",
+ " df[\"Basic rate addition\"] * df[\"Higher/additional rate increase ratio\"]\n",
+ ")\n",
"df[\"UBI/Increase ratio combination\"] = [\n",
" f\"{higher_add_ratio:.0%}\" + \" \" * (ubi // 100)\n",
" for ubi, higher_add_ratio in zip(\n",
@@ -1944,22 +2379,26 @@
"df[\"Higher rate\"] = df[\"Higher rate addition\"] + 0.4\n",
"df[\"Add. rate\"] = df[\"Additional rate addition\"] + 0.45\n",
"\n",
- "df[[\n",
- " \"Adult UBI\",\n",
- " \"Personal Allowance\",\n",
- " \"Basic rate addition\",\n",
- " \"Higher/additional rate increase ratio\",\n",
- "]].to_csv(\"table.csv\", index=False)\n",
+ "df[\n",
+ " [\n",
+ " \"Adult UBI\",\n",
+ " \"Personal Allowance\",\n",
+ " \"Basic rate addition\",\n",
+ " \"Higher/additional rate increase ratio\",\n",
+ " ]\n",
+ "].to_csv(\"table.csv\", index=False)\n",
"df[\"Feasible MTR\"] = np.where(\n",
- " # £150k+ MTR under 100%\n",
- " (df[\"Add. rate\"] + 0.0325 < 1)\n",
- " # £100k-150k MTR under 100%\n",
- " & (df[\"Higher rate\"] * (\n",
- " np.where(df[\"Personal Allowance\"] > 0, 1.5, 1)\n",
- " ) + 0.0325 < 1),\n",
- " \"Yes\",\n",
- " \"No\",\n",
- " )\n",
+ " # £150k+ MTR under 100%\n",
+ " (df[\"Add. rate\"] + 0.0325 < 1)\n",
+ " # £100k-150k MTR under 100%\n",
+ " & (\n",
+ " df[\"Higher rate\"] * (np.where(df[\"Personal Allowance\"] > 0, 1.5, 1))\n",
+ " + 0.0325\n",
+ " < 1\n",
+ " ),\n",
+ " \"Yes\",\n",
+ " \"No\",\n",
+ ")\n",
"\n",
"df.to_csv(\"experiment_results.csv\")\n",
"\n",
@@ -1976,7 +2415,7 @@
" additional_rate=model_1_row[\"Add. rate\"],\n",
" personal_allowance=model_1_row[\"Personal Allowance\"],\n",
" adult_bi=model_1_row[\"Adult UBI\"],\n",
- " higher_rate_threshold=50_270. - model_1_row[\"Personal Allowance\"],\n",
+ " higher_rate_threshold=50_270.0 - model_1_row[\"Personal Allowance\"],\n",
" child_bi=model_1_row[\"Adult UBI\"] * 0.5,\n",
" senior_bi=model_1_row[\"Adult UBI\"],\n",
")\n",
@@ -1987,7 +2426,7 @@
" additional_rate=model_2_row[\"Add. rate\"],\n",
" personal_allowance=model_2_row[\"Personal Allowance\"],\n",
" adult_bi=model_2_row[\"Adult UBI\"],\n",
- " higher_rate_threshold=50_270. - model_2_row[\"Personal Allowance\"],\n",
+ " higher_rate_threshold=50_270.0 - model_2_row[\"Personal Allowance\"],\n",
" child_bi=model_2_row[\"Adult UBI\"] * 0.5,\n",
" senior_bi=model_2_row[\"Adult UBI\"],\n",
")\n",
@@ -1998,7 +2437,7 @@
" additional_rate=model_3_row[\"Add. rate\"],\n",
" personal_allowance=model_3_row[\"Personal Allowance\"],\n",
" adult_bi=model_3_row[\"Adult UBI\"],\n",
- " higher_rate_threshold=50_270. - model_3_row[\"Personal Allowance\"],\n",
+ " higher_rate_threshold=50_270.0 - model_3_row[\"Personal Allowance\"],\n",
" child_bi=model_3_row[\"Adult UBI\"] * 0.5,\n",
" senior_bi=model_3_row[\"Adult UBI\"],\n",
")\n",
@@ -2018,14 +2457,39 @@
"summary_table_original = summary_table.copy()\n",
"\n",
"summary_table = pd.DataFrame()\n",
- "summary_table[\"Monthly UBI amount for adults\"] = [model_1_row[\"Adult UBI\"] / 12, model_2_row[\"Adult UBI\"] / 12, model_3_row[\"Adult UBI\"] / 12]\n",
- "summary_table[\"Monthly UBI amount for children\"] = [model_1_row[\"Adult UBI\"] / 12 * 0.5, model_2_row[\"Adult UBI\"] / 12 * 0.5, model_3_row[\"Adult UBI\"] / 12 * 0.5]\n",
- "summary_table[\"Personal Allowance\"] = [model_1_row[\"Personal Allowance\"], model_2_row[\"Personal Allowance\"], model_3_row[\"Personal Allowance\"]]\n",
- "summary_table[\"Basic rate increase\"] = [model_1_row[\"Basic rate addition\"], model_2_row[\"Basic rate addition\"], model_3_row[\"Basic rate addition\"]]\n",
- "summary_table[\"Higher rate increase\"] = [model_1_row[\"Higher rate addition\"], model_2_row[\"Higher rate addition\"], model_3_row[\"Higher rate addition\"]]\n",
- "summary_table[\"Additional rate increase\"] = [model_1_row[\"Additional rate addition\"], model_2_row[\"Additional rate addition\"], model_3_row[\"Additional rate addition\"]]\n",
+ "summary_table[\"Monthly UBI amount for adults\"] = [\n",
+ " model_1_row[\"Adult UBI\"] / 12,\n",
+ " model_2_row[\"Adult UBI\"] / 12,\n",
+ " model_3_row[\"Adult UBI\"] / 12,\n",
+ "]\n",
+ "summary_table[\"Monthly UBI amount for children\"] = [\n",
+ " model_1_row[\"Adult UBI\"] / 12 * 0.5,\n",
+ " model_2_row[\"Adult UBI\"] / 12 * 0.5,\n",
+ " model_3_row[\"Adult UBI\"] / 12 * 0.5,\n",
+ "]\n",
+ "summary_table[\"Personal Allowance\"] = [\n",
+ " model_1_row[\"Personal Allowance\"],\n",
+ " model_2_row[\"Personal Allowance\"],\n",
+ " model_3_row[\"Personal Allowance\"],\n",
+ "]\n",
+ "summary_table[\"Basic rate increase\"] = [\n",
+ " model_1_row[\"Basic rate addition\"],\n",
+ " model_2_row[\"Basic rate addition\"],\n",
+ " model_3_row[\"Basic rate addition\"],\n",
+ "]\n",
+ "summary_table[\"Higher rate increase\"] = [\n",
+ " model_1_row[\"Higher rate addition\"],\n",
+ " model_2_row[\"Higher rate addition\"],\n",
+ " model_3_row[\"Higher rate addition\"],\n",
+ "]\n",
+ "summary_table[\"Additional rate increase\"] = [\n",
+ " model_1_row[\"Additional rate addition\"],\n",
+ " model_2_row[\"Additional rate addition\"],\n",
+ " model_3_row[\"Additional rate addition\"],\n",
+ "]\n",
"summary_table[\"Net cost\"] = [\n",
- " sim.calculate(\"household_net_income\").sum() - baseline.calculate(\"household_net_income\").sum()\n",
+ " sim.calculate(\"household_net_income\").sum()\n",
+ " - baseline.calculate(\"household_net_income\").sum()\n",
" for sim in [model_1_sim, model_2_sim, model_3_sim]\n",
"]\n",
"summary_table[\"Revenue raised\"] = [\n",
@@ -2033,19 +2497,27 @@
" for sim in [model_1_sim, model_2_sim, model_3_sim]\n",
"]\n",
"summary_table[\"Benefit outlay increase\"] = [\n",
- " sim.calculate(\"household_benefits\").sum() - baseline.calculate(\"household_benefits\").sum()\n",
+ " sim.calculate(\"household_benefits\").sum()\n",
+ " - baseline.calculate(\"household_benefits\").sum()\n",
" for sim in [model_1_sim, model_2_sim, model_3_sim]\n",
"]\n",
"summary_table[\"Poverty impact\"] = [\n",
- " sim.calculate(\"in_poverty\", map_to=\"person\").sum() / baseline.calculate(\"in_poverty\", map_to=\"person\").sum() - 1\n",
+ " sim.calculate(\"in_poverty\", map_to=\"person\").sum()\n",
+ " / baseline.calculate(\"in_poverty\", map_to=\"person\").sum()\n",
+ " - 1\n",
" for sim in [model_1_sim, model_2_sim, model_3_sim]\n",
"]\n",
"summary_table[\"Inequality impact\"] = [\n",
- " sim.calculate(\"equiv_household_net_income\", map_to=\"person\").gini() / baseline.calculate(\"equiv_household_net_income\", map_to=\"person\").gini() - 1\n",
+ " sim.calculate(\"equiv_household_net_income\", map_to=\"person\").gini()\n",
+ " / baseline.calculate(\"equiv_household_net_income\", map_to=\"person\").gini()\n",
+ " - 1\n",
" for sim in [model_1_sim, model_2_sim, model_3_sim]\n",
"]\n",
"summary_table[\"Percent of population gaining\"] = [\n",
- " (sim.calculate(\"household_net_income\", map_to=\"person\") > baseline.calculate(\"household_net_income\", map_to=\"person\")).mean()\n",
+ " (\n",
+ " sim.calculate(\"household_net_income\", map_to=\"person\")\n",
+ " > baseline.calculate(\"household_net_income\", map_to=\"person\")\n",
+ " ).mean()\n",
" for sim in [model_1_sim, model_2_sim, model_3_sim]\n",
"]\n",
"\n",
@@ -2053,29 +2525,62 @@
"\n",
"# Formatting\n",
"\n",
- "summary_table[\"Monthly UBI amount for adults\"] = summary_table[\"Monthly UBI amount for adults\"].map(lambda x: f\"£{x:,.0f}\")\n",
- "summary_table[\"Monthly UBI amount for children\"] = summary_table[\"Monthly UBI amount for children\"].map(lambda x: f\"£{x:,.0f}\")\n",
- "summary_table[\"Personal Allowance\"] = summary_table[\"Personal Allowance\"].map(lambda x: f\"£{x:,.0f}\")\n",
- "summary_table[\"Basic rate increase\"] = summary_table[\"Basic rate increase\"].map(lambda x: f\"{x * 100:.1f}p\")\n",
- "summary_table[\"Higher rate increase\"] = summary_table[\"Higher rate increase\"].map(lambda x: f\"{x * 100:.1f}p\")\n",
- "summary_table[\"Additional rate increase\"] = summary_table[\"Additional rate increase\"].map(lambda x: f\"{x * 100:.1f}p\")\n",
- "summary_table[\"Westminster contribution\"] = summary_table[\"Net cost\"] / summary_table[\"Benefit outlay increase\"]\n",
- "summary_table[\"Westminster contribution\"] = summary_table[\"Westminster contribution\"].map(lambda x: f\"{x:.1%}\")\n",
- "summary_table[\"Net cost\"] = summary_table[\"Net cost\"].map(lambda x: f\"£{x/1e9:,.1f}bn\")\n",
- "summary_table[\"Revenue raised\"] = summary_table[\"Revenue raised\"].map(lambda x: f\"£{x/1e9:,.1f}bn\")\n",
- "summary_table[\"Benefit outlay increase\"] = summary_table[\"Benefit outlay increase\"].map(lambda x: f\"£{x/1e9:,.1f}bn\")\n",
- "summary_table[\"Poverty impact\"] = summary_table[\"Poverty impact\"].map(lambda x: f\"{x * 100:+.1f}%\")\n",
- "summary_table[\"Inequality impact\"] = summary_table[\"Inequality impact\"].map(lambda x: f\"{x * 100:+.1f}%\")\n",
- "summary_table[\"Percent of population gaining\"] = summary_table[\"Percent of population gaining\"].map(lambda x: f\"{x * 100:.0f}%\")\n",
+ "summary_table[\"Monthly UBI amount for adults\"] = summary_table[\n",
+ " \"Monthly UBI amount for adults\"\n",
+ "].map(lambda x: f\"£{x:,.0f}\")\n",
+ "summary_table[\"Monthly UBI amount for children\"] = summary_table[\n",
+ " \"Monthly UBI amount for children\"\n",
+ "].map(lambda x: f\"£{x:,.0f}\")\n",
+ "summary_table[\"Personal Allowance\"] = summary_table[\"Personal Allowance\"].map(\n",
+ " lambda x: f\"£{x:,.0f}\"\n",
+ ")\n",
+ "summary_table[\"Basic rate increase\"] = summary_table[\n",
+ " \"Basic rate increase\"\n",
+ "].map(lambda x: f\"{x * 100:.1f}p\")\n",
+ "summary_table[\"Higher rate increase\"] = summary_table[\n",
+ " \"Higher rate increase\"\n",
+ "].map(lambda x: f\"{x * 100:.1f}p\")\n",
+ "summary_table[\"Additional rate increase\"] = summary_table[\n",
+ " \"Additional rate increase\"\n",
+ "].map(lambda x: f\"{x * 100:.1f}p\")\n",
+ "summary_table[\"Westminster contribution\"] = (\n",
+ " summary_table[\"Net cost\"] / summary_table[\"Benefit outlay increase\"]\n",
+ ")\n",
+ "summary_table[\"Westminster contribution\"] = summary_table[\n",
+ " \"Westminster contribution\"\n",
+ "].map(lambda x: f\"{x:.1%}\")\n",
+ "summary_table[\"Net cost\"] = summary_table[\"Net cost\"].map(\n",
+ " lambda x: f\"£{x/1e9:,.1f}bn\"\n",
+ ")\n",
+ "summary_table[\"Revenue raised\"] = summary_table[\"Revenue raised\"].map(\n",
+ " lambda x: f\"£{x/1e9:,.1f}bn\"\n",
+ ")\n",
+ "summary_table[\"Benefit outlay increase\"] = summary_table[\n",
+ " \"Benefit outlay increase\"\n",
+ "].map(lambda x: f\"£{x/1e9:,.1f}bn\")\n",
+ "summary_table[\"Poverty impact\"] = summary_table[\"Poverty impact\"].map(\n",
+ " lambda x: f\"{x * 100:+.1f}%\"\n",
+ ")\n",
+ "summary_table[\"Inequality impact\"] = summary_table[\"Inequality impact\"].map(\n",
+ " lambda x: f\"{x * 100:+.1f}%\"\n",
+ ")\n",
+ "summary_table[\"Percent of population gaining\"] = summary_table[\n",
+ " \"Percent of population gaining\"\n",
+ "].map(lambda x: f\"{x * 100:.0f}%\")\n",
"\n",
"policy_ids = [\n",
" reform.api_id\n",
" for reform in [model_1_reform, model_2_reform, model_3_reform]\n",
"]\n",
"summary_table[\"PolicyEngine link\"] = [\n",
- " f\"#{policy_id}\" for policy_id in policy_ids\n",
+ " f'#{policy_id}'\n",
+ " for policy_id in policy_ids\n",
+ "]\n",
+ "summary_table.index = [\n",
+ " \"Model 1 (+ children)\",\n",
+ " \"Model 2 (+ children)\",\n",
+ " \"Model 3 (+ children)\",\n",
"]\n",
- "summary_table.index = [\"Model 1 (+ children)\", \"Model 2 (+ children)\", \"Model 3 (+ children)\"]\n",
"\n",
"from IPython.display import Markdown\n",
"\n",
diff --git a/tools/create_social_card_image.py b/tools/create_social_card_image.py
index 6d1d4d41e..39b881508 100644
--- a/tools/create_social_card_image.py
+++ b/tools/create_social_card_image.py
@@ -5,6 +5,7 @@
ROBOTO_SERIF_FONT = "./tools/RobotoSerif.ttf"
SIZE_MULTIPLIER = 2
+
def create_social_card(
title: str,
image_path: str,
@@ -13,7 +14,6 @@ def create_social_card(
# Create a social card with the given title and image.
# Put a blue bar at the bottom overlaid with the title.
-
# Load the image. 800px by 418px is the ideal size.
image = Image.open(image_path)
image_width, image_height = image.size
@@ -27,16 +27,28 @@ def create_social_card(
# Too tall
new_width = 800
new_height = int(800 / aspect_ratio)
- image = image.resize((new_width * SIZE_MULTIPLIER, new_height * SIZE_MULTIPLIER))
+ image = image.resize(
+ (new_width * SIZE_MULTIPLIER, new_height * SIZE_MULTIPLIER)
+ )
# Create a blank canvas
- canvas = Image.new('RGB', (800 * SIZE_MULTIPLIER, 418 * SIZE_MULTIPLIER), color = (255, 255, 255))
+ canvas = Image.new(
+ "RGB",
+ (800 * SIZE_MULTIPLIER, 418 * SIZE_MULTIPLIER),
+ color=(255, 255, 255),
+ )
# Paste the image
canvas.paste(image, (0, 0))
# Create a blue bar at the bottom
draw = ImageDraw.Draw(canvas)
- draw.rectangle([(0 * SIZE_MULTIPLIER, (418 - 70) * SIZE_MULTIPLIER), (800 * SIZE_MULTIPLIER, 418 * SIZE_MULTIPLIER)], fill=(44, 100, 150))
+ draw.rectangle(
+ [
+ (0 * SIZE_MULTIPLIER, (418 - 70) * SIZE_MULTIPLIER),
+ (800 * SIZE_MULTIPLIER, 418 * SIZE_MULTIPLIER),
+ ],
+ fill=(44, 100, 150),
+ )
# Load a font- Roboto Serif
font = ImageFont.truetype(ROBOTO_SERIF_FONT, 25 * SIZE_MULTIPLIER)
@@ -44,18 +56,24 @@ def create_social_card(
_, text_height = draw.textsize(title, font=font)
# Calculate the position of the text. Should be inside the box
text_x = 20 * SIZE_MULTIPLIER
- text_y = ((418 - 70) * SIZE_MULTIPLIER + (70 * SIZE_MULTIPLIER - text_height) / 2)
- # Draw the text with colour
+ text_y = (418 - 70) * SIZE_MULTIPLIER + (
+ 70 * SIZE_MULTIPLIER - text_height
+ ) / 2
+ # Draw the text with colour
draw.text((text_x, text_y), title, font=font)
# Add the logo to the bottom right
logo = Image.open(LOGO)
logo = logo.resize((60 * SIZE_MULTIPLIER, 60 * SIZE_MULTIPLIER))
- canvas.paste(logo, ((800 - 60 - 20) * SIZE_MULTIPLIER, (418 - 45 - 20) * SIZE_MULTIPLIER))
+ canvas.paste(
+ logo,
+ ((800 - 60 - 20) * SIZE_MULTIPLIER, (418 - 45 - 20) * SIZE_MULTIPLIER),
+ )
# Save the image
canvas.save(output_path)
+
if __name__ == "__main__":
parser = ArgumentParser()
# Args: title, image_path, output_path
@@ -64,4 +82,4 @@ def create_social_card(
parser.add_argument("output_path", type=str)
args = parser.parse_args()
- create_social_card(args.title, args.image_path, args.output_path)
\ No newline at end of file
+ create_social_card(args.title, args.image_path, args.output_path)