From 1ea72bfaf661990c678d8387eb47fd3d1ffb8d40 Mon Sep 17 00:00:00 2001 From: Nikhil Woodruff <35577657+nikhilwoodruff@users.noreply.github.com> Date: Sat, 4 Nov 2023 13:06:04 -0600 Subject: [PATCH] Format Python script --- gcp/social_card_tags.py | 10 +- src/posts/northern-ireland-feasibility.ipynb | 1217 +++++++++++++----- tools/create_social_card_image.py | 34 +- 3 files changed, 894 insertions(+), 367 deletions(-) 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)