From f59f3ade209e796f98f1d3810bb0d59fbe92286b Mon Sep 17 00:00:00 2001 From: Nikhil Woodruff Date: Tue, 19 Nov 2024 13:34:58 +0000 Subject: [PATCH 1/3] Move to pyproject.tomk --- README.md | 1 + pyproject.toml | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++ setup.py | 19 ---------------- 3 files changed, 62 insertions(+), 19 deletions(-) create mode 100644 pyproject.toml delete mode 100644 setup.py diff --git a/README.md b/README.md index 9df7622..693fb49 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,3 @@ # policyengine.py + [WIP] PolicyEngine's main user-facing Python package, incorporating country packages and integrating data visualization and analytics. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..8b26a60 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,61 @@ +[build-system] +requires = ["setuptools>=45", "wheel", "setuptools_scm[toml]>=6.2"] +build-backend = "setuptools.build_meta" + +[project] +name = "policyengine" +version = "1.0.0" +description = "A package to conduct policy analysis using PolicyEngine tax-benefit models." +readme = "README.md" +authors = [ + {name = "PolicyEngine", email = "hello@policyengine.org"}, +] +license = {file = "LICENSE"} +requires-python = ">=3.6" +dependencies = [ + "policyengine_core", + "policyengine-uk", + "policyengine-us", +] + +[project.optional-dependencies] +dev = [ + "black", + "pytest", + "furo", + "jupyter-book", + "yaml-changelog>=0.1.7", + "itables", + "build", +] + +[tool.setuptools] +packages = ["policyengine"] +include-package-data = true + +[tool.setuptools.package-data] +"policyengine" = ["**/*"] + +[tool.pytest.ini_options] +addopts = "-v" +testpaths = [ + "tests", +] + +[tool.black] +line-length = 79 +target-version = ['py311'] +include = '\.pyi?$' +extend-exclude = ''' +/( + # directories + \.eggs + | \.git + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | build + | dist +)/ +''' \ No newline at end of file diff --git a/setup.py b/setup.py deleted file mode 100644 index 4dc5482..0000000 --- a/setup.py +++ /dev/null @@ -1,19 +0,0 @@ -from setuptools import setup, find_packages - -setup( - name='policyengine', - version='0.1.0', - packages=find_packages(), - install_requires=[ - 'policyengine_us', - 'policyengine_uk', - #'policyengine_canada' - ], - extras_require={ - "dev": [ - "black", - "jupyter-book", - "pytest" - ] - } -) \ No newline at end of file From ba3495327483dd650b186c1987f5ee73198bc3d9 Mon Sep 17 00:00:00 2001 From: Nikhil Woodruff Date: Wed, 20 Nov 2024 11:34:07 +0000 Subject: [PATCH 2/3] Add early prototype --- .gitignore | 2 + policyengine/__init__.py | 2 +- policyengine/charts/__init__.py | 0 .../charts/distributional_impact/__init__.py | 0 .../by_income_decile/__init__.py | 0 .../by_income_decile/average.py | 114 ------- .../by_income_decile/relative.py | 104 ------ .../by_wealth_decile/__init__.py | 0 .../by_wealth_decile/average.py | 114 ------- .../by_wealth_decile/relative.py | 105 ------ policyengine/charts/inequality.py | 65 ---- policyengine/charts/poverty/__init__.py | 0 policyengine/charts/poverty/deep/__init__.py | 0 policyengine/charts/poverty/deep/by_age.py | 107 ------- policyengine/charts/poverty/deep/by_gender.py | 103 ------ .../charts/poverty/regular/__init__.py | 0 policyengine/charts/poverty/regular/by_age.py | 107 ------- .../charts/poverty/regular/by_gender.py | 103 ------ policyengine/economic_impact/__init__.py | 0 .../economic_impact/base_metric_calculator.py | 54 ---- .../budgetary_impact/__init__.py | 0 .../budgetary_impact/by_program/__init__.py | 0 .../budgetary_impact/by_program/by_program.py | 192 ----------- .../budgetary_impact/overall/__init__.py | 0 .../budgetary_impact/overall/overall.py | 70 ---- .../distributional_impact/__init__.py | 0 .../by_income_decile/__init__.py | 0 .../by_income_decile/average/__init__.py | 0 .../by_income_decile/average/average.py | 37 --- .../by_income_decile/relative/__init__.py | 0 .../by_income_decile/relative/relative.py | 38 --- .../by_wealth_decile/__init__.py | 0 .../by_wealth_decile/average/__init__.py | 0 .../by_wealth_decile/average/average.py | 37 --- .../by_wealth_decile/relative/__init__.py | 0 .../by_wealth_decile/relative/relative.py | 38 --- .../economic_impact/economic_impact.py | 300 ------------------ .../inequality_impact/__init__.py | 0 .../inequality_impact/inequality_impact.py | 164 ---------- .../labour_supply_impact/__init__.py | 0 .../earnings/by_decile/__init__.py | 0 .../earnings/by_decile/absolute/__init__.py | 0 .../absolute/income_effect/__init__.py | 0 .../absolute/income_effect/income_effect.py | 93 ------ .../absolute/substitution_effect/__init__.py | 0 .../substitution_effect.py | 92 ------ .../by_decile/absolute/total/__init__.py | 0 .../by_decile/absolute/total/total.py | 203 ------------ .../earnings/by_decile/relative/__init__.py | 0 .../relative/income_effect/__init__.py | 0 .../relative/income_effect/income_effect.py | 97 ------ .../relative/substitution_effect/__init__.py | 0 .../substitutional_effect.py | 96 ------ .../by_decile/relative/total/__init__.py | 0 .../by_decile/relative/total/total.py | 215 ------------- .../earnings/overall/__init__.py | 0 .../earnings/overall/absolute/__init__.py | 0 .../earnings/overall/absolute/absolute.py | 77 ----- .../earnings/overall/relative/__init__.py | 0 .../earnings/overall/relative/relative.py | 143 --------- .../poverty_impact/__init__.py | 0 .../poverty_impact/deep_poverty/__init__.py | 0 .../deep_poverty/by_age/__init__.py | 0 .../deep_poverty/by_age/by_age.py | 89 ------ .../deep_poverty/by_gender/__init__.py | 0 .../deep_poverty/by_gender/by_gender.py | 67 ---- .../regular_poverty/__init__.py | 0 .../regular_poverty/by_age/__init__.py | 0 .../regular_poverty/by_age/by_age.py | 89 ------ .../regular_poverty/by_gender/__init__.py | 0 .../regular_poverty/by_gender/by_gender.py | 67 ---- .../winners_and_losers/__init__.py | 0 .../by_income_decile/__init__.py | 0 .../by_income_decile/by_income_decile.py | 55 ---- .../by_wealth_decile/__init__.py | 0 .../by_wealth_decile/by_wealth_decile.py | 55 ---- .../outputs/macro/impact/revenue_impact.py | 12 + policyengine/simulation.py | 106 +++++++ .../by_program/by_program.yaml | 100 ------ .../by_program/test_by_program.py | 53 ---- .../budgetary_impact/overall/overall.yaml | 28 -- .../budgetary_impact/overall/test_overall.py | 39 --- .../by_income/average/average.yaml | 19 -- .../by_income/average/test_average.py | 38 --- .../by_income/relative/relative.yaml | 19 -- .../by_income/relative/test_relative.py | 38 --- .../by_wealth/average/average.yaml | 18 -- .../by_wealth/average/test_average.py | 38 --- .../by_wealth/relative/relative.yaml | 18 -- .../by_wealth/relative/test_relative.py | 38 --- .../inequality_impact.py/inequality_impact.py | 39 --- .../inequality_impact.yaml | 33 -- .../deep_poverty/by_age/by_age.yaml | 40 --- .../deep_poverty/by_age/test_deep_by_age.py | 41 --- .../deep_poverty/by_gender/by_gender.yaml | 30 -- .../by_gender/test_deep_by_gender.py | 41 --- .../regular_poverty/by_age/by_age.yaml | 40 --- .../regular_poverty/by_age/test_by_age.py | 41 --- .../regular_poverty/by_gender/by_gender.yaml | 30 -- .../by_gender/test_by_gender.py | 39 --- .../by_income_decile.yaml | 19 -- .../test_by_income_decile.py | 40 --- .../by_wealth_decile.yaml | 18 -- .../test_by_wealth_decile.py | 40 --- simulation.py | 48 +++ test.ipynb | 93 ++++++ 106 files changed, 262 insertions(+), 4228 deletions(-) delete mode 100644 policyengine/charts/__init__.py delete mode 100644 policyengine/charts/distributional_impact/__init__.py delete mode 100644 policyengine/charts/distributional_impact/by_income_decile/__init__.py delete mode 100644 policyengine/charts/distributional_impact/by_income_decile/average.py delete mode 100644 policyengine/charts/distributional_impact/by_income_decile/relative.py delete mode 100644 policyengine/charts/distributional_impact/by_wealth_decile/__init__.py delete mode 100644 policyengine/charts/distributional_impact/by_wealth_decile/average.py delete mode 100644 policyengine/charts/distributional_impact/by_wealth_decile/relative.py delete mode 100644 policyengine/charts/inequality.py delete mode 100644 policyengine/charts/poverty/__init__.py delete mode 100644 policyengine/charts/poverty/deep/__init__.py delete mode 100644 policyengine/charts/poverty/deep/by_age.py delete mode 100644 policyengine/charts/poverty/deep/by_gender.py delete mode 100644 policyengine/charts/poverty/regular/__init__.py delete mode 100644 policyengine/charts/poverty/regular/by_age.py delete mode 100644 policyengine/charts/poverty/regular/by_gender.py delete mode 100644 policyengine/economic_impact/__init__.py delete mode 100644 policyengine/economic_impact/base_metric_calculator.py delete mode 100644 policyengine/economic_impact/budgetary_impact/__init__.py delete mode 100644 policyengine/economic_impact/budgetary_impact/by_program/__init__.py delete mode 100644 policyengine/economic_impact/budgetary_impact/by_program/by_program.py delete mode 100644 policyengine/economic_impact/budgetary_impact/overall/__init__.py delete mode 100644 policyengine/economic_impact/budgetary_impact/overall/overall.py delete mode 100644 policyengine/economic_impact/distributional_impact/__init__.py delete mode 100644 policyengine/economic_impact/distributional_impact/by_income_decile/__init__.py delete mode 100644 policyengine/economic_impact/distributional_impact/by_income_decile/average/__init__.py delete mode 100644 policyengine/economic_impact/distributional_impact/by_income_decile/average/average.py delete mode 100644 policyengine/economic_impact/distributional_impact/by_income_decile/relative/__init__.py delete mode 100644 policyengine/economic_impact/distributional_impact/by_income_decile/relative/relative.py delete mode 100644 policyengine/economic_impact/distributional_impact/by_wealth_decile/__init__.py delete mode 100644 policyengine/economic_impact/distributional_impact/by_wealth_decile/average/__init__.py delete mode 100644 policyengine/economic_impact/distributional_impact/by_wealth_decile/average/average.py delete mode 100644 policyengine/economic_impact/distributional_impact/by_wealth_decile/relative/__init__.py delete mode 100644 policyengine/economic_impact/distributional_impact/by_wealth_decile/relative/relative.py delete mode 100644 policyengine/economic_impact/economic_impact.py delete mode 100644 policyengine/economic_impact/inequality_impact/__init__.py delete mode 100644 policyengine/economic_impact/inequality_impact/inequality_impact.py delete mode 100644 policyengine/economic_impact/labour_supply_impact/__init__.py delete mode 100644 policyengine/economic_impact/labour_supply_impact/earnings/by_decile/__init__.py delete mode 100644 policyengine/economic_impact/labour_supply_impact/earnings/by_decile/absolute/__init__.py delete mode 100644 policyengine/economic_impact/labour_supply_impact/earnings/by_decile/absolute/income_effect/__init__.py delete mode 100644 policyengine/economic_impact/labour_supply_impact/earnings/by_decile/absolute/income_effect/income_effect.py delete mode 100644 policyengine/economic_impact/labour_supply_impact/earnings/by_decile/absolute/substitution_effect/__init__.py delete mode 100644 policyengine/economic_impact/labour_supply_impact/earnings/by_decile/absolute/substitution_effect/substitution_effect.py delete mode 100644 policyengine/economic_impact/labour_supply_impact/earnings/by_decile/absolute/total/__init__.py delete mode 100644 policyengine/economic_impact/labour_supply_impact/earnings/by_decile/absolute/total/total.py delete mode 100644 policyengine/economic_impact/labour_supply_impact/earnings/by_decile/relative/__init__.py delete mode 100644 policyengine/economic_impact/labour_supply_impact/earnings/by_decile/relative/income_effect/__init__.py delete mode 100644 policyengine/economic_impact/labour_supply_impact/earnings/by_decile/relative/income_effect/income_effect.py delete mode 100644 policyengine/economic_impact/labour_supply_impact/earnings/by_decile/relative/substitution_effect/__init__.py delete mode 100644 policyengine/economic_impact/labour_supply_impact/earnings/by_decile/relative/substitution_effect/substitutional_effect.py delete mode 100644 policyengine/economic_impact/labour_supply_impact/earnings/by_decile/relative/total/__init__.py delete mode 100644 policyengine/economic_impact/labour_supply_impact/earnings/by_decile/relative/total/total.py delete mode 100644 policyengine/economic_impact/labour_supply_impact/earnings/overall/__init__.py delete mode 100644 policyengine/economic_impact/labour_supply_impact/earnings/overall/absolute/__init__.py delete mode 100644 policyengine/economic_impact/labour_supply_impact/earnings/overall/absolute/absolute.py delete mode 100644 policyengine/economic_impact/labour_supply_impact/earnings/overall/relative/__init__.py delete mode 100644 policyengine/economic_impact/labour_supply_impact/earnings/overall/relative/relative.py delete mode 100644 policyengine/economic_impact/poverty_impact/__init__.py delete mode 100644 policyengine/economic_impact/poverty_impact/deep_poverty/__init__.py delete mode 100644 policyengine/economic_impact/poverty_impact/deep_poverty/by_age/__init__.py delete mode 100644 policyengine/economic_impact/poverty_impact/deep_poverty/by_age/by_age.py delete mode 100644 policyengine/economic_impact/poverty_impact/deep_poverty/by_gender/__init__.py delete mode 100644 policyengine/economic_impact/poverty_impact/deep_poverty/by_gender/by_gender.py delete mode 100644 policyengine/economic_impact/poverty_impact/regular_poverty/__init__.py delete mode 100644 policyengine/economic_impact/poverty_impact/regular_poverty/by_age/__init__.py delete mode 100644 policyengine/economic_impact/poverty_impact/regular_poverty/by_age/by_age.py delete mode 100644 policyengine/economic_impact/poverty_impact/regular_poverty/by_gender/__init__.py delete mode 100644 policyengine/economic_impact/poverty_impact/regular_poverty/by_gender/by_gender.py delete mode 100644 policyengine/economic_impact/winners_and_losers/__init__.py delete mode 100644 policyengine/economic_impact/winners_and_losers/by_income_decile/__init__.py delete mode 100644 policyengine/economic_impact/winners_and_losers/by_income_decile/by_income_decile.py delete mode 100644 policyengine/economic_impact/winners_and_losers/by_wealth_decile/__init__.py delete mode 100644 policyengine/economic_impact/winners_and_losers/by_wealth_decile/by_wealth_decile.py create mode 100644 policyengine/outputs/macro/impact/revenue_impact.py create mode 100644 policyengine/simulation.py delete mode 100644 policyengine/tests/economic_impact/budgetary_impact/by_program/by_program.yaml delete mode 100644 policyengine/tests/economic_impact/budgetary_impact/by_program/test_by_program.py delete mode 100644 policyengine/tests/economic_impact/budgetary_impact/overall/overall.yaml delete mode 100644 policyengine/tests/economic_impact/budgetary_impact/overall/test_overall.py delete mode 100644 policyengine/tests/economic_impact/distributional_impact/by_income/average/average.yaml delete mode 100644 policyengine/tests/economic_impact/distributional_impact/by_income/average/test_average.py delete mode 100644 policyengine/tests/economic_impact/distributional_impact/by_income/relative/relative.yaml delete mode 100644 policyengine/tests/economic_impact/distributional_impact/by_income/relative/test_relative.py delete mode 100644 policyengine/tests/economic_impact/distributional_impact/by_wealth/average/average.yaml delete mode 100644 policyengine/tests/economic_impact/distributional_impact/by_wealth/average/test_average.py delete mode 100644 policyengine/tests/economic_impact/distributional_impact/by_wealth/relative/relative.yaml delete mode 100644 policyengine/tests/economic_impact/distributional_impact/by_wealth/relative/test_relative.py delete mode 100644 policyengine/tests/economic_impact/inequality_impact.py/inequality_impact.py delete mode 100644 policyengine/tests/economic_impact/inequality_impact.py/inequality_impact.yaml delete mode 100644 policyengine/tests/economic_impact/poverty_impact/deep_poverty/by_age/by_age.yaml delete mode 100644 policyengine/tests/economic_impact/poverty_impact/deep_poverty/by_age/test_deep_by_age.py delete mode 100644 policyengine/tests/economic_impact/poverty_impact/deep_poverty/by_gender/by_gender.yaml delete mode 100644 policyengine/tests/economic_impact/poverty_impact/deep_poverty/by_gender/test_deep_by_gender.py delete mode 100644 policyengine/tests/economic_impact/poverty_impact/regular_poverty/by_age/by_age.yaml delete mode 100644 policyengine/tests/economic_impact/poverty_impact/regular_poverty/by_age/test_by_age.py delete mode 100644 policyengine/tests/economic_impact/poverty_impact/regular_poverty/by_gender/by_gender.yaml delete mode 100644 policyengine/tests/economic_impact/poverty_impact/regular_poverty/by_gender/test_by_gender.py delete mode 100644 policyengine/tests/economic_impact/winners_and_losers/test_by_income_decile/by_income_decile.yaml delete mode 100644 policyengine/tests/economic_impact/winners_and_losers/test_by_income_decile/test_by_income_decile.py delete mode 100644 policyengine/tests/economic_impact/winners_and_losers/test_by_wealth_decile/by_wealth_decile.yaml delete mode 100644 policyengine/tests/economic_impact/winners_and_losers/test_by_wealth_decile/test_by_wealth_decile.py create mode 100644 simulation.py create mode 100644 test.ipynb diff --git a/.gitignore b/.gitignore index 68bc17f..7421d05 100644 --- a/.gitignore +++ b/.gitignore @@ -158,3 +158,5 @@ cython_debug/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ + +old/ diff --git a/policyengine/__init__.py b/policyengine/__init__.py index 3bc9d06..120af89 100644 --- a/policyengine/__init__.py +++ b/policyengine/__init__.py @@ -1 +1 @@ -from .economic_impact.economic_impact import EconomicImpact \ No newline at end of file +from .simulation import Simulation diff --git a/policyengine/charts/__init__.py b/policyengine/charts/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/policyengine/charts/distributional_impact/__init__.py b/policyengine/charts/distributional_impact/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/policyengine/charts/distributional_impact/by_income_decile/__init__.py b/policyengine/charts/distributional_impact/by_income_decile/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/policyengine/charts/distributional_impact/by_income_decile/average.py b/policyengine/charts/distributional_impact/by_income_decile/average.py deleted file mode 100644 index 4bda492..0000000 --- a/policyengine/charts/distributional_impact/by_income_decile/average.py +++ /dev/null @@ -1,114 +0,0 @@ -import plotly.graph_objects as go -from policyengine_core.charts.formatting import * - -class ByIncomeDecileAverageChart: - def __init__(self, country: str, data=None): - if data is None: - raise ValueError("Data must be provided") - - # Store values as they are (no percentage conversion) - for i in range(1, 12): - setattr(self, f"decile_{i}", data['average'][i]) - - self.country = country - - def _get_color(self, value): - if value is None or value == 0 or value < 0: - return GRAY - return BLUE - - def _get_change_direction(self, value): - if value > 0: - return "increase" - elif value < 0: - return "decrease" - else: - return "no change" - - def _get_currency_symbol(self): - if self.country.lower() == "us": - return "$" - elif self.country.lower() == "uk": - return "£" - else: - return "$" # Default to USD if country not recognized - - def ordinal_suffix(self, n): - """Return the ordinal suffix for an integer.""" - if 10 <= n % 100 <= 20: - suffix = 'th' - else: - suffix = {1: 'st', 2: 'nd', 3: 'rd'}.get(n % 10, 'th') - return suffix - - def generate_chart_data(self): - categories = [str(i) for i in range(1, 12)] - values = [getattr(self, f"decile_{i}") for i in range(1, 12)] - - # Filter out categories and values with zero difference - non_zero_data = [(cat, val) for cat, val in zip(categories, values) if val != 0] - - if not non_zero_data: - fig = go.Figure() - fig.add_annotation( - x=0.5, - y=0.5, - xref="paper", - yref="paper", - text="No differences to display", - showarrow=False, - font=dict(size=20) - ) - fig.update_layout( - title="Absolute change in household income", - xaxis=dict(visible=False), - yaxis=dict(visible=False) - ) - return fig - - non_zero_categories, non_zero_values = zip(*non_zero_data) - - # Get currency symbol based on country - currency_symbol = self._get_currency_symbol() - - # Generate hover texts with raw impact values and change direction - hover_texts = [ - f"This reform would {self._get_change_direction(val)} the income of households in the {i}{self.ordinal_suffix(int(i))} decile by {currency_symbol}{abs(val):,.1f}" - for i, val in zip(non_zero_categories, non_zero_values) - ] - - fig = go.Figure() - - values_in_bn = non_zero_values # No need to convert values - colors = [self._get_color(value) for value in non_zero_values] - - # Add bar chart with text formatted with currency symbol - fig.add_trace(go.Bar( - x=non_zero_categories, - y=values_in_bn, - marker=dict(color=colors, line=dict(width=1)), - width=0.6, - text=[f"{currency_symbol}{abs(value):,.1f}" for value in non_zero_values], # Display values with currency symbol - textposition='outside', - hovertemplate="Decile %{x}

%{customdata}", # Hover shows "Decile {x}" - customdata=hover_texts - )) - - # Update layout to include currency on y-axis - fig.update_layout( - yaxis=dict( - tickformat=",.0f", # No decimal places for the y-axis, add thousands separator - title=f"Absolute Impact on Income ({currency_symbol})" - ), - xaxis=dict( - title="Income Decile" - ), - hoverlabel=dict( - bgcolor="white", - font=dict(color="black", size=16) - ), - title="Absolute Change in Household Income by Decile" - ) - - format_fig(fig) # Keep the formatting logic from policyengine_core - return fig \ No newline at end of file diff --git a/policyengine/charts/distributional_impact/by_income_decile/relative.py b/policyengine/charts/distributional_impact/by_income_decile/relative.py deleted file mode 100644 index c7795ff..0000000 --- a/policyengine/charts/distributional_impact/by_income_decile/relative.py +++ /dev/null @@ -1,104 +0,0 @@ -import plotly.graph_objects as go -from policyengine_core.charts.formatting import * - - -class ByIncomeDecileRelativeChart: - def __init__(self, country: str, data=None): - if data is None: - raise ValueError("Data must be provided") - - # Convert the relative values to percentages and store them in attributes for each decile - for i in range(1, 12): - setattr(self, f"decile_{i}", data['relative'][i] * 100) - - self.country = country - - def _get_color(self, value): - if value is None or value == 0 or value < 0: - return GRAY - return BLUE - - def _get_change_direction(self, value): - if value > 0: - return "increase" - elif value < 0: - return "decrease" - else: - return "no change" - - def ordinal_suffix(self, n): - """Return the ordinal suffix for an integer.""" - if 10 <= n % 100 <= 20: - suffix = 'th' - else: - suffix = {1: 'st', 2: 'nd', 3: 'rd'}.get(n % 10, 'th') - return suffix - - - def generate_chart_data(self): - categories = [str(i) for i in range(1, 12)] - values = [getattr(self, f"decile_{i}") for i in range(1, 12)] - - # Filter out categories and values with zero difference - non_zero_data = [(cat, val) for cat, val in zip(categories, values) if val != 0] - - if not non_zero_data: - fig = go.Figure() - fig.add_annotation( - x=0.5, - y=0.5, - xref="paper", - yref="paper", - text="No differences to display", - showarrow=False, - font=dict(size=20) - ) - fig.update_layout( - title="Relative change in household income", - xaxis=dict(visible=False), - yaxis=dict(visible=False) - ) - return fig - - non_zero_categories, non_zero_values = zip(*non_zero_data) - - # Generate hover texts with formatted impact values and change direction - hover_texts = [ - f"This reform would {self._get_change_direction(val)} the income of households in the {i}{self.ordinal_suffix(int(i))} decile by {abs(val):,.1f}%" - for i, val in zip(non_zero_categories, non_zero_values) - ] - - fig = go.Figure() - - values_in_bn = non_zero_values # The values are already in percentages - colors = [self._get_color(value) for value in non_zero_values] - - fig.add_trace(go.Bar( - x=non_zero_categories, - y=values_in_bn, - marker=dict(color=colors, line=dict(width=1)), - width=0.6, - text=[f"{value:.1f}%" for value in non_zero_values], # Display values with one decimal place and percentage symbol - textposition='outside', - hovertemplate="Decile %{x}

%{customdata}", # Hover shows "Decile {x}" - customdata=hover_texts - )) - - # Update layout to show percentage on y-axis and format figure - fig.update_layout( - yaxis=dict( - tickformat=".1f%", # Format for one decimal place with percentage symbol - ticksuffix="%", - title="Relative Impact on Income (%)" - ), - xaxis=dict( - title="Income Decile" - ), - hoverlabel=dict( - bgcolor="white", - font=dict(color="black", size=16) - ), - title="Relative Change in Household Income by Decile" - ) - format_fig(fig) # Keep the formatting logic from policyengine_core - return fig \ No newline at end of file diff --git a/policyengine/charts/distributional_impact/by_wealth_decile/__init__.py b/policyengine/charts/distributional_impact/by_wealth_decile/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/policyengine/charts/distributional_impact/by_wealth_decile/average.py b/policyengine/charts/distributional_impact/by_wealth_decile/average.py deleted file mode 100644 index 5b0ab43..0000000 --- a/policyengine/charts/distributional_impact/by_wealth_decile/average.py +++ /dev/null @@ -1,114 +0,0 @@ -import plotly.graph_objects as go -from policyengine_core.charts.formatting import * - -class ByWealthDecileAverageChart: - def __init__(self, country: str, data=None): - if data is None: - raise ValueError("Data must be provided") - - # Store values as they are (no percentage conversion) - for i in range(1, 12): - setattr(self, f"decile_{i}", data['average'][i]) - - self.country = country - - def _get_color(self, value): - if value is None or value == 0 or value < 0: - return GRAY - return BLUE - - def _get_change_direction(self, value): - if value > 0: - return "increase" - elif value < 0: - return "decrease" - else: - return "no change" - - def _get_currency_symbol(self): - if self.country.lower() == "us": - return "$" - elif self.country.lower() == "uk": - return "£" - else: - return "$" # Default to USD if country not recognized - - def ordinal_suffix(self, n): - """Return the ordinal suffix for an integer.""" - if 10 <= n % 100 <= 20: - suffix = 'th' - else: - suffix = {1: 'st', 2: 'nd', 3: 'rd'}.get(n % 10, 'th') - return suffix - - def generate_chart_data(self): - categories = [str(i) for i in range(1, 12)] - values = [getattr(self, f"decile_{i}") for i in range(1, 12)] - - # Filter out categories and values with zero difference - non_zero_data = [(cat, val) for cat, val in zip(categories, values) if val != 0] - - if not non_zero_data: - fig = go.Figure() - fig.add_annotation( - x=0.5, - y=0.5, - xref="paper", - yref="paper", - text="No differences to display", - showarrow=False, - font=dict(size=20) - ) - fig.update_layout( - title="Absolute change in household income", - xaxis=dict(visible=False), - yaxis=dict(visible=False) - ) - return fig - - non_zero_categories, non_zero_values = zip(*non_zero_data) - - # Get currency symbol based on country - currency_symbol = self._get_currency_symbol() - - # Generate hover texts with raw impact values and change direction - hover_texts = [ - f"This reform would {self._get_change_direction(val)} the income of households in the {i}{self.ordinal_suffix(int(i))} decile by {currency_symbol}{abs(val):,.1f}" - for i, val in zip(non_zero_categories, non_zero_values) - ] - - fig = go.Figure() - - values_in_bn = non_zero_values # No need to convert values - colors = [self._get_color(value) for value in non_zero_values] - - # Add bar chart with text formatted with currency symbol - fig.add_trace(go.Bar( - x=non_zero_categories, - y=values_in_bn, - marker=dict(color=colors, line=dict(width=1)), - width=0.6, - text=[f"{currency_symbol}{abs(value):,.1f}" for value in non_zero_values], # Display values with currency symbol - textposition='outside', - hovertemplate="Decile %{x}

%{customdata}", # Hover shows "Decile {x}" - customdata=hover_texts - )) - - # Update layout to include currency on y-axis - fig.update_layout( - yaxis=dict( - tickformat=",.0f", # No decimal places for the y-axis, add thousands separator - title=f"Absolute Impact on Wealth ({currency_symbol})" - ), - xaxis=dict( - title="Wealth Decile" - ), - hoverlabel=dict( - bgcolor="white", - font=dict(color="black", size=16) - ), - title="Absolute Change in Household Income by Decile" - ) - - format_fig(fig) # Keep the formatting logic from policyengine_core - return fig \ No newline at end of file diff --git a/policyengine/charts/distributional_impact/by_wealth_decile/relative.py b/policyengine/charts/distributional_impact/by_wealth_decile/relative.py deleted file mode 100644 index 5377781..0000000 --- a/policyengine/charts/distributional_impact/by_wealth_decile/relative.py +++ /dev/null @@ -1,105 +0,0 @@ -import plotly.graph_objects as go -from policyengine_core.charts.formatting import * - - -class ByWealthDecileRelativeChart: - def __init__(self, country: str, data=None): - if data is None: - raise ValueError("Data must be provided") - - # Convert the relative values to percentages and store them in attributes for each decile - for i in range(1, 12): - setattr(self, f"decile_{i}", data['relative'][i] * 100) - - self.country = country - - def _get_color(self, value): - if value is None or value == 0 or value < 0: - return GRAY - return BLUE - - def _get_change_direction(self, value): - if value > 0: - return "increase" - elif value < 0: - return "decrease" - else: - return "no change" - - def ordinal_suffix(self, n): - """Return the ordinal suffix for an integer.""" - if 10 <= n % 100 <= 20: - suffix = 'th' - else: - suffix = {1: 'st', 2: 'nd', 3: 'rd'}.get(n % 10, 'th') - return suffix - - - def generate_chart_data(self): - categories = [str(i) for i in range(1, 12)] - values = [getattr(self, f"decile_{i}") for i in range(1, 12)] - - # Filter out categories and values with zero difference - non_zero_data = [(cat, val) for cat, val in zip(categories, values) if val != 0] - - if not non_zero_data: - fig = go.Figure() - fig.add_annotation( - x=0.5, - y=0.5, - xref="paper", - yref="paper", - text="No differences to display", - showarrow=False, - font=dict(size=20) - ) - fig.update_layout( - title="Relative change in household income", - xaxis=dict(visible=False), - yaxis=dict(visible=False) - ) - return fig - - non_zero_categories, non_zero_values = zip(*non_zero_data) - - # Generate hover texts with formatted impact values and change direction - hover_texts = [ - f"This reform would {self._get_change_direction(val)} the income of households in the {i}{self.ordinal_suffix(int(i))} decile by {abs(val):,.1f}%" - for i, val in zip(non_zero_categories, non_zero_values) - ] - - fig = go.Figure() - - values_in_bn = non_zero_values # The values are already in percentages - colors = [self._get_color(value) for value in non_zero_values] - - fig.add_trace(go.Bar( - x=non_zero_categories, - y=values_in_bn, - marker=dict(color=colors, line=dict(width=1)), - width=0.6, - text=[f"{value:.1f}%" for value in non_zero_values], # Display values with one decimal place and percentage symbol - textposition='outside', - hovertemplate="Decile %{x}

%{customdata}", # Hover shows "Decile {x}" - customdata=hover_texts - )) - - # Update layout to show percentage on y-axis and format figure - fig.update_layout( - yaxis=dict( - tickformat=".1f%", # Format for one decimal place with percentage symbol - ticksuffix="%", - title="Relative Impact on Wealth (%)" - ), - xaxis=dict( - title="Wealth Decile" - ), - hoverlabel=dict( - bgcolor="white", - font=dict(color="black", size=16) - ), - title="Relative Change in Household Income by Decile" - ) - - format_fig(fig) # Keep the formatting logic from policyengine_core - return fig diff --git a/policyengine/charts/inequality.py b/policyengine/charts/inequality.py deleted file mode 100644 index c206c1d..0000000 --- a/policyengine/charts/inequality.py +++ /dev/null @@ -1,65 +0,0 @@ -import plotly.graph_objects as go -from policyengine_core.charts.formatting import * - -class InequalityImpactChart: - def __init__(self, data=None) -> None: - if data is None: - raise ValueError("Data must be provided") - - # Expecting data to contain baseline, reform, change, and change_percentage for each metric - self.data = data - - def generate_chart_data(self): - # Data for the x-axis labels - metrics = ["Gini index", "Top 1% share", "Top 10% share"] - - # Extract the change percentages, baseline, and reform values for hover text - change_percentages = [self.data[metric]['change_percentage'] for metric in metrics] - baseline_values = [self.data[metric]['baseline'] for metric in metrics] - reform_values = [self.data[metric]['reform'] for metric in metrics] - - # Generate hover text for each metric - hover_texts = [ - f"The reform would increase the {metric} by {change_percentages[i]}% from {baseline_values[i]} to {reform_values[i]}%" - if change_percentages[i] > 0 - else f"The reform would decrease the {metric} by {change_percentages[i]}% from {baseline_values[i]} to {reform_values[i]}%" - for i, metric in enumerate(metrics) - ] - - # Create the bar chart figure - fig = go.Figure() - - # Add a bar trace for the change percentages of each metric - fig.add_trace(go.Bar( - x=metrics, # Labels for each metric - y=change_percentages, # Change percentages for each metric - marker=dict( - color=[BLUE if change_percentages[i] > 0 else GRAY for i in range(len(change_percentages))], # Conditional color for each bar - line=dict(width=1), - ), - text=[f"{percent}%" for percent in change_percentages], # Display percentage as text - textposition='outside', # Position text outside the bars - hovertemplate=f"%{{x}}

%{{customdata}}", - customdata=hover_texts # Hover text for each bar - )) - - # Update layout for the chart - fig.update_layout( - yaxis=dict( - tickformat=".1f", # Show y-values with one decimal place - ticksuffix="%", - title="Relative change" # Add percentage symbol - ), - hoverlabel=dict( - bgcolor="white", # Background color of the hover label - font=dict( - color="black", # Text color of the hover label - size=16, # Font size - ), - ), - title="Impact of Reform on Inequality Metrics" # Add a title to the chart - ) - - format_fig(fig) - - return fig \ No newline at end of file diff --git a/policyengine/charts/poverty/__init__.py b/policyengine/charts/poverty/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/policyengine/charts/poverty/deep/__init__.py b/policyengine/charts/poverty/deep/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/policyengine/charts/poverty/deep/by_age.py b/policyengine/charts/poverty/deep/by_age.py deleted file mode 100644 index fabe5c6..0000000 --- a/policyengine/charts/poverty/deep/by_age.py +++ /dev/null @@ -1,107 +0,0 @@ -import plotly.graph_objects as go -from policyengine_core.charts.formatting import * - -class DeepPovertyByAgeChart: - def __init__(self,country:str, data=None): - if data is None: - raise ValueError("Data must be provided") - - self.data = data - - def _get_color(self, value): - # All bars should be gray - return GRAY - - def _get_change_direction(self, value): - if value > 0: - return "increase" - elif value < 0: - return "decrease" - else: - return "no change" - - def ordinal_suffix(self, n): - """Return the ordinal suffix for an integer.""" - if 10 <= n % 100 <= 20: - suffix = 'th' - else: - suffix = {1: 'st', 2: 'nd', 3: 'rd'}.get(n % 10, 'th') - return suffix - - def generate_chart_data(self): - categories = list(self.data.keys()) - values = [self.data[cat]['change'] for cat in categories] - baselines = [self.data[cat]['baseline'] * 100 for cat in categories] - reforms = [self.data[cat]['reform'] * 100 for cat in categories] - - # Generate hover texts with baseline, reform, and percentage change - hover_texts = [ - f"This reform would {self._get_change_direction(val)} the percentage of {category.lower()} in poverty by {abs(val):.1f}% from {baseline:.1f}% to {reform:.1f}%" - for category, val, baseline, reform in zip(categories, values, baselines, reforms) - ] - - fig = go.Figure() - - values_in_pct = values # Use percentage values - colors = [self._get_color(value) for value in values] - - # Add bar chart with percentage values - fig.add_trace(go.Bar( - x=categories, - y=values_in_pct, - marker=dict(color=colors, line=dict(width=1)), - width=0.6, - text=[f"{abs(value):.1f}%" for value in values], # Display values as percentages - textposition='outside', - hovertemplate="%{x}

%{customdata}", # Hover shows category - customdata=hover_texts - )) - - # Update layout to reflect percentage values on y-axis - fig.update_layout( - yaxis=dict( - tickformat=",.1f%%", # Format y-axis as percentages with one decimal place - title="Percentage Change in Poverty" - ), - xaxis=dict( - title="Category" - ), - hoverlabel=dict( - bgcolor="white", - font=dict(color="black", size=16) - ), - title="Change in Poverty Percentage by Category" - ) - - format_fig(fig) # Keep the formatting logic from policyengine_core - return fig - - -# # Example data -# data = { -# 'Child': { -# 'Baseline': 0.32427219591395395, -# 'Reform': 0.33392168532001054, -# 'Change': 3.0 -# }, -# 'Adult': { -# 'Baseline': 0.17427822561729264, -# 'Reform': 0.17757158627182623, -# 'Change': 1.9 -# }, -# 'Senior': { -# 'Baseline': 0.12817646500651358, -# 'Reform': 0.1370685860340031, -# 'Change': 6.9 -# }, -# 'All': { -# 'Baseline': 0.19913534734369268, -# 'Reform': 0.20487670454940832, -# 'Change': 2.9 -# } -# } - -# # Generate chart for all categories -# chart = OverallChart(data=data) -# fig = chart.generate_chart_data() -# fig.show() diff --git a/policyengine/charts/poverty/deep/by_gender.py b/policyengine/charts/poverty/deep/by_gender.py deleted file mode 100644 index 40e1a61..0000000 --- a/policyengine/charts/poverty/deep/by_gender.py +++ /dev/null @@ -1,103 +0,0 @@ -import plotly.graph_objects as go -from policyengine_core.charts.formatting import * - -class DeepPovertyByGenderChart: - def __init__(self, country:str,data=None): - if data is None: - raise ValueError("Data must be provided") - - self.data = data - - def _get_color(self, value): - # All bars should be gray - return GRAY - - def _get_change_direction(self, value): - if value > 0: - return "increase" - elif value < 0: - return "decrease" - else: - return "no change" - - def ordinal_suffix(self, n): - """Return the ordinal suffix for an integer.""" - if 10 <= n % 100 <= 20: - suffix = 'th' - else: - suffix = {1: 'st', 2: 'nd', 3: 'rd'}.get(n % 10, 'th') - return suffix - - def generate_chart_data(self): - categories = list(self.data.keys()) - values = [self.data[cat]['change'] for cat in categories] - baselines = [self.data[cat]['baseline'] * 100 for cat in categories] - reforms = [self.data[cat]['reform'] * 100 for cat in categories] - - # Generate hover texts with baseline, reform, and percentage change - hover_texts = [ - f"This reform would {self._get_change_direction(val)} the percentage of {category.lower()} in poverty by {abs(val):.1f}% from {baseline:.1f}% to {reform:.1f}%" - for category, val, baseline, reform in zip(categories, values, baselines, reforms) - ] - - fig = go.Figure() - - values_in_pct = values # Use percentage values - colors = [self._get_color(value) for value in values] - - # Add bar chart with percentage values - fig.add_trace(go.Bar( - x=categories, - y=values_in_pct, - marker=dict(color=colors, line=dict(width=1)), - width=0.6, - text=[f"{abs(value):.1f}%" for value in values], # Display values as percentages - textposition='outside', - hovertemplate="Category %{x}

%{customdata}", # Hover shows category - customdata=hover_texts - )) - - # Update layout to reflect percentage values on y-axis - fig.update_layout( - yaxis=dict( - tickformat=",.1f%%", - ticksuffix = "%", # Format y-axis as percentages with one decimal place - title="Percentage Change in Poverty" - ), - xaxis=dict( - title="Category" - ), - hoverlabel=dict( - bgcolor="white", - font=dict(color="black", size=16) - ), - title="Change in Poverty Percentage by Category" - ) - - format_fig(fig) # Keep the formatting logic from policyengine_core - return fig - - -# # Example data -# data = { -# 'Male': { -# 'Baseline': 0.18412623468617267, -# 'Reform': 0.18932591339284738, -# 'Change': 2.8 -# }, -# 'Female': { -# 'Baseline': 0.21377616483057263, -# 'Reform': 0.22004590877186603, -# 'Change': 2.9 -# }, -# 'All': { -# 'Baseline': 0.19913534734369268, -# 'Reform': 0.20487670454940832, -# 'Change': 2.9 -# } -# } - -# # Generate chart for all categories -# chart = OverallChart(data=data) -# fig = chart.generate_chart_data() -# fig.show() diff --git a/policyengine/charts/poverty/regular/__init__.py b/policyengine/charts/poverty/regular/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/policyengine/charts/poverty/regular/by_age.py b/policyengine/charts/poverty/regular/by_age.py deleted file mode 100644 index e60333b..0000000 --- a/policyengine/charts/poverty/regular/by_age.py +++ /dev/null @@ -1,107 +0,0 @@ -import plotly.graph_objects as go -from policyengine_core.charts.formatting import * - -class RegularPovertyByAgeChart: - def __init__(self,country:str, data=None): - if data is None: - raise ValueError("Data must be provided") - - self.data = data - - def _get_color(self, value): - # All bars should be gray - return GRAY - - def _get_change_direction(self, value): - if value > 0: - return "increase" - elif value < 0: - return "decrease" - else: - return "no change" - - def ordinal_suffix(self, n): - """Return the ordinal suffix for an integer.""" - if 10 <= n % 100 <= 20: - suffix = 'th' - else: - suffix = {1: 'st', 2: 'nd', 3: 'rd'}.get(n % 10, 'th') - return suffix - - def generate_chart_data(self): - categories = list(self.data.keys()) - values = [self.data[cat]['change'] for cat in categories] - baselines = [self.data[cat]['baseline'] * 100 for cat in categories] - reforms = [self.data[cat]['reform'] * 100 for cat in categories] - - # Generate hover texts with baseline, reform, and percentage change - hover_texts = [ - f"This reform would {self._get_change_direction(val)} the percentage of {category.lower()} in poverty by {abs(val):.1f}% from {baseline:.1f}% to {reform:.1f}%" - for category, val, baseline, reform in zip(categories, values, baselines, reforms) - ] - - fig = go.Figure() - - values_in_pct = values # Use percentage values - colors = [self._get_color(value) for value in values] - - # Add bar chart with percentage values - fig.add_trace(go.Bar( - x=categories, - y=values_in_pct, - marker=dict(color=colors, line=dict(width=1)), - width=0.6, - text=[f"{abs(value):.1f}%" for value in values], # Display values as percentages - textposition='outside', - hovertemplate="%{x}

%{customdata}", # Hover shows category - customdata=hover_texts - )) - - # Update layout to reflect percentage values on y-axis - fig.update_layout( - yaxis=dict( - tickformat=",.1f%%", # Format y-axis as percentages with one decimal place - title="Percentage Change in Poverty" - ), - xaxis=dict( - title="Category" - ), - hoverlabel=dict( - bgcolor="white", - font=dict(color="black", size=16) - ), - title="Change in Poverty Percentage by Category" - ) - - format_fig(fig) # Keep the formatting logic from policyengine_core - return fig - - -# # Example data -# data = { -# 'Child': { -# 'Baseline': 0.32427219591395395, -# 'Reform': 0.33392168532001054, -# 'Change': 3.0 -# }, -# 'Adult': { -# 'Baseline': 0.17427822561729264, -# 'Reform': 0.17757158627182623, -# 'Change': 1.9 -# }, -# 'Senior': { -# 'Baseline': 0.12817646500651358, -# 'Reform': 0.1370685860340031, -# 'Change': 6.9 -# }, -# 'All': { -# 'Baseline': 0.19913534734369268, -# 'Reform': 0.20487670454940832, -# 'Change': 2.9 -# } -# } - -# # Generate chart for all categories -# chart = OverallChart(data=data) -# fig = chart.generate_chart_data() -# fig.show() diff --git a/policyengine/charts/poverty/regular/by_gender.py b/policyengine/charts/poverty/regular/by_gender.py deleted file mode 100644 index a393c25..0000000 --- a/policyengine/charts/poverty/regular/by_gender.py +++ /dev/null @@ -1,103 +0,0 @@ -import plotly.graph_objects as go -from policyengine_core.charts.formatting import * - -class RegularPovertyByGenderChart: - def __init__(self, country:str,data=None): - if data is None: - raise ValueError("Data must be provided") - - self.data = data - - def _get_color(self, value): - # All bars should be gray - return GRAY - - def _get_change_direction(self, value): - if value > 0: - return "increase" - elif value < 0: - return "decrease" - else: - return "no change" - - def ordinal_suffix(self, n): - """Return the ordinal suffix for an integer.""" - if 10 <= n % 100 <= 20: - suffix = 'th' - else: - suffix = {1: 'st', 2: 'nd', 3: 'rd'}.get(n % 10, 'th') - return suffix - - def generate_chart_data(self): - categories = list(self.data.keys()) - values = [self.data[cat]['change'] for cat in categories] - baselines = [self.data[cat]['baseline'] * 100 for cat in categories] - reforms = [self.data[cat]['reform'] * 100 for cat in categories] - - # Generate hover texts with baseline, reform, and percentage change - hover_texts = [ - f"This reform would {self._get_change_direction(val)} the percentage of {category.lower()} in poverty by {abs(val):.1f}% from {baseline:.1f}% to {reform:.1f}%" - for category, val, baseline, reform in zip(categories, values, baselines, reforms) - ] - - fig = go.Figure() - - values_in_pct = values # Use percentage values - colors = [self._get_color(value) for value in values] - - # Add bar chart with percentage values - fig.add_trace(go.Bar( - x=categories, - y=values_in_pct, - marker=dict(color=colors, line=dict(width=1)), - width=0.6, - text=[f"{abs(value):.1f}%" for value in values], # Display values as percentages - textposition='outside', - hovertemplate="Category %{x}

%{customdata}", # Hover shows category - customdata=hover_texts - )) - - # Update layout to reflect percentage values on y-axis - fig.update_layout( - yaxis=dict( - tickformat=",.1f%%", - ticksuffix = "%", # Format y-axis as percentages with one decimal place - title="Percentage Change in Poverty" - ), - xaxis=dict( - title="Category" - ), - hoverlabel=dict( - bgcolor="white", - font=dict(color="black", size=16) - ), - title="Change in Poverty Percentage by Category" - ) - - format_fig(fig) # Keep the formatting logic from policyengine_core - return fig - - -# # Example data -# data = { -# 'Male': { -# 'Baseline': 0.18412623468617267, -# 'Reform': 0.18932591339284738, -# 'Change': 2.8 -# }, -# 'Female': { -# 'Baseline': 0.21377616483057263, -# 'Reform': 0.22004590877186603, -# 'Change': 2.9 -# }, -# 'All': { -# 'Baseline': 0.19913534734369268, -# 'Reform': 0.20487670454940832, -# 'Change': 2.9 -# } -# } - -# # Generate chart for all categories -# chart = OverallChart(data=data) -# fig = chart.generate_chart_data() -# fig.show() diff --git a/policyengine/economic_impact/__init__.py b/policyengine/economic_impact/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/policyengine/economic_impact/base_metric_calculator.py b/policyengine/economic_impact/base_metric_calculator.py deleted file mode 100644 index 601de32..0000000 --- a/policyengine/economic_impact/base_metric_calculator.py +++ /dev/null @@ -1,54 +0,0 @@ -from policyengine_core.reforms import Reform -from typing import Dict, Union - -class BaseMetricCalculator: - """ - Base class for calculating metrics based on baseline and reformed data. - - Attributes: - baseline (object): Object representing baseline data. - reformed (object): Object representing reformed data. - """ - def __init__(self, baseline: object, reformed: object, default_period: int = 2024) -> None: - """ - Initialize with baseline and reformed data objects. - - Args: - baseline (object): Object representing baseline data. - reformed (object): Object representing reformed data. - """ - self.baseline = baseline - self.reformed = reformed - self.default_period = default_period - self.baseline.default_calculation_period = default_period - self.reformed.default_calculation_period = default_period - - def calculate_baseline(self, variable: str, period: int = None) -> Union[float, int]: - """ - Calculate baseline metric value. - - Args: - variable (str): Variable to calculate. - period (int): Year or period for calculation (default: 2024). - - Returns: - Union[float, int]: Calculated metric value. - """ - if period is None: - period = self.default_period - return self.baseline.calculate(variable, period=period) - - def calculate_reformed(self, variable: str, period: int = None) -> Union[float, int]: - """ - Calculate reformed metric value. - - Args: - variable (str): Variable to calculate. - period (int): Year or period for calculation (default: 2024). - - Returns: - Union[float, int]: Calculated metric value. - """ - if period is None: - period = self.default_period - return self.reformed.calculate(variable, period=period) \ No newline at end of file diff --git a/policyengine/economic_impact/budgetary_impact/__init__.py b/policyengine/economic_impact/budgetary_impact/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/policyengine/economic_impact/budgetary_impact/by_program/__init__.py b/policyengine/economic_impact/budgetary_impact/by_program/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/policyengine/economic_impact/budgetary_impact/by_program/by_program.py b/policyengine/economic_impact/budgetary_impact/by_program/by_program.py deleted file mode 100644 index a1273d7..0000000 --- a/policyengine/economic_impact/budgetary_impact/by_program/by_program.py +++ /dev/null @@ -1,192 +0,0 @@ -from policyengine.economic_impact.base_metric_calculator import BaseMetricCalculator -from policyengine_uk import Microsimulation - -class IncomeTax(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - self.baseline = baseline - self.reformed = reformed - - def calculate(self): - - baseline = self.baseline.calculate("income_tax", map_to="household").sum() - reform = self.reformed.calculate("income_tax", map_to="household").sum() - - change = ((reform - baseline) / baseline) * 100 - - return { - "baseline": round(baseline,2), - "reform": round(reform,2), - "change": round(change,1) - } - -class NationalInsurance(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - self.baseline = baseline - self.reformed = reformed - - def calculate(self): - - baseline = self.baseline.calculate("national_insurance", map_to="household").sum() - reform = self.reformed.calculate("national_insurance", map_to="household").sum() - - change = ((reform - baseline) / baseline) * 100 - - return { - "baseline": round(baseline,2), - "reform": round(reform,2), - "change": round(change,1) - } - -class Vat(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - self.baseline = baseline - self.reformed = reformed - - def calculate(self): - - baseline = self.baseline.calculate("vat", map_to="household").sum() - reform = self.reformed.calculate("vat", map_to="household").sum() - - change = ((reform - baseline) / baseline) * 100 - - return { - "baseline": round(baseline,2), - "reform": round(reform,2), - "change": round(change,1) - } - -class CouncilTax(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - self.baseline = baseline - self.reformed = reformed - - def calculate(self): - - baseline = self.baseline.calculate("council_tax", map_to="household").sum() - reform = self.reformed.calculate("council_tax", map_to="household").sum() - - change = ((reform - baseline) / baseline) * 100 - - return { - "baseline": round(baseline,2), - "reform": round(reform,2), - "change": round(change,1) - } - -class FuelDuty(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - self.baseline = baseline - self.reformed = reformed - - def calculate(self): - - baseline = self.baseline.calculate("fuel_duty", map_to="household").sum() - reform = self.reformed.calculate("fuel_duty", map_to="household").sum() - - change = ((reform - baseline) / baseline) * 100 - - return { - "baseline": round(baseline,2), - "reform": round(reform,2), - "change": round(change,1) - } - -class TaxCredits(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - self.baseline = baseline - self.reformed = reformed - - def calculate(self): - - baseline = self.baseline.calculate("tax_credits", map_to="household").sum() * -1 - reform = self.reformed.calculate("tax_credits", map_to="household").sum() * -1 - - change = ((reform - baseline) / baseline) * 100 - - return { - "baseline": round(baseline,2), - "reform": round(reform,2), - "change": round(change,1) - } - -class UniversalCredit(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - self.baseline = baseline - self.reformed = reformed - - def calculate(self): - - baseline = self.baseline.calculate("universal_credit", map_to="household").sum() * -1 - reform = self.reformed.calculate("universal_credit", map_to="household").sum() * -1 - - change = ((reform - baseline) / baseline) * 100 - - return { - "baseline": round(baseline,2), - "reform": round(reform,2), - "change": round(change,1) - } - -class ChildBenefit(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - self.baseline = baseline - self.reformed = reformed - - def calculate(self): - - baseline = self.baseline.calculate("child_benefit", map_to="household").sum() * -1 - reform = self.reformed.calculate("child_benefit", map_to="household").sum() * -1 - - change = ((reform - baseline) / baseline) * 100 - - return { - "baseline": round(baseline,2), - "reform": round(reform,2), - "change": round(change,1) - } - -class StatePension(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - self.baseline = baseline - self.reformed = reformed - - def calculate(self): - - baseline = self.baseline.calculate("state_pension", map_to="household").sum() * -1 - reform = self.reformed.calculate("state_pension", map_to="household").sum() * -1 - - change = ((reform - baseline) / baseline) * 100 - - return { - "baseline": round(baseline,2), - "reform": round(reform,2), - "change": round(change,1) - } - -class PensionCredit(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - self.baseline = baseline - self.reformed = reformed - - def calculate(self): - - baseline = self.baseline.calculate("pension_credit", map_to="household").sum() * -1 - reform = self.reformed.calculate("pension_credit", map_to="household").sum() * -1 - - change = ((reform - baseline) / baseline) * 100 - - return { - "baseline": round(baseline,2), - "reform": round(reform,2), - "change": round(change,1) - } \ No newline at end of file diff --git a/policyengine/economic_impact/budgetary_impact/overall/__init__.py b/policyengine/economic_impact/budgetary_impact/overall/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/policyengine/economic_impact/budgetary_impact/overall/overall.py b/policyengine/economic_impact/budgetary_impact/overall/overall.py deleted file mode 100644 index 85ced16..0000000 --- a/policyengine/economic_impact/budgetary_impact/overall/overall.py +++ /dev/null @@ -1,70 +0,0 @@ -from policyengine.economic_impact.base_metric_calculator import BaseMetricCalculator -from policyengine_uk import Microsimulation - -class BudgetaryImpact(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - self.baseline = baseline - self.reformed = reformed - - def calculate(self): - - baseline_total_tax = self.baseline.calculate("household_tax").sum() - reformed_total_tax = self.reformed.calculate("household_tax").sum() - - tax_revenue_impact = reformed_total_tax - baseline_total_tax - - baseline_total_benefits = self.baseline.calculate("household_benefits").sum() - reformed_total_benefits = self.reformed.calculate("household_benefits").sum() - - - benefit_spending_impact = reformed_total_benefits - baseline_total_benefits - - budgetary_impact = tax_revenue_impact - benefit_spending_impact - - - - return { - "budgetary_impact" : round(budgetary_impact,2) - } - -class BenefitSpendingImpact(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - self.baseline = baseline - self.reformed = reformed - - def calculate(self): - - baseline_total_benefits = self.baseline.calculate("household_benefits").sum() - reformed_total_benefits = self.reformed.calculate("household_benefits").sum() - - - benefit_spending_impact = reformed_total_benefits - baseline_total_benefits - - - - return { - "baseline_total_benefits": round(baseline_total_benefits,2), - "reformed_total_benefits": round(reformed_total_benefits,2), - "benefit_spending_impact": round(benefit_spending_impact,2) - } - -class TaxRevenueImpact(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - self.baseline = baseline - self.reformed = reformed - - def calculate(self): - - baseline_total_tax = self.baseline.calculate("household_tax").sum() - reformed_total_tax = self.reformed.calculate("household_tax").sum() - - tax_revenue_impact = reformed_total_tax - baseline_total_tax - - return { - "baseline_total_tax": round(baseline_total_tax,2), - "reformed_total_tax": round(reformed_total_tax,2), - "tax_revenue_impact": round(tax_revenue_impact,2) - } diff --git a/policyengine/economic_impact/distributional_impact/__init__.py b/policyengine/economic_impact/distributional_impact/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/policyengine/economic_impact/distributional_impact/by_income_decile/__init__.py b/policyengine/economic_impact/distributional_impact/by_income_decile/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/policyengine/economic_impact/distributional_impact/by_income_decile/average/__init__.py b/policyengine/economic_impact/distributional_impact/by_income_decile/average/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/policyengine/economic_impact/distributional_impact/by_income_decile/average/average.py b/policyengine/economic_impact/distributional_impact/by_income_decile/average/average.py deleted file mode 100644 index de64b2c..0000000 --- a/policyengine/economic_impact/distributional_impact/by_income_decile/average/average.py +++ /dev/null @@ -1,37 +0,0 @@ -from policyengine.economic_impact.base_metric_calculator import BaseMetricCalculator -from policyengine_uk import Microsimulation -from microdf import MicroDataFrame, MicroSeries - - -class Average(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - self.baseline = baseline - self.reformed = reformed - - def calculate(self): - - baseline_income = MicroSeries(self.baseline.calculate("household_net_income"), weights = self.baseline.calculate("household_weight")) - reform_income = MicroSeries(self.reformed.calculate("household_net_income") , weights = baseline_income.weights) - - decile = self.baseline.calculate("household_income_decile") - income_change = reform_income - baseline_income - - - avg_income_change_by_decile = ( - income_change.groupby(decile).sum() - / baseline_income.groupby(decile).count() - ) - - - avg_decile_dict = avg_income_change_by_decile.to_dict() - result = dict( - - average={int(k): v for k, v in avg_decile_dict.items() if k > 0}, - ) - - - return { - "average": result["average"], - - } \ No newline at end of file diff --git a/policyengine/economic_impact/distributional_impact/by_income_decile/relative/__init__.py b/policyengine/economic_impact/distributional_impact/by_income_decile/relative/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/policyengine/economic_impact/distributional_impact/by_income_decile/relative/relative.py b/policyengine/economic_impact/distributional_impact/by_income_decile/relative/relative.py deleted file mode 100644 index 33f6098..0000000 --- a/policyengine/economic_impact/distributional_impact/by_income_decile/relative/relative.py +++ /dev/null @@ -1,38 +0,0 @@ -from policyengine.economic_impact.base_metric_calculator import BaseMetricCalculator -from policyengine_uk import Microsimulation -from microdf import MicroDataFrame, MicroSeries - - -class Relative(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - self.baseline = baseline - self.reformed = reformed - - def calculate(self): - - baseline_income = MicroSeries(self.baseline.calculate("household_net_income"), weights = self.baseline.calculate("household_weight")) - reform_income = MicroSeries(self.reformed.calculate("household_net_income") , weights = baseline_income.weights) - - decile = self.baseline.calculate("household_income_decile") - income_change = reform_income - baseline_income - - - rel_income_change_by_decile = ( - income_change.groupby(decile).sum() - / baseline_income.groupby(decile).sum() - ) - - - rel_decile_dict = rel_income_change_by_decile.to_dict() - - result = dict( - relative={int(k): v for k, v in rel_decile_dict.items() if k > 0} - - ) - - - return { - "relative": result["relative"], - - } \ No newline at end of file diff --git a/policyengine/economic_impact/distributional_impact/by_wealth_decile/__init__.py b/policyengine/economic_impact/distributional_impact/by_wealth_decile/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/policyengine/economic_impact/distributional_impact/by_wealth_decile/average/__init__.py b/policyengine/economic_impact/distributional_impact/by_wealth_decile/average/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/policyengine/economic_impact/distributional_impact/by_wealth_decile/average/average.py b/policyengine/economic_impact/distributional_impact/by_wealth_decile/average/average.py deleted file mode 100644 index faac486..0000000 --- a/policyengine/economic_impact/distributional_impact/by_wealth_decile/average/average.py +++ /dev/null @@ -1,37 +0,0 @@ -from policyengine.economic_impact.base_metric_calculator import BaseMetricCalculator -from policyengine_uk import Microsimulation -from microdf import MicroDataFrame, MicroSeries - - -class Average(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - self.baseline = baseline - self.reformed = reformed - - def calculate(self): - - baseline_income = MicroSeries(self.baseline.calculate("household_net_income"), weights = self.baseline.calculate("household_weight")) - reform_income = MicroSeries(self.reformed.calculate("household_net_income") , weights = baseline_income.weights) - - decile = self.baseline.calculate("household_wealth_decile") - income_change = reform_income - baseline_income - - - avg_income_change_by_decile = ( - income_change.groupby(decile).sum() - / baseline_income.groupby(decile).count() - ) - - - avg_decile_dict = avg_income_change_by_decile.to_dict() - result = dict( - - average={int(k): v for k, v in avg_decile_dict.items() if k > 0}, - ) - - - return { - "average": result["average"], - - } \ No newline at end of file diff --git a/policyengine/economic_impact/distributional_impact/by_wealth_decile/relative/__init__.py b/policyengine/economic_impact/distributional_impact/by_wealth_decile/relative/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/policyengine/economic_impact/distributional_impact/by_wealth_decile/relative/relative.py b/policyengine/economic_impact/distributional_impact/by_wealth_decile/relative/relative.py deleted file mode 100644 index fdba948..0000000 --- a/policyengine/economic_impact/distributional_impact/by_wealth_decile/relative/relative.py +++ /dev/null @@ -1,38 +0,0 @@ -from policyengine.economic_impact.base_metric_calculator import BaseMetricCalculator -from policyengine_uk import Microsimulation -from microdf import MicroDataFrame, MicroSeries - - -class Relative(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - self.baseline = baseline - self.reformed = reformed - - def calculate(self): - - baseline_income = MicroSeries(self.baseline.calculate("household_net_income"), weights = self.baseline.calculate("household_weight")) - reform_income = MicroSeries(self.reformed.calculate("household_net_income") , weights = baseline_income.weights) - - decile = self.baseline.calculate("household_wealth_decile") - income_change = reform_income - baseline_income - - - rel_income_change_by_decile = ( - income_change.groupby(decile).sum() - / baseline_income.groupby(decile).sum() - ) - - - rel_decile_dict = rel_income_change_by_decile.to_dict() - - result = dict( - relative={int(k): v for k, v in rel_decile_dict.items() if k > 0} - - ) - - - return { - "relative": result["relative"], - - } \ No newline at end of file diff --git a/policyengine/economic_impact/economic_impact.py b/policyengine/economic_impact/economic_impact.py deleted file mode 100644 index 6e0d47c..0000000 --- a/policyengine/economic_impact/economic_impact.py +++ /dev/null @@ -1,300 +0,0 @@ -from policyengine_core.reforms import Reform -from .inequality_impact.inequality_impact import GiniCalculator, Top10PctShareCalculator, Top1PctShareCalculator -from .poverty_impact.regular_poverty.by_age.by_age import ( - ChildPoverty as RegularChildPoverty, - AdultPoverty as RegularAdultPoverty, - SeniorPoverty as RegularSeniorPoverty, - AllPoverty as RegularAgeAllPoverty -) -from .poverty_impact.regular_poverty.by_gender.by_gender import ( - MalePoverty as RegularMalePoverty, - FemalePoverty as RegularFemalePoverty, - AllPoverty as RegularGenderAllPoverty -) -from .poverty_impact.deep_poverty.by_age.by_age import ( - ChildPoverty as DeepChildPoverty, - AdultPoverty as DeepAdultPoverty, - SeniorPoverty as DeepSeniorPoverty, - AllPoverty as DeepAgeAllPoverty -) -from .poverty_impact.deep_poverty.by_gender.by_gender import ( - MalePoverty as DeepMalePoverty, - FemalePoverty as DeepFemalePoverty, - AllPoverty as DeepGenderAllPoverty -) - -from .budgetary_impact.by_program.by_program import ( - IncomeTax, - NationalInsurance, - Vat, - CouncilTax, - FuelDuty, - TaxCredits, - UniversalCredit, - ChildBenefit, - StatePension, - PensionCredit -) - - - -from .distributional_impact.by_income_decile.average.average import Average as AverageByIncome -from .distributional_impact.by_income_decile.relative.relative import Relative as RelativeByIncome - -from .distributional_impact.by_wealth_decile.average.average import Average as AverageByWealth -from .distributional_impact.by_wealth_decile.relative.relative import Relative as RelativeByWealth - -from .distributional_impact.by_income_decile.average.average import Average -from .distributional_impact.by_income_decile.relative.relative import Relative - - -from .budgetary_impact.overall.overall import ( - BudgetaryImpact, - BenefitSpendingImpact, - TaxRevenueImpact -) - - -from .labour_supply_impact.earnings.overall.relative.relative import IncomeLSR , SubstitutionLSR , NetLSRChange - -from .labour_supply_impact.earnings.overall.absolute.absolute import ( - IncomeLSR as AbsoluteIncomeLSR, - SubstitutionLSR as AbsoluteSubstitutionLSR, - NetLSRChange as AbsoluteNetLSRChange -) - -from .labour_supply_impact.earnings.by_decile.relative.substitution_effect.substitutional_effect import SubstitutionEffect -from .labour_supply_impact.earnings.by_decile.relative.income_effect.income_effect import IncomeEffect -from .labour_supply_impact.earnings.by_decile.relative.total.total import Total - -from .labour_supply_impact.earnings.by_decile.absolute.substitution_effect.substitution_effect import SubstitutionEffect as AbsoluteSubstutionEffect -from .labour_supply_impact.earnings.by_decile.absolute.income_effect.income_effect import IncomeEffect as AbsoluteIncomeEffect -from .labour_supply_impact.earnings.by_decile.absolute.total.total import Total as AbsoluteTotal - - -from .winners_and_losers.by_income_decile.by_income_decile import ByIncomeDecile -from .winners_and_losers.by_wealth_decile.by_wealth_decile import ByWealthDecile - - -from typing import Dict, Type, Union - -from policyengine.charts.inequality import InequalityImpactChart -from policyengine.charts.poverty.regular.by_age import RegularPovertyByAgeChart -from policyengine.charts.poverty.deep.by_age import DeepPovertyByAgeChart -from policyengine.charts.poverty.regular.by_gender import RegularPovertyByGenderChart -from policyengine.charts.poverty.deep.by_gender import DeepPovertyByGenderChart -from policyengine.charts.distributional_impact.by_income_decile.average import ByIncomeDecileAverageChart -from policyengine.charts.distributional_impact.by_income_decile.relative import ByIncomeDecileRelativeChart -from policyengine.charts.distributional_impact.by_wealth_decile.average import ByWealthDecileAverageChart -from policyengine.charts.distributional_impact.by_wealth_decile.relative import ByWealthDecileRelativeChart - - -class EconomicImpact: - """ - A class to calculate economic impact metrics based on different reforms and countries. - - Attributes: - reform (dict): Dictionary representing the reform parameters. - country (str): Country code in lowercase ('uk' or 'us'). - dataset (str, optional): Dataset to be used for the simulation. - Microsimulation (type): Class representing the microsimulation engine based on country. - baseline (Microsimulation): Instance of Microsimulation for baseline scenario. - reformed (Microsimulation): Instance of Microsimulation for reformed scenario based on given reform. - metric_calculators (Dict[str, BaseMetricCalculator]): Dictionary mapping metric names to metric calculators. - """ - - def __init__(self, reform: dict, country: str, dataset: str = None) -> None: - """ - Initialize EconomicImpact with reform parameters, country code, and optional dataset. - - Args: - reform (dict): Dictionary representing the reform parameters. - country (str): Country code in lowercase ('uk' or 'us'). - dataset (str, optional): Dataset to be used for the simulation. Defaults to None. - """ - self.reform = reform - self.country = country.lower() - self.dataset = dataset - self.Microsimulation = self._get_simulation_class() - - # Initialize baseline and reformed simulations - self.baseline = self.Microsimulation(dataset=self.dataset) - self.reformed = self.Microsimulation(reform=Reform.from_dict(self.reform, country_id=self.country), dataset=self.dataset) - - # Set up metric calculators - self.metric_calculators: Dict[str, object] = { - "budgetary/overall/budgetary_impact" : BudgetaryImpact(self.baseline, self.reformed), - "budgetary/overall/benefit_spending_impact" : BenefitSpendingImpact(self.baseline, self.reformed), - "budgetary/overall/tax_revenue_impact" : TaxRevenueImpact(self.baseline, self.reformed), - "budgetary/by_program/income_tax" : IncomeTax(self.baseline, self.reformed), - "budgetary/by_program/national_insurance" : NationalInsurance(self.baseline, self.reformed), - "budgetary/by_program/vat" : Vat(self.baseline, self.reformed), - "budgetary/by_program/council_tax" : CouncilTax(self.baseline, self.reformed), - "budgetary/by_program/fuel_duty" : FuelDuty(self.baseline, self.reformed), - "budgetary/by_program/tax_credits" : TaxCredits(self.baseline, self.reformed), - "budgetary/by_program/universal_credits" : UniversalCredit(self.baseline, self.reformed), - "budgetary/by_program/child_benefits" : ChildBenefit(self.baseline, self.reformed), - "budgetary/by_program/state_pension" : StatePension(self.baseline, self.reformed), - "budgetary/by_program/pension_credit" : PensionCredit(self.baseline, self.reformed), - "inequality/gini": GiniCalculator(self.baseline, self.reformed), - "inequality/top_1_pct_share": Top1PctShareCalculator(self.baseline, self.reformed), - "inequality/top_10_pct_share": Top10PctShareCalculator(self.baseline, self.reformed), - "poverty/regular/child": RegularChildPoverty(self.baseline, self.reformed), - "poverty/regular/adult": RegularAdultPoverty(self.baseline, self.reformed), - "poverty/regular/senior": RegularSeniorPoverty(self.baseline, self.reformed), - "poverty/regular/age/all": RegularAgeAllPoverty(self.baseline, self.reformed), - "poverty/regular/male": RegularMalePoverty(self.baseline, self.reformed), - "poverty/regular/female": RegularFemalePoverty(self.baseline, self.reformed), - "poverty/regular/gender/all": RegularGenderAllPoverty(self.baseline, self.reformed), - "poverty/deep/child": DeepChildPoverty(self.baseline, self.reformed), - "poverty/deep/adult": DeepAdultPoverty(self.baseline, self.reformed), - "poverty/deep/senior": DeepSeniorPoverty(self.baseline, self.reformed), - "poverty/deep/age/all": DeepAgeAllPoverty(self.baseline, self.reformed), - "poverty/deep/male": DeepMalePoverty(self.baseline, self.reformed), - "poverty/deep/female": DeepFemalePoverty(self.baseline, self.reformed), - "poverty/deep/gender/all": DeepGenderAllPoverty(self.baseline, self.reformed), - - "labour_supply_impact/earnings/overall/relative/IncomeLSR" : IncomeLSR(self.baseline,self.reformed), - "labour_supply_impact/earnings/overall/relative/SubstitutionLSR" : SubstitutionLSR(self.baseline,self.reformed), - "labour_supply_impact/earnings/overall/relative/NetLSRChange" : NetLSRChange(self.baseline,self.reformed), - "labour_supply_impact/earnings/overall/absolute/IncomeLSR" : AbsoluteIncomeLSR(self.baseline,self.reformed), - "labour_supply_impact/earnings/overall/absolute/SubstitutionLSR" : AbsoluteSubstitutionLSR(self.baseline,self.reformed), - "labour_supply_impact/earnings/overall/absolute/NetLSRChange" : AbsoluteNetLSRChange(self.baseline,self.reformed), - "labour_supply_impact/earnings/by_decile/relative/IncomeEffect" : IncomeEffect(self.baseline,self.reformed), - "labour_supply_impact/earnings/by_decile/relative/SubstitutionEffect" : SubstitutionEffect(self.baseline,self.reformed), - "labour_supply_impact/earnings/by_decile/relative/Total" : Total(self.baseline,self.reformed), - "labour_supply_impact/earnings/by_decile/absolute/income_effect" : AbsoluteIncomeEffect(self.baseline,self.reformed), - "labour_supply_impact/earnings/by_decile/absolute/substitution_effect" : AbsoluteSubstutionEffect(self.baseline,self.reformed), - "labour_supply_impact/earnings/by_decile/absolute/total" : AbsoluteTotal(self.baseline,self.reformed), - - - "distributional/by_income/average": AverageByIncome(self.baseline, self.reformed), - "distributional/by_income/relative": RelativeByIncome(self.baseline, self.reformed), - "distributional/by_wealth/average": AverageByWealth(self.baseline, self.reformed), - "distributional/by_wealth/relative": RelativeByWealth(self.baseline, self.reformed), - - "distributional/by_income/average": Average(self.baseline, self.reformed), - "distributional/by_income/relative": Relative(self.baseline, self.reformed), - - "winners_and_losers/by_income_decile": ByIncomeDecile(self.baseline, self.reformed), - "winners_and_losers/by_wealth_decile": ByWealthDecile(self.baseline, self.reformed), - - } - - - self.chart_generators: Dict[str, Type] = { - "inequality": InequalityImpactChart, - "poverty/regular/by_age": RegularPovertyByAgeChart, - "poverty/regular/by_gender": RegularPovertyByGenderChart, - "poverty/deep/by_age": DeepPovertyByAgeChart, - "poverty/deep/by_gender": DeepPovertyByGenderChart, - "distributional/by_income/average": ByIncomeDecileAverageChart, - "distributional/by_income/relative": ByIncomeDecileRelativeChart, - "distributional/by_wealth/average": ByWealthDecileAverageChart, - "distributional/by_wealth/relative": ByWealthDecileRelativeChart - } - - self.composite_metrics: Dict[str, Dict[str, str]] = { - "inequality": { - "Gini index": "inequality/gini", - "Top 1% share": "inequality/top_1_pct_share", - "Top 10% share": "inequality/top_10_pct_share", - }, - "poverty/regular/by_age": { - "Child": "poverty/regular/child", - "Adult": "poverty/regular/adult", - "Senior":"poverty/regular/senior", - "All": "poverty/regular/age/all" - }, - "poverty/regular/by_gender": { - "Male": "poverty/regular/male", - "Female": "poverty/regular/female", - "All": "poverty/regular/gender/all" - }, - "poverty/deep/by_age": { - "Child": "poverty/deep/child", - "Adult": "poverty/deep/adult", - "Senior":"poverty/deep/senior", - "All": "poverty/deep/age/all" - }, - "poverty/deep/by_gender": { - "Male": "poverty/deep/male", - "Female": "poverty/deep/female", - "All": "poverty/deep/gender/all" - } - } - - self.metric_results: Dict[str, any] = {} - - def _get_simulation_class(self) -> type: - """ - Get the appropriate Microsimulation class based on the country code. - - Returns: - type: Microsimulation class based on the country. - - Raises: - ValueError: If the country is not supported ('uk' or 'us'). - """ - if self.country == "uk": - from policyengine_uk import Microsimulation - elif self.country == "us": - from policyengine_us import Microsimulation - else: - raise ValueError(f"Unsupported country: {self.country}") - return Microsimulation - - def calculate(self, metric: str) -> dict: - """ - Calculate the specified economic impact metric. - - Args: - metric (str): Name of the metric to calculate ("inequality/gini", "inequality/top_1_pct_share", "inequality/top_10_pct_share"). - - Returns: - dict: Dictionary containing metric values ("baseline", "reform", "change"). - - Raises: - ValueError: If the metric is unknown. - """ - if metric not in self.metric_calculators: - raise ValueError(f"Unknown metric: {metric}") - - if metric not in self.metric_results: - result = self.metric_calculators[metric].calculate() - self.metric_results[metric] = result - - return self.metric_results[metric] - - def _calculate_composite_metric(self, metric: str) -> dict: - if metric not in self.composite_metrics: - raise ValueError(f"Unknown composite metric: {metric}") - - composite_data = {} - for key, sub_metric in self.composite_metrics[metric].items(): - composite_data[key] = self.calculate(sub_metric) - - return composite_data - - def chart(self, metric: str) -> dict: - if metric in self.composite_metrics: - data = self._calculate_composite_metric(metric) - elif metric in self.chart_generators: - data = self.calculate(metric) - else: - raise ValueError(f"Unknown metric for charting: {metric}") - - chart_generator = self.chart_generators.get(metric) - if not chart_generator: - raise ValueError(f"No chart generator found for metric: {metric}") - - return chart_generator(self.country,data=data).generate_chart_data() - - def add_metric(self, metric: str, calculator: object, chart_generator: Type = None): - self.metric_calculators[metric] = calculator - if chart_generator: - self.chart_generators[metric] = chart_generator - - def add_composite_metric(self, name: str, components: Dict[str, str], chart_generator: Type): - self.composite_metrics[name] = components - self.chart_generators[name] = chart_generator \ No newline at end of file diff --git a/policyengine/economic_impact/inequality_impact/__init__.py b/policyengine/economic_impact/inequality_impact/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/policyengine/economic_impact/inequality_impact/inequality_impact.py b/policyengine/economic_impact/inequality_impact/inequality_impact.py deleted file mode 100644 index d47ab34..0000000 --- a/policyengine/economic_impact/inequality_impact/inequality_impact.py +++ /dev/null @@ -1,164 +0,0 @@ -from policyengine_core.reforms import Reform -from typing import Dict, Union - -class BaseMetricCalculator: - """ - Base class for calculating metrics based on baseline and reformed data. - - Attributes: - baseline (object): Object representing baseline data. - reformed (object): Object representing reformed data. - """ - def __init__(self, baseline: object, reformed: object, default_period: int = 2024) -> None: - """ - Initialize with baseline and reformed data objects. - - Args: - baseline (object): Object representing baseline data. - reformed (object): Object representing reformed data. - """ - self.baseline = baseline - self.reformed = reformed - self.default_period = default_period - self.baseline.default_calculation_period = default_period - self.reformed.default_calculation_period = default_period - - def calculate_baseline(self, variable: str, period: int = None) -> Union[float, int]: - """ - Calculate baseline metric value. - - Args: - variable (str): Variable to calculate. - period (int): Year or period for calculation (default: 2024). - - Returns: - Union[float, int]: Calculated metric value. - """ - if period is None: - period = self.default_period - return self.baseline.calculate(variable, period=period) - - def calculate_reformed(self, variable: str, period: int = None) -> Union[float, int]: - """ - Calculate reformed metric value. - - Args: - variable (str): Variable to calculate. - period (int): Year or period for calculation (default: 2024). - - Returns: - Union[float, int]: Calculated metric value. - """ - if period is None: - period = self.default_period - return self.reformed.calculate(variable, period=period) - -class GiniCalculator(BaseMetricCalculator): - """ - Calculate Gini coefficient metrics based on baseline and reformed data. - Inherits from BaseMetricCalculator. - """ - def calculate(self) -> dict: - baseline_personal_hh_equiv_income = self.calculate_baseline("equiv_household_net_income") - baseline_household_count_people = self.calculate_baseline("household_count_people") - baseline_personal_hh_equiv_income.weights *= baseline_household_count_people - - reformed_personal_hh_equiv_income = self.calculate_reformed("equiv_household_net_income") - reformed_household_count_people = self.calculate_reformed("household_count_people") - reformed_personal_hh_equiv_income.weights *= reformed_household_count_people - - try: - baseline_value = baseline_personal_hh_equiv_income.gini() - except: - print("WARNING: Baseline Gini index calculations resulted in an error: returning 0.4, but this is inaccurate.") - baseline_value = 0.4 - - try: - reformed_value = reformed_personal_hh_equiv_income.gini() - except: - print("WARNING: Reformed Gini index calculations resulted in an error: returning 0.4, but this is inaccurate.") - reformed_value = 0.4 - - change_value = reformed_value - baseline_value - change_perc = (change_value / baseline_value) * 100 - - return { - "baseline": round(baseline_value,2), - "reform": round(reformed_value,2), - "change": round(change_value,2), - "change_percentage": round(change_perc,2) - } - -class Top1PctShareCalculator(BaseMetricCalculator): - """ - Calculate top 1% income share metrics based on baseline and reformed data. - Inherits from BaseMetricCalculator. - """ - def calculate(self) -> dict: - baseline_personal_hh_equiv_income = self.calculate_baseline("equiv_household_net_income") - baseline_household_count_people = self.calculate_baseline("household_count_people") - baseline_personal_hh_equiv_income.weights *= baseline_household_count_people - in_top_1_pct = baseline_personal_hh_equiv_income.percentile_rank() == 100 - baseline_personal_hh_equiv_income.weights /= baseline_household_count_people - baseline_top_1_pct_share = ( - baseline_personal_hh_equiv_income[in_top_1_pct].sum() - / baseline_personal_hh_equiv_income.sum() - ) - - reformed_personal_hh_equiv_income = self.calculate_reformed("equiv_household_net_income") - reformed_household_count_people = self.calculate_reformed("household_count_people") - reformed_personal_hh_equiv_income.weights *= reformed_household_count_people - in_top_1_pct = reformed_personal_hh_equiv_income.percentile_rank() == 100 - reformed_personal_hh_equiv_income.weights /= reformed_household_count_people - reformed_top_1_pct_share = ( - reformed_personal_hh_equiv_income[in_top_1_pct].sum() - / reformed_personal_hh_equiv_income.sum() - ) - - - change_value = reformed_top_1_pct_share - baseline_top_1_pct_share - change_perc = (change_value / baseline_top_1_pct_share) * 100 - - return { - "baseline": round(baseline_top_1_pct_share,2), - "reform": round(reformed_top_1_pct_share,2), - "change": round(change_value,2), - "change_percentage": round(change_perc,2) - } - -class Top10PctShareCalculator(BaseMetricCalculator): - """ - Calculate top 10% income share metrics based on baseline and reformed data. - Inherits from BaseMetricCalculator. - """ - def calculate(self) -> dict: - - baseline_personal_hh_equiv_income = self.calculate_baseline("equiv_household_net_income") - baseline_household_count_people = self.calculate_baseline("household_count_people") - baseline_personal_hh_equiv_income.weights *= baseline_household_count_people - in_top_10_pct = baseline_personal_hh_equiv_income.decile_rank() == 10 - baseline_personal_hh_equiv_income.weights /= baseline_household_count_people - baseline_top_10_pct_share = ( - baseline_personal_hh_equiv_income[in_top_10_pct].sum() - / baseline_personal_hh_equiv_income.sum() - ) - - reformed_personal_hh_equiv_income = self.calculate_reformed("equiv_household_net_income") - reformed_household_count_people = self.calculate_reformed("household_count_people") - reformed_personal_hh_equiv_income.weights *= reformed_household_count_people - in_top_10_pct = reformed_personal_hh_equiv_income.decile_rank() == 10 - reformed_personal_hh_equiv_income.weights /= reformed_household_count_people - reformed_top_10_pct_share = ( - reformed_personal_hh_equiv_income[in_top_10_pct].sum() - / reformed_personal_hh_equiv_income.sum() - ) - - change_value = reformed_top_10_pct_share - baseline_top_10_pct_share - change_perc = (change_value / baseline_top_10_pct_share) * 100 - - return { - "baseline": round(baseline_top_10_pct_share,2), - "reform": round(reformed_top_10_pct_share,2), - "change": round(change_value,2), - "change_percentage": round(change_perc,2) - } diff --git a/policyengine/economic_impact/labour_supply_impact/__init__.py b/policyengine/economic_impact/labour_supply_impact/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/policyengine/economic_impact/labour_supply_impact/earnings/by_decile/__init__.py b/policyengine/economic_impact/labour_supply_impact/earnings/by_decile/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/policyengine/economic_impact/labour_supply_impact/earnings/by_decile/absolute/__init__.py b/policyengine/economic_impact/labour_supply_impact/earnings/by_decile/absolute/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/policyengine/economic_impact/labour_supply_impact/earnings/by_decile/absolute/income_effect/__init__.py b/policyengine/economic_impact/labour_supply_impact/earnings/by_decile/absolute/income_effect/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/policyengine/economic_impact/labour_supply_impact/earnings/by_decile/absolute/income_effect/income_effect.py b/policyengine/economic_impact/labour_supply_impact/earnings/by_decile/absolute/income_effect/income_effect.py deleted file mode 100644 index 661e7ce..0000000 --- a/policyengine/economic_impact/labour_supply_impact/earnings/by_decile/absolute/income_effect/income_effect.py +++ /dev/null @@ -1,93 +0,0 @@ -from policyengine.economic_impact.base_metric_calculator import BaseMetricCalculator -from policyengine_uk import Microsimulation -from policyengine_core.reforms import Reform -from microdf import MicroSeries -import numpy as np -from typing import Dict, Union - - -class IncomeEffect(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - - def calculate(self): - # Calculate household weight - household_weight = self.baseline.calculate("household_weight") - - # Calculate household count people - household_count_people_baseline = self.baseline.calculate("household_count_people") - household_count_people_reformed = self.reformed.calculate("household_count_people") - - # Initialize income LSR and substitution LSR - income_lsr_hh_baseline = np.zeros_like(household_count_people_baseline, dtype=float) - income_lsr_hh_reformed = np.zeros_like(household_count_people_reformed, dtype=float) - - substitution_lsr_hh_baseline = np.zeros_like(household_count_people_baseline, dtype=float) - substitution_lsr_hh_reformed = np.zeros_like(household_count_people_reformed, dtype=float) - - # Check if behavioral response exists and update income LSR - if "employment_income_behavioral_response" in self.baseline.tax_benefit_system.variables: - if np.any(self.baseline.calculate("employment_income_behavioral_response") != 0): - income_elasticity_baseline = self.baseline.calculate("income_elasticity_lsr", map_to="household") - income_lsr_hh_baseline = income_elasticity_baseline.astype(float) - - if "employment_income_behavioral_response" in self.reformed.tax_benefit_system.variables: - if np.any(self.reformed.calculate("employment_income_behavioral_response") != 0): - income_elasticity_reformed = self.reformed.calculate("income_elasticity_lsr", map_to="household") - income_lsr_hh_reformed = income_elasticity_reformed.astype(float) - - if "employment_income_behavioral_response" in self.baseline.tax_benefit_system.variables: - if np.any(self.baseline.calculate("employment_income_behavioral_response") != 0): - substitution_lsr_hh_baseline = ( - self.baseline.calculate("substitution_elasticity_lsr", map_to="household") - .astype(float) - ) - - if "employment_income_behavioral_response" in self.reformed.tax_benefit_system.variables: - if np.any(self.reformed.calculate("employment_income_behavioral_response") != 0): - substitution_lsr_hh_reformed = ( - self.reformed.calculate("substitution_elasticity_lsr", map_to="household") - .astype(float) - ) - - # Calculate differences - substitution_lsr_hh = np.array(substitution_lsr_hh_reformed) - np.array(substitution_lsr_hh_baseline) - income_lsr_hh = np.array(income_lsr_hh_reformed) - np.array(income_lsr_hh_baseline) - - # Convert to MicroSeries - income_lsr_hh = MicroSeries(income_lsr_hh, weights=household_weight) - substitution_lsr_hh = MicroSeries(substitution_lsr_hh, weights=household_weight) - - decile = np.array(self.baseline.calculate("household_income_decile")) - total_lsr_hh = substitution_lsr_hh + income_lsr_hh - - # Calculate earnings - emp_income = MicroSeries( - self.baseline.calculate("employment_income", map_to="household").astype(float), - weights=household_weight - ) - - self_emp_income = MicroSeries( - self.baseline.calculate("self_employment_income", map_to="household").astype(float), - weights=household_weight - ) - - earnings = emp_income + self_emp_income - original_earnings = earnings - total_lsr_hh - - # Calculate decile relative and averages - decile_rel = dict( - income=( - income_lsr_hh.groupby(decile).sum() / original_earnings.groupby(decile).sum() - ).to_dict(), - ) - - decile_avg = dict( - income=income_lsr_hh.groupby(decile).mean().to_dict() - ) - - decile_rel["income"] = {int(k): round(v * 100,2) for k, v in decile_rel["income"].items() if k > 0} - - return { - "decile_avg": decile_avg - } \ No newline at end of file diff --git a/policyengine/economic_impact/labour_supply_impact/earnings/by_decile/absolute/substitution_effect/__init__.py b/policyengine/economic_impact/labour_supply_impact/earnings/by_decile/absolute/substitution_effect/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/policyengine/economic_impact/labour_supply_impact/earnings/by_decile/absolute/substitution_effect/substitution_effect.py b/policyengine/economic_impact/labour_supply_impact/earnings/by_decile/absolute/substitution_effect/substitution_effect.py deleted file mode 100644 index 8bd487b..0000000 --- a/policyengine/economic_impact/labour_supply_impact/earnings/by_decile/absolute/substitution_effect/substitution_effect.py +++ /dev/null @@ -1,92 +0,0 @@ -from policyengine.economic_impact.base_metric_calculator import BaseMetricCalculator -from policyengine_uk import Microsimulation -from policyengine_core.reforms import Reform -from microdf import MicroSeries -import numpy as np -from typing import Dict, Union - -class SubstitutionEffect(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - - def calculate(self): - # Calculate household weight - household_weight = self.baseline.calculate("household_weight") - - # Calculate household count people - household_count_people_baseline = self.baseline.calculate("household_count_people") - household_count_people_reformed = self.reformed.calculate("household_count_people") - - # Initialize income LSR and substitution LSR - income_lsr_hh_baseline = np.zeros_like(household_count_people_baseline, dtype=float) - income_lsr_hh_reformed = np.zeros_like(household_count_people_reformed, dtype=float) - - substitution_lsr_hh_baseline = np.zeros_like(household_count_people_baseline, dtype=float) - substitution_lsr_hh_reformed = np.zeros_like(household_count_people_reformed, dtype=float) - - # Check if behavioral response exists and update income LSR - if "employment_income_behavioral_response" in self.baseline.tax_benefit_system.variables: - if np.any(self.baseline.calculate("employment_income_behavioral_response") != 0): - income_elasticity_baseline = self.baseline.calculate("income_elasticity_lsr", map_to="household") - income_lsr_hh_baseline = income_elasticity_baseline.astype(float) - - if "employment_income_behavioral_response" in self.reformed.tax_benefit_system.variables: - if np.any(self.reformed.calculate("employment_income_behavioral_response") != 0): - income_elasticity_reformed = self.reformed.calculate("income_elasticity_lsr", map_to="household") - income_lsr_hh_reformed = income_elasticity_reformed.astype(float) - - if "employment_income_behavioral_response" in self.baseline.tax_benefit_system.variables: - if np.any(self.baseline.calculate("employment_income_behavioral_response") != 0): - substitution_lsr_hh_baseline = ( - self.baseline.calculate("substitution_elasticity_lsr", map_to="household") - .astype(float) - ) - - if "employment_income_behavioral_response" in self.reformed.tax_benefit_system.variables: - if np.any(self.reformed.calculate("employment_income_behavioral_response") != 0): - substitution_lsr_hh_reformed = ( - self.reformed.calculate("substitution_elasticity_lsr", map_to="household") - .astype(float) - ) - - # Calculate differences - substitution_lsr_hh = np.array(substitution_lsr_hh_reformed) - np.array(substitution_lsr_hh_baseline) - income_lsr_hh = np.array(income_lsr_hh_reformed) - np.array(income_lsr_hh_baseline) - - # Convert to MicroSeries - income_lsr_hh = MicroSeries(income_lsr_hh, weights=household_weight) - substitution_lsr_hh = MicroSeries(substitution_lsr_hh, weights=household_weight) - - decile = np.array(self.baseline.calculate("household_income_decile")) - total_lsr_hh = substitution_lsr_hh + income_lsr_hh - - # Calculate earnings - emp_income = MicroSeries( - self.baseline.calculate("employment_income", map_to="household").astype(float), - weights=household_weight - ) - - self_emp_income = MicroSeries( - self.baseline.calculate("self_employment_income", map_to="household").astype(float), - weights=household_weight - ) - - earnings = emp_income + self_emp_income - original_earnings = earnings - total_lsr_hh - - # Calculate decile relative and averages - decile_rel = dict( - income=( - income_lsr_hh.groupby(decile).sum() / original_earnings.groupby(decile).sum() - ).to_dict(), - ) - - decile_avg = dict( - substitution=substitution_lsr_hh.groupby(decile).mean().to_dict(), - ) - - decile_rel["income"] = {int(k): round(v * 100,2) for k, v in decile_rel["income"].items() if k > 0} - - return { - "decile_avg": decile_avg - } \ No newline at end of file diff --git a/policyengine/economic_impact/labour_supply_impact/earnings/by_decile/absolute/total/__init__.py b/policyengine/economic_impact/labour_supply_impact/earnings/by_decile/absolute/total/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/policyengine/economic_impact/labour_supply_impact/earnings/by_decile/absolute/total/total.py b/policyengine/economic_impact/labour_supply_impact/earnings/by_decile/absolute/total/total.py deleted file mode 100644 index 8ac9e9f..0000000 --- a/policyengine/economic_impact/labour_supply_impact/earnings/by_decile/absolute/total/total.py +++ /dev/null @@ -1,203 +0,0 @@ -from policyengine.economic_impact.base_metric_calculator import BaseMetricCalculator -from policyengine_uk import Microsimulation -from policyengine_core.reforms import Reform -from microdf import MicroSeries -import numpy as np -from typing import Dict, Union - - -class IncomeEffect(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - - def calculate(self): - # Calculate household weight - household_weight = self.baseline.calculate("household_weight") - - # Calculate household count people - household_count_people_baseline = self.baseline.calculate("household_count_people") - household_count_people_reformed = self.reformed.calculate("household_count_people") - - # Initialize income LSR and substitution LSR - income_lsr_hh_baseline = np.zeros_like(household_count_people_baseline, dtype=float) - income_lsr_hh_reformed = np.zeros_like(household_count_people_reformed, dtype=float) - - substitution_lsr_hh_baseline = np.zeros_like(household_count_people_baseline, dtype=float) - substitution_lsr_hh_reformed = np.zeros_like(household_count_people_reformed, dtype=float) - - # Check if behavioral response exists and update income LSR - if "employment_income_behavioral_response" in self.baseline.tax_benefit_system.variables: - if np.any(self.baseline.calculate("employment_income_behavioral_response") != 0): - income_elasticity_baseline = self.baseline.calculate("income_elasticity_lsr", map_to="household") - income_lsr_hh_baseline = income_elasticity_baseline.astype(float) - - if "employment_income_behavioral_response" in self.reformed.tax_benefit_system.variables: - if np.any(self.reformed.calculate("employment_income_behavioral_response") != 0): - income_elasticity_reformed = self.reformed.calculate("income_elasticity_lsr", map_to="household") - income_lsr_hh_reformed = income_elasticity_reformed.astype(float) - - if "employment_income_behavioral_response" in self.baseline.tax_benefit_system.variables: - if np.any(self.baseline.calculate("employment_income_behavioral_response") != 0): - substitution_lsr_hh_baseline = ( - self.baseline.calculate("substitution_elasticity_lsr", map_to="household") - .astype(float) - ) - - if "employment_income_behavioral_response" in self.reformed.tax_benefit_system.variables: - if np.any(self.reformed.calculate("employment_income_behavioral_response") != 0): - substitution_lsr_hh_reformed = ( - self.reformed.calculate("substitution_elasticity_lsr", map_to="household") - .astype(float) - ) - - # Calculate differences - substitution_lsr_hh = np.array(substitution_lsr_hh_reformed) - np.array(substitution_lsr_hh_baseline) - income_lsr_hh = np.array(income_lsr_hh_reformed) - np.array(income_lsr_hh_baseline) - - # Convert to MicroSeries - income_lsr_hh = MicroSeries(income_lsr_hh, weights=household_weight) - substitution_lsr_hh = MicroSeries(substitution_lsr_hh, weights=household_weight) - - decile = np.array(self.baseline.calculate("household_income_decile")) - total_lsr_hh = substitution_lsr_hh + income_lsr_hh - - # Calculate earnings - emp_income = MicroSeries( - self.baseline.calculate("employment_income", map_to="household").astype(float), - weights=household_weight - ) - - self_emp_income = MicroSeries( - self.baseline.calculate("self_employment_income", map_to="household").astype(float), - weights=household_weight - ) - - earnings = emp_income + self_emp_income - original_earnings = earnings - total_lsr_hh - - # Calculate decile relative and averages - decile_rel = dict( - income=( - income_lsr_hh.groupby(decile).sum() / original_earnings.groupby(decile).sum() - ).to_dict(), - ) - - decile_avg = dict( - income=income_lsr_hh.groupby(decile).mean().to_dict() - ) - - decile_rel["income"] = {int(k): v * 100 for k, v in decile_rel["income"].items() if k > 0} - - return { - "decile_avg": decile_avg - } - -class SubstitutionEffect(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - - def calculate(self): - # Calculate household weight - household_weight = self.baseline.calculate("household_weight") - - # Calculate household count people - household_count_people_baseline = self.baseline.calculate("household_count_people") - household_count_people_reformed = self.reformed.calculate("household_count_people") - - # Initialize income LSR and substitution LSR - income_lsr_hh_baseline = np.zeros_like(household_count_people_baseline, dtype=float) - income_lsr_hh_reformed = np.zeros_like(household_count_people_reformed, dtype=float) - - substitution_lsr_hh_baseline = np.zeros_like(household_count_people_baseline, dtype=float) - substitution_lsr_hh_reformed = np.zeros_like(household_count_people_reformed, dtype=float) - - # Check if behavioral response exists and update income LSR - if "employment_income_behavioral_response" in self.baseline.tax_benefit_system.variables: - if np.any(self.baseline.calculate("employment_income_behavioral_response") != 0): - income_elasticity_baseline = self.baseline.calculate("income_elasticity_lsr", map_to="household") - income_lsr_hh_baseline = income_elasticity_baseline.astype(float) - - if "employment_income_behavioral_response" in self.reformed.tax_benefit_system.variables: - if np.any(self.reformed.calculate("employment_income_behavioral_response") != 0): - income_elasticity_reformed = self.reformed.calculate("income_elasticity_lsr", map_to="household") - income_lsr_hh_reformed = income_elasticity_reformed.astype(float) - - if "employment_income_behavioral_response" in self.baseline.tax_benefit_system.variables: - if np.any(self.baseline.calculate("employment_income_behavioral_response") != 0): - substitution_lsr_hh_baseline = ( - self.baseline.calculate("substitution_elasticity_lsr", map_to="household") - .astype(float) - ) - - if "employment_income_behavioral_response" in self.reformed.tax_benefit_system.variables: - if np.any(self.reformed.calculate("employment_income_behavioral_response") != 0): - substitution_lsr_hh_reformed = ( - self.reformed.calculate("substitution_elasticity_lsr", map_to="household") - .astype(float) - ) - - # Calculate differences - substitution_lsr_hh = np.array(substitution_lsr_hh_reformed) - np.array(substitution_lsr_hh_baseline) - income_lsr_hh = np.array(income_lsr_hh_reformed) - np.array(income_lsr_hh_baseline) - - # Convert to MicroSeries - income_lsr_hh = MicroSeries(income_lsr_hh, weights=household_weight) - substitution_lsr_hh = MicroSeries(substitution_lsr_hh, weights=household_weight) - - decile = np.array(self.baseline.calculate("household_income_decile")) - total_lsr_hh = substitution_lsr_hh + income_lsr_hh - - # Calculate earnings - emp_income = MicroSeries( - self.baseline.calculate("employment_income", map_to="household").astype(float), - weights=household_weight - ) - - self_emp_income = MicroSeries( - self.baseline.calculate("self_employment_income", map_to="household").astype(float), - weights=household_weight - ) - - earnings = emp_income + self_emp_income - original_earnings = earnings - total_lsr_hh - - # Calculate decile relative and averages - decile_rel = dict( - income=( - income_lsr_hh.groupby(decile).sum() / original_earnings.groupby(decile).sum() - ).to_dict(), - ) - - decile_avg = dict( - substitution=substitution_lsr_hh.groupby(decile).mean().to_dict(), - ) - - decile_rel["income"] = {int(k): v * 100 for k, v in decile_rel["income"].items() if k > 0} - - return { - "decile_avg": decile_avg - } - -class Total(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - self.baseline = baseline - self.reformed = reformed - - def calculate(self): - income_result = IncomeEffect(baseline=self.baseline, reformed=self.reformed).calculate() - substitution_result = SubstitutionEffect(baseline=self.baseline, reformed=self.reformed).calculate() - - income_dict = income_result['decile_avg']["income"] - substitution_dict = substitution_result['decile_avg']["substitution"] - - # Ensure all keys are present in both dictionaries - all_keys = set(income_dict.keys()).union(set(substitution_dict.keys())) - - net_change = {} - for key in all_keys: - income_value = income_dict.get(key, 0) - substitution_value = substitution_dict.get(key, 0) - net_change[key] = round(income_value + substitution_value, 4) - - return {"net_change": net_change} \ No newline at end of file diff --git a/policyengine/economic_impact/labour_supply_impact/earnings/by_decile/relative/__init__.py b/policyengine/economic_impact/labour_supply_impact/earnings/by_decile/relative/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/policyengine/economic_impact/labour_supply_impact/earnings/by_decile/relative/income_effect/__init__.py b/policyengine/economic_impact/labour_supply_impact/earnings/by_decile/relative/income_effect/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/policyengine/economic_impact/labour_supply_impact/earnings/by_decile/relative/income_effect/income_effect.py b/policyengine/economic_impact/labour_supply_impact/earnings/by_decile/relative/income_effect/income_effect.py deleted file mode 100644 index 960a5ea..0000000 --- a/policyengine/economic_impact/labour_supply_impact/earnings/by_decile/relative/income_effect/income_effect.py +++ /dev/null @@ -1,97 +0,0 @@ -from policyengine.economic_impact.base_metric_calculator import BaseMetricCalculator -from policyengine_uk import Microsimulation -from policyengine_core.reforms import Reform -from microdf import MicroSeries -import numpy as np - -class IncomeEffect(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - self.baseline = baseline - self.reformed = reformed - - def calculate(self): - # Calculate household weight - household_weight = self.baseline.calculate("household_weight") - - # Calculate household count people - household_count_people_baseline = self.baseline.calculate("household_count_people") - household_count_people_reformed = self.reformed.calculate("household_count_people") - - # Initialize income LSR - income_lsr_hh_baseline = (household_count_people_baseline * 0).astype(float).tolist() - income_lsr_hh_reformed = (household_count_people_reformed * 0).astype(float).tolist() - - - # Check if behavioral response exists and update income LSR - if "employment_income_behavioral_response" in self.baseline.tax_benefit_system.variables: - if any(self.baseline.calculate("employment_income_behavioral_response") != 0): - income_elasticity_baseline = self.baseline.calculate("income_elasticity_lsr", map_to="household") - income_lsr_hh_baseline = income_elasticity_baseline.astype(float).tolist() - - if "employment_income_behavioral_response" in self.reformed.tax_benefit_system.variables: - if any(self.reformed.calculate("employment_income_behavioral_response") != 0): - income_elasticity_reformed = self.reformed.calculate("income_elasticity_lsr", map_to="household") - income_lsr_hh_reformed = income_elasticity_reformed.astype(float).tolist() - - - # Calculate substitution LSR - substitution_lsr_hh_baseline = (household_count_people_baseline * 0).astype(float).tolist() - if "employment_income_behavioral_response" in self.baseline.tax_benefit_system.variables: - if any(self.baseline.calculate("employment_income_behavioral_response") != 0): - substitution_lsr_hh_baseline = ( - self.baseline.calculate("substitution_elasticity_lsr", map_to="household") - .astype(float) - .tolist() - ) - - substitution_lsr_hh_reformed = (household_count_people_reformed * 0).astype(float).tolist() - if "employment_income_behavioral_response" in self.reformed.tax_benefit_system.variables: - if any(self.reformed.calculate("employment_income_behavioral_response") != 0): - substitution_lsr_hh_reformed = ( - self.reformed.calculate("substitution_elasticity_lsr", map_to="household") - .astype(float) - .tolist() - ) - - - substitution_lsr_hh = np.array(substitution_lsr_hh_reformed) - np.array(substitution_lsr_hh_baseline) - income_lsr_hh = np.array(income_lsr_hh_reformed) - np.array(income_lsr_hh_baseline) - - # Convert to MicroSeries - income_lsr_hh = MicroSeries(income_lsr_hh, weights=household_weight) - substitution_lsr_hh = MicroSeries(substitution_lsr_hh, weights=household_weight) - - decile = np.array(self.baseline.calculate("household_income_decile")) - total_lsr_hh = substitution_lsr_hh + income_lsr_hh - - # Calculate earnings - emp_income = MicroSeries( - self.baseline.calculate("employment_income", map_to="household").astype(float).tolist(), - weights=household_weight - ) - - self_emp_income = MicroSeries( - self.baseline.calculate("self_employment_income", map_to="household").astype(float).tolist(), - weights=household_weight - ) - - earnings = emp_income + self_emp_income - original_earnings = earnings - total_lsr_hh - - # Calculate decile relative - decile_rel = dict( - income=( - income_lsr_hh.groupby(decile).sum() - / original_earnings.groupby(decile).sum() - ).to_dict(), - ) - - - - decile_rel["income"] = {int(k): round(v *100,2) for k, v in decile_rel["income"].items() if k > 0} - - - return { - "income": decile_rel["income"] - } \ No newline at end of file diff --git a/policyengine/economic_impact/labour_supply_impact/earnings/by_decile/relative/substitution_effect/__init__.py b/policyengine/economic_impact/labour_supply_impact/earnings/by_decile/relative/substitution_effect/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/policyengine/economic_impact/labour_supply_impact/earnings/by_decile/relative/substitution_effect/substitutional_effect.py b/policyengine/economic_impact/labour_supply_impact/earnings/by_decile/relative/substitution_effect/substitutional_effect.py deleted file mode 100644 index da79eab..0000000 --- a/policyengine/economic_impact/labour_supply_impact/earnings/by_decile/relative/substitution_effect/substitutional_effect.py +++ /dev/null @@ -1,96 +0,0 @@ -from policyengine.economic_impact.base_metric_calculator import BaseMetricCalculator -from policyengine_uk import Microsimulation -from policyengine_core.reforms import Reform -from microdf import MicroSeries -import numpy as np - -class SubstitutionEffect(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - self.baseline = baseline - self.reformed = reformed - - def calculate(self): - # Calculate household weight - household_weight = self.baseline.calculate("household_weight") - - # Calculate household count people - household_count_people_baseline = self.baseline.calculate("household_count_people") - household_count_people_reformed = self.reformed.calculate("household_count_people") - - # Initialize income LSR - income_lsr_hh_baseline = (household_count_people_baseline * 0).astype(float).tolist() - income_lsr_hh_reformed = (household_count_people_reformed * 0).astype(float).tolist() - - - # Check if behavioral response exists and update income LSR - if "employment_income_behavioral_response" in self.baseline.tax_benefit_system.variables: - if any(self.baseline.calculate("employment_income_behavioral_response") != 0): - income_elasticity_baseline = self.baseline.calculate("income_elasticity_lsr", map_to="household") - income_lsr_hh_baseline = income_elasticity_baseline.astype(float).tolist() - - if "employment_income_behavioral_response" in self.reformed.tax_benefit_system.variables: - if any(self.reformed.calculate("employment_income_behavioral_response") != 0): - income_elasticity_reformed = self.reformed.calculate("income_elasticity_lsr", map_to="household") - income_lsr_hh_reformed = income_elasticity_reformed.astype(float).tolist() - - - # Calculate substitution LSR - substitution_lsr_hh_baseline = (household_count_people_baseline * 0).astype(float).tolist() - if "employment_income_behavioral_response" in self.baseline.tax_benefit_system.variables: - if any(self.baseline.calculate("employment_income_behavioral_response") != 0): - substitution_lsr_hh_baseline = ( - self.baseline.calculate("substitution_elasticity_lsr", map_to="household") - .astype(float) - .tolist() - ) - - substitution_lsr_hh_reformed = (household_count_people_reformed * 0).astype(float).tolist() - if "employment_income_behavioral_response" in self.reformed.tax_benefit_system.variables: - if any(self.reformed.calculate("employment_income_behavioral_response") != 0): - substitution_lsr_hh_reformed = ( - self.reformed.calculate("substitution_elasticity_lsr", map_to="household") - .astype(float) - .tolist() - ) - - - substitution_lsr_hh = np.array(substitution_lsr_hh_reformed) - np.array(substitution_lsr_hh_baseline) - income_lsr_hh = np.array(income_lsr_hh_reformed) - np.array(income_lsr_hh_baseline) - - # Convert to MicroSeries - income_lsr_hh = MicroSeries(income_lsr_hh, weights=household_weight) - substitution_lsr_hh = MicroSeries(substitution_lsr_hh, weights=household_weight) - - decile = np.array(self.baseline.calculate("household_income_decile")) - total_lsr_hh = substitution_lsr_hh + income_lsr_hh - - # Calculate earnings - emp_income = MicroSeries( - self.baseline.calculate("employment_income", map_to="household").astype(float).tolist(), - weights=household_weight - ) - - self_emp_income = MicroSeries( - self.baseline.calculate("self_employment_income", map_to="household").astype(float).tolist(), - weights=household_weight - ) - - earnings = emp_income + self_emp_income - original_earnings = earnings - total_lsr_hh - - # Calculate decile relative - decile_rel = dict( - substitution=( - substitution_lsr_hh.groupby(decile).sum() - / original_earnings.groupby(decile).sum() - ).to_dict() - ) - - - decile_rel["substitution"] = {int(k): round(v*100,2) for k, v in decile_rel["substitution"].items() if k > 0} - - return { - "substitution": decile_rel["substitution"] - } - diff --git a/policyengine/economic_impact/labour_supply_impact/earnings/by_decile/relative/total/__init__.py b/policyengine/economic_impact/labour_supply_impact/earnings/by_decile/relative/total/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/policyengine/economic_impact/labour_supply_impact/earnings/by_decile/relative/total/total.py b/policyengine/economic_impact/labour_supply_impact/earnings/by_decile/relative/total/total.py deleted file mode 100644 index a8cdaa8..0000000 --- a/policyengine/economic_impact/labour_supply_impact/earnings/by_decile/relative/total/total.py +++ /dev/null @@ -1,215 +0,0 @@ -from policyengine.economic_impact.base_metric_calculator import BaseMetricCalculator -from policyengine_uk import Microsimulation -from policyengine_core.reforms import Reform -from microdf import MicroSeries -import numpy as np -from typing import Union - - -class IncomeEffect(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - self.baseline = baseline - self.reformed = reformed - - def calculate(self): - # Calculate household weight - household_weight = self.baseline.calculate("household_weight") - - # Calculate household count people - household_count_people_baseline = self.baseline.calculate("household_count_people") - household_count_people_reformed = self.reformed.calculate("household_count_people") - - # Initialize income LSR - income_lsr_hh_baseline = (household_count_people_baseline * 0).astype(float).tolist() - income_lsr_hh_reformed = (household_count_people_reformed * 0).astype(float).tolist() - - - # Check if behavioral response exists and update income LSR - if "employment_income_behavioral_response" in self.baseline.tax_benefit_system.variables: - if any(self.baseline.calculate("employment_income_behavioral_response") != 0): - income_elasticity_baseline = self.baseline.calculate("income_elasticity_lsr", map_to="household") - income_lsr_hh_baseline = income_elasticity_baseline.astype(float).tolist() - - if "employment_income_behavioral_response" in self.reformed.tax_benefit_system.variables: - if any(self.reformed.calculate("employment_income_behavioral_response") != 0): - income_elasticity_reformed = self.reformed.calculate("income_elasticity_lsr", map_to="household") - income_lsr_hh_reformed = income_elasticity_reformed.astype(float).tolist() - - - # Calculate substitution LSR - substitution_lsr_hh_baseline = (household_count_people_baseline * 0).astype(float).tolist() - if "employment_income_behavioral_response" in self.baseline.tax_benefit_system.variables: - if any(self.baseline.calculate("employment_income_behavioral_response") != 0): - substitution_lsr_hh_baseline = ( - self.baseline.calculate("substitution_elasticity_lsr", map_to="household") - .astype(float) - .tolist() - ) - - substitution_lsr_hh_reformed = (household_count_people_reformed * 0).astype(float).tolist() - if "employment_income_behavioral_response" in self.reformed.tax_benefit_system.variables: - if any(self.reformed.calculate("employment_income_behavioral_response") != 0): - substitution_lsr_hh_reformed = ( - self.reformed.calculate("substitution_elasticity_lsr", map_to="household") - .astype(float) - .tolist() - ) - - - substitution_lsr_hh = np.array(substitution_lsr_hh_reformed) - np.array(substitution_lsr_hh_baseline) - income_lsr_hh = np.array(income_lsr_hh_reformed) - np.array(income_lsr_hh_baseline) - - # Convert to MicroSeries - income_lsr_hh = MicroSeries(income_lsr_hh, weights=household_weight) - substitution_lsr_hh = MicroSeries(substitution_lsr_hh, weights=household_weight) - - decile = np.array(self.baseline.calculate("household_income_decile")) - total_lsr_hh = substitution_lsr_hh + income_lsr_hh - - # Calculate earnings - emp_income = MicroSeries( - self.baseline.calculate("employment_income", map_to="household").astype(float).tolist(), - weights=household_weight - ) - - self_emp_income = MicroSeries( - self.baseline.calculate("self_employment_income", map_to="household").astype(float).tolist(), - weights=household_weight - ) - - earnings = emp_income + self_emp_income - original_earnings = earnings - total_lsr_hh - - # Calculate decile relative - decile_rel = dict( - income=( - income_lsr_hh.groupby(decile).sum() - / original_earnings.groupby(decile).sum() - ).to_dict(), - ) - - - - decile_rel["income"] = {int(k): v for k, v in decile_rel["income"].items() if k > 0} - - - return { - "income": decile_rel["income"] - } - - -class SubstitutionEffect(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - self.baseline = baseline - self.reformed = reformed - - def calculate(self): - # Calculate household weight - household_weight = self.baseline.calculate("household_weight") - - # Calculate household count people - household_count_people_baseline = self.baseline.calculate("household_count_people") - household_count_people_reformed = self.reformed.calculate("household_count_people") - - # Initialize income LSR - income_lsr_hh_baseline = (household_count_people_baseline * 0).astype(float).tolist() - income_lsr_hh_reformed = (household_count_people_reformed * 0).astype(float).tolist() - - - # Check if behavioral response exists and update income LSR - if "employment_income_behavioral_response" in self.baseline.tax_benefit_system.variables: - if any(self.baseline.calculate("employment_income_behavioral_response") != 0): - income_elasticity_baseline = self.baseline.calculate("income_elasticity_lsr", map_to="household") - income_lsr_hh_baseline = income_elasticity_baseline.astype(float).tolist() - - if "employment_income_behavioral_response" in self.reformed.tax_benefit_system.variables: - if any(self.reformed.calculate("employment_income_behavioral_response") != 0): - income_elasticity_reformed = self.reformed.calculate("income_elasticity_lsr", map_to="household") - income_lsr_hh_reformed = income_elasticity_reformed.astype(float).tolist() - - - # Calculate substitution LSR - substitution_lsr_hh_baseline = (household_count_people_baseline * 0).astype(float).tolist() - if "employment_income_behavioral_response" in self.baseline.tax_benefit_system.variables: - if any(self.baseline.calculate("employment_income_behavioral_response") != 0): - substitution_lsr_hh_baseline = ( - self.baseline.calculate("substitution_elasticity_lsr", map_to="household") - .astype(float) - .tolist() - ) - - substitution_lsr_hh_reformed = (household_count_people_reformed * 0).astype(float).tolist() - if "employment_income_behavioral_response" in self.reformed.tax_benefit_system.variables: - if any(self.reformed.calculate("employment_income_behavioral_response") != 0): - substitution_lsr_hh_reformed = ( - self.reformed.calculate("substitution_elasticity_lsr", map_to="household") - .astype(float) - .tolist() - ) - - - substitution_lsr_hh = np.array(substitution_lsr_hh_reformed) - np.array(substitution_lsr_hh_baseline) - income_lsr_hh = np.array(income_lsr_hh_reformed) - np.array(income_lsr_hh_baseline) - - # Convert to MicroSeries - income_lsr_hh = MicroSeries(income_lsr_hh, weights=household_weight) - substitution_lsr_hh = MicroSeries(substitution_lsr_hh, weights=household_weight) - - decile = np.array(self.baseline.calculate("household_income_decile")) - total_lsr_hh = substitution_lsr_hh + income_lsr_hh - - # Calculate earnings - emp_income = MicroSeries( - self.baseline.calculate("employment_income", map_to="household").astype(float).tolist(), - weights=household_weight - ) - - self_emp_income = MicroSeries( - self.baseline.calculate("self_employment_income", map_to="household").astype(float).tolist(), - weights=household_weight - ) - - earnings = emp_income + self_emp_income - original_earnings = earnings - total_lsr_hh - - # Calculate decile relative - decile_rel = dict( - substitution=( - substitution_lsr_hh.groupby(decile).sum() - / original_earnings.groupby(decile).sum() - ).to_dict() - ) - - - decile_rel["substitution"] = {int(k): v for k, v in decile_rel["substitution"].items() if k > 0} - - return { - "substitution": decile_rel["substitution"] - } - -class Total(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - self.baseline = baseline - self.reformed = reformed - - def calculate(self): - income_result = IncomeEffect(baseline=self.baseline, reformed=self.reformed).calculate() - substitution_result = SubstitutionEffect(baseline=self.baseline, reformed=self.reformed).calculate() - - income_dict = income_result["income"] - substitution_dict = substitution_result["substitution"] - - # Ensure all keys are present in both dictionaries - all_keys = set(income_dict.keys()).union(set(substitution_dict.keys())) - - net_change = {} - for key in all_keys: - income_value = income_dict.get(key, 0) - substitution_value = substitution_dict.get(key, 0) - net_change[key] = round(income_value + substitution_value, 4) - - return {"net_change": net_change} - diff --git a/policyengine/economic_impact/labour_supply_impact/earnings/overall/__init__.py b/policyengine/economic_impact/labour_supply_impact/earnings/overall/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/policyengine/economic_impact/labour_supply_impact/earnings/overall/absolute/__init__.py b/policyengine/economic_impact/labour_supply_impact/earnings/overall/absolute/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/policyengine/economic_impact/labour_supply_impact/earnings/overall/absolute/absolute.py b/policyengine/economic_impact/labour_supply_impact/earnings/overall/absolute/absolute.py deleted file mode 100644 index 1222304..0000000 --- a/policyengine/economic_impact/labour_supply_impact/earnings/overall/absolute/absolute.py +++ /dev/null @@ -1,77 +0,0 @@ -from policyengine.economic_impact.base_metric_calculator import BaseMetricCalculator -from policyengine_uk import Microsimulation -from policyengine_core.reforms import Reform -from microdf import MicroSeries -import numpy as np - - -class IncomeLSR(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - self.baseline = baseline - self.reformed = reformed - - def calculate(self): - income_lsr_baseline = 0 - income_lsr_reformed = 0 - - if "employment_income_behavioral_response" in self.baseline.tax_benefit_system.variables: - if any(self.baseline.calculate("employment_income_behavioral_response") != 0): - income_lsr_baseline = self.baseline.calculate("income_elasticity_lsr").sum() - - if "employment_income_behavioral_response" in self.reformed.tax_benefit_system.variables: - if any(self.reformed.calculate("employment_income_behavioral_response") != 0): - income_lsr_reformed = self.reformed.calculate("income_elasticity_lsr").sum() - - income_lsr = income_lsr_reformed - income_lsr_baseline - # Convert to billions and round to 2 decimal places - income_lsr_billion = round(income_lsr / 1e9, 2) - - return { - "income_lsr": income_lsr_billion - } - -class SubstitutionLSR(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - self.baseline = baseline - self.reformed = reformed - - def calculate(self): - substitution_lsr_baseline = 0 - substitution_lsr_reformed = 0 - - if "employment_income_behavioral_response" in self.baseline.tax_benefit_system.variables: - if any(self.baseline.calculate("employment_income_behavioral_response") != 0): - substitution_lsr_baseline = self.baseline.calculate("substitution_elasticity_lsr").sum() - - if "employment_income_behavioral_response" in self.reformed.tax_benefit_system.variables: - if any(self.reformed.calculate("employment_income_behavioral_response") != 0): - substitution_lsr_reformed = self.reformed.calculate("substitution_elasticity_lsr").sum() - - substitution_lsr = substitution_lsr_reformed - substitution_lsr_baseline - # Convert to billions and round to 2 decimal places - substitution_lsr_billion = round(substitution_lsr / 1e9, 2) - - return { - "substitution_lsr": substitution_lsr_billion - } - -class NetLSRChange(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - self.baseline = baseline - self.reformed = reformed - - def calculate(self): - income_result = IncomeLSR(baseline=self.baseline, reformed=self.reformed).calculate() - substitution_result = SubstitutionLSR(baseline=self.baseline, reformed=self.reformed).calculate() - - income_value = income_result["income_lsr"] - substitution_value = substitution_result["substitution_lsr"] - - net_change = (income_value + substitution_value) - # Round to 2 decimal places - net_change_billion = round(net_change, 2) - - return {"net_change": net_change_billion} diff --git a/policyengine/economic_impact/labour_supply_impact/earnings/overall/relative/__init__.py b/policyengine/economic_impact/labour_supply_impact/earnings/overall/relative/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/policyengine/economic_impact/labour_supply_impact/earnings/overall/relative/relative.py b/policyengine/economic_impact/labour_supply_impact/earnings/overall/relative/relative.py deleted file mode 100644 index ee83a1c..0000000 --- a/policyengine/economic_impact/labour_supply_impact/earnings/overall/relative/relative.py +++ /dev/null @@ -1,143 +0,0 @@ -from policyengine.economic_impact.base_metric_calculator import BaseMetricCalculator -from policyengine_uk import Microsimulation -from policyengine_core.reforms import Reform -from microdf import MicroSeries -import numpy as np - -class IncomeLSR(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - self.baseline = baseline - self.reformed = reformed - - def calculate(self): - # Calculate household counts - # household_count_people_baseline = self.baseline.calculate("household_count_people") - # household_count_people_reformed = self.reformed.calculate("household_count_people") - - # Calculate baseline and reformed substitution LSR - substitution_lsr_hh_baseline = self._calculate_substitution_lsr(self.baseline) - substitution_lsr_hh_reformed = self._calculate_substitution_lsr(self.reformed) - - # Compute the change in substitution LSR - substitution_lsr_hh = np.array(substitution_lsr_hh_reformed) - np.array(substitution_lsr_hh_baseline) - - # Calculate baseline and reformed income LSR - income_lsr_hh_baseline = self._calculate_income_lsr(self.baseline) - income_lsr_hh_reformed = self._calculate_income_lsr(self.reformed) - - # Compute the change in income LSR - income_lsr_hh = np.array(income_lsr_hh_reformed) - np.array(income_lsr_hh_baseline) - - # Calculate weights and earnings - household_weight = self.baseline.calculate("household_weight") - emp_income = MicroSeries( - self.baseline.calculate("employment_income", map_to="household").astype(float).tolist(), - weights=household_weight - ) - self_emp_income = MicroSeries( - self.baseline.calculate("self_employment_income", map_to="household").astype(float).tolist(), - weights=household_weight - ) - total_lsr_hh = substitution_lsr_hh + income_lsr_hh - earnings = emp_income + self_emp_income - original_earnings = earnings - total_lsr_hh - - # Calculate income LSR ratio - income_lsr_hh = MicroSeries(income_lsr_hh, weights=household_weight) - income = income_lsr_hh.sum() / original_earnings.sum() * 100 - - return {"income": round(income,2)} - - def _calculate_substitution_lsr(self, simulation: Microsimulation): - """Calculate substitution LSR for a given simulation.""" - if "employment_income_behavioral_response" in simulation.tax_benefit_system.variables: - if any(simulation.calculate("employment_income_behavioral_response") != 0): - return simulation.calculate("substitution_elasticity_lsr", map_to="household").astype(float).tolist() - return (self.baseline.calculate("household_count_people") * 0).astype(float).tolist() - - def _calculate_income_lsr(self, simulation: Microsimulation): - """Calculate income LSR for a given simulation.""" - if "employment_income_behavioral_response" in simulation.tax_benefit_system.variables: - if any(simulation.calculate("employment_income_behavioral_response") != 0): - return simulation.calculate("income_elasticity_lsr", map_to="household").astype(float).tolist() - return (self.baseline.calculate("household_count_people") * 0).astype(float).tolist() - - -class SubstitutionLSR(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - self.baseline = baseline - self.reformed = reformed - - def calculate(self): - # Calculate household counts - # household_count_people_baseline = self.baseline.calculate("household_count_people") - # household_count_people_reformed = self.reformed.calculate("household_count_people") - - # Calculate baseline and reformed income LSR - income_lsr_hh_baseline = self._calculate_income_lsr(self.baseline) - income_lsr_hh_reformed = self._calculate_income_lsr(self.reformed) - - # Calculate baseline and reformed substitution LSR - substitution_lsr_hh_baseline = self._calculate_substitution_lsr(self.baseline) - substitution_lsr_hh_reformed = self._calculate_substitution_lsr(self.reformed) - - # Compute the change in substitution LSR - substitution_lsr_hh = np.array(substitution_lsr_hh_reformed) - np.array(substitution_lsr_hh_baseline) - household_weight = self.baseline.calculate("household_weight") - - # Convert to MicroSeries and compute total LSR - substitution_lsr_hh = MicroSeries(substitution_lsr_hh, weights=household_weight) - income_lsr_hh = np.array(income_lsr_hh_reformed) - np.array(income_lsr_hh_baseline) - total_lsr_hh = substitution_lsr_hh + income_lsr_hh - - # Calculate earnings - emp_income = MicroSeries( - self.baseline.calculate("employment_income", map_to="household").astype(float).tolist(), - weights=household_weight - ) - self_emp_income = MicroSeries( - self.baseline.calculate("self_employment_income", map_to="household").astype(float).tolist(), - weights=household_weight - ) - earnings = emp_income + self_emp_income - original_earnings = earnings - total_lsr_hh - - # Calculate substitution ratio - substitution = substitution_lsr_hh.sum() / original_earnings.sum() * 100 - - return {"substitution": round(substitution,2)} - - def _calculate_substitution_lsr(self, simulation: Microsimulation): - """Calculate substitution LSR for a given simulation.""" - if "employment_income_behavioral_response" in simulation.tax_benefit_system.variables: - if any(simulation.calculate("employment_income_behavioral_response") != 0): - return simulation.calculate("substitution_elasticity_lsr", map_to="household").astype(float).tolist() - return (self.baseline.calculate("household_count_people") * 0).astype(float).tolist() - - def _calculate_income_lsr(self, simulation: Microsimulation): - """Calculate income LSR for a given simulation.""" - if "employment_income_behavioral_response" in simulation.tax_benefit_system.variables: - if any(simulation.calculate("employment_income_behavioral_response") != 0): - return simulation.calculate("income_elasticity_lsr", map_to="household").astype(float).tolist() - return (self.baseline.calculate("household_count_people") * 0).astype(float).tolist() - - - -class NetLSRChange(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - self.baseline = baseline - self.reformed = reformed - - def calculate(self): - income_result = IncomeLSR(baseline=self.baseline, reformed=self.reformed).calculate() - substitution_result = SubstitutionLSR(baseline=self.baseline, reformed=self.reformed).calculate() - - income_value = income_result["income"] - substitution_value = substitution_result["substitution"] - - net_change = (income_value + substitution_value) - - return {"net_change": round(net_change,2)} \ No newline at end of file diff --git a/policyengine/economic_impact/poverty_impact/__init__.py b/policyengine/economic_impact/poverty_impact/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/policyengine/economic_impact/poverty_impact/deep_poverty/__init__.py b/policyengine/economic_impact/poverty_impact/deep_poverty/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/policyengine/economic_impact/poverty_impact/deep_poverty/by_age/__init__.py b/policyengine/economic_impact/poverty_impact/deep_poverty/by_age/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/policyengine/economic_impact/poverty_impact/deep_poverty/by_age/by_age.py b/policyengine/economic_impact/poverty_impact/deep_poverty/by_age/by_age.py deleted file mode 100644 index 79bcb4f..0000000 --- a/policyengine/economic_impact/poverty_impact/deep_poverty/by_age/by_age.py +++ /dev/null @@ -1,89 +0,0 @@ -from policyengine.economic_impact.base_metric_calculator import BaseMetricCalculator -from policyengine_uk import Microsimulation - -class ChildPoverty(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - self.baseline = baseline - self.reformed = reformed - - def calculate(self): - age = self.baseline.calculate("age") - - baseline_poverty = self.baseline.calculate("in_deep_poverty", map_to="person") - reform_poverty = self.reformed.calculate("in_deep_poverty", map_to="person") - - baseline = float(baseline_poverty[age < 18].mean()) - reform = float(reform_poverty[age < 18].mean()) - change = ((reform - baseline) / baseline) * 100 - - return { - "baseline": round(baseline*100,2), - "reform": round(reform*100,2), - "change": round(change,1) - } - -class AdultPoverty(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - self.baseline = baseline - self.reformed = reformed - - def calculate(self): - age = self.baseline.calculate("age") - - baseline_poverty = self.baseline.calculate("in_deep_poverty", map_to="person") - reform_poverty = self.reformed.calculate("in_deep_poverty", map_to="person") - - baseline = float(baseline_poverty[(age >= 18) & (age < 65)].mean()) - reform = float(reform_poverty[(age >= 18) & (age < 65)].mean()) - change = ((reform - baseline) / baseline) * 100 - - return { - "baseline": round(baseline*100,2), - "reform": round(reform*100,2), - "change": round(change,1) - } - -class SeniorPoverty(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - self.baseline = baseline - self.reformed = reformed - - def calculate(self): - age = self.baseline.calculate("age") - - baseline_poverty = self.baseline.calculate("in_deep_poverty", map_to="person") - reform_poverty = self.reformed.calculate("in_deep_poverty", map_to="person") - - baseline = float(baseline_poverty[age >= 65].mean()) - reform = float(reform_poverty[age >= 65].mean()) - change = ((reform - baseline) / baseline) * 100 - - return { - "baseline": round(baseline*100,2), - "reform": round(reform*100,2), - "change": round(change,1) - } - -class AllPoverty(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - self.baseline = baseline - self.reformed = reformed - - def calculate(self): - - baseline_poverty = self.baseline.calculate("in_deep_poverty", map_to="person") - reform_poverty = self.reformed.calculate("in_deep_poverty", map_to="person") - - baseline = float(baseline_poverty.mean()) - reform = float(reform_poverty.mean()) - change = ((reform - baseline) / baseline) * 100 - - return { - "baseline": round(baseline*100,2), - "reform": round(reform*100,2), - "change": round(change,1) - } \ No newline at end of file diff --git a/policyengine/economic_impact/poverty_impact/deep_poverty/by_gender/__init__.py b/policyengine/economic_impact/poverty_impact/deep_poverty/by_gender/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/policyengine/economic_impact/poverty_impact/deep_poverty/by_gender/by_gender.py b/policyengine/economic_impact/poverty_impact/deep_poverty/by_gender/by_gender.py deleted file mode 100644 index 05ce1f1..0000000 --- a/policyengine/economic_impact/poverty_impact/deep_poverty/by_gender/by_gender.py +++ /dev/null @@ -1,67 +0,0 @@ -from policyengine.economic_impact.inequality_impact.inequality_impact import BaseMetricCalculator -from policyengine_uk import Microsimulation - -class MalePoverty(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - self.baseline = baseline - self.reformed = reformed - - def calculate(self): - is_male = self.baseline.calculate("is_male") - - baseline_poverty = self.baseline.calculate("in_deep_poverty", map_to="person") - reform_poverty = self.reformed.calculate("in_deep_poverty", map_to="person") - - baseline = float(baseline_poverty[is_male].mean()) - reform = float(reform_poverty[is_male].mean()) - change = ((reform - baseline) / baseline) * 100 - - return { - "baseline": round(baseline*100,2), - "reform": round(reform*100,2), - "change": round(change,1) - } - -class FemalePoverty(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - self.baseline = baseline - self.reformed = reformed - - def calculate(self): - is_male = self.baseline.calculate("is_male") - - baseline_poverty = self.baseline.calculate("in_deep_poverty", map_to="person") - reform_poverty = self.reformed.calculate("in_deep_poverty", map_to="person") - - baseline = float(baseline_poverty[~is_male].mean()) - reform = float(reform_poverty[~is_male].mean()) - change = ((reform - baseline) / baseline) * 100 - - return { - "baseline": round(baseline*100,2), - "reform": round(reform*100,2), - "change": round(change,1) - } - -class AllPoverty(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - self.baseline = baseline - self.reformed = reformed - - def calculate(self): - - baseline_poverty = self.baseline.calculate("in_deep_poverty", map_to="person") - reform_poverty = self.reformed.calculate("in_deep_poverty", map_to="person") - - baseline = float(baseline_poverty.mean()) - reform = float(reform_poverty.mean()) - change = ((reform - baseline) / baseline) * 100 - - return { - "baseline": round(baseline*100,2), - "reform": round(reform*100,2), - "change": round(change,1) - } diff --git a/policyengine/economic_impact/poverty_impact/regular_poverty/__init__.py b/policyengine/economic_impact/poverty_impact/regular_poverty/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/policyengine/economic_impact/poverty_impact/regular_poverty/by_age/__init__.py b/policyengine/economic_impact/poverty_impact/regular_poverty/by_age/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/policyengine/economic_impact/poverty_impact/regular_poverty/by_age/by_age.py b/policyengine/economic_impact/poverty_impact/regular_poverty/by_age/by_age.py deleted file mode 100644 index 144041a..0000000 --- a/policyengine/economic_impact/poverty_impact/regular_poverty/by_age/by_age.py +++ /dev/null @@ -1,89 +0,0 @@ -from policyengine.economic_impact.base_metric_calculator import BaseMetricCalculator -from policyengine_uk import Microsimulation - -class ChildPoverty(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - self.baseline = baseline - self.reformed = reformed - - def calculate(self): - age = self.baseline.calculate("age") - - baseline_poverty = self.baseline.calculate("in_poverty", map_to="person") - reform_poverty = self.reformed.calculate("in_poverty", map_to="person") - - baseline = float(baseline_poverty[age < 18].mean()) - reform = float(reform_poverty[age < 18].mean()) - change = ((reform - baseline) / baseline) * 100 - - return { - "baseline": round(baseline,2), - "reform": round(reform,2), - "change": round(change,1) - } - -class AdultPoverty(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - self.baseline = baseline - self.reformed = reformed - - def calculate(self): - age = self.baseline.calculate("age") - - baseline_poverty = self.baseline.calculate("in_poverty", map_to="person") - reform_poverty = self.reformed.calculate("in_poverty", map_to="person") - - baseline = float(baseline_poverty[(age >= 18) & (age < 65)].mean()) - reform = float(reform_poverty[(age >= 18) & (age < 65)].mean()) - change = ((reform - baseline) / baseline) * 100 - - return { - "baseline": round(baseline,2), - "reform": round(reform,2), - "change": round(change,1) - } - -class SeniorPoverty(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - self.baseline = baseline - self.reformed = reformed - - def calculate(self): - age = self.baseline.calculate("age") - - baseline_poverty = self.baseline.calculate("in_poverty", map_to="person") - reform_poverty = self.reformed.calculate("in_poverty", map_to="person") - - baseline = float(baseline_poverty[age >= 65].mean()) - reform = float(reform_poverty[age >= 65].mean()) - change = ((reform - baseline) / baseline) * 100 - - return { - "baseline": round(baseline,2), - "reform": round(reform,2), - "change": round(change,1) - } - -class AllPoverty(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - self.baseline = baseline - self.reformed = reformed - - def calculate(self): - - baseline_poverty = self.baseline.calculate("in_poverty", map_to="person") - reform_poverty = self.reformed.calculate("in_poverty", map_to="person") - - baseline = float(baseline_poverty.mean()) - reform = float(reform_poverty.mean()) - change = ((reform - baseline) / baseline) * 100 - - return { - "baseline": round(baseline,2), - "reform": round(reform,2), - "change": round(change,1) - } \ No newline at end of file diff --git a/policyengine/economic_impact/poverty_impact/regular_poverty/by_gender/__init__.py b/policyengine/economic_impact/poverty_impact/regular_poverty/by_gender/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/policyengine/economic_impact/poverty_impact/regular_poverty/by_gender/by_gender.py b/policyengine/economic_impact/poverty_impact/regular_poverty/by_gender/by_gender.py deleted file mode 100644 index 5b1251b..0000000 --- a/policyengine/economic_impact/poverty_impact/regular_poverty/by_gender/by_gender.py +++ /dev/null @@ -1,67 +0,0 @@ -from policyengine.economic_impact.inequality_impact.inequality_impact import BaseMetricCalculator -from policyengine_uk import Microsimulation - -class MalePoverty(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - self.baseline = baseline - self.reformed = reformed - - def calculate(self): - is_male = self.baseline.calculate("is_male") - - baseline_poverty = self.baseline.calculate("in_poverty", map_to="person") - reform_poverty = self.reformed.calculate("in_poverty", map_to="person") - - baseline = float(baseline_poverty[is_male].mean()) - reform = float(reform_poverty[is_male].mean()) - change = ((reform - baseline) / baseline) * 100 - - return { - "baseline": round(baseline,2), - "reform": round(reform,2), - "change": round(change,1) - } - -class FemalePoverty(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - self.baseline = baseline - self.reformed = reformed - - def calculate(self): - is_male = self.baseline.calculate("is_male") - - baseline_poverty = self.baseline.calculate("in_poverty", map_to="person") - reform_poverty = self.reformed.calculate("in_poverty", map_to="person") - - baseline = float(baseline_poverty[~is_male].mean()) - reform = float(reform_poverty[~is_male].mean()) - change = ((reform - baseline) / baseline) * 100 - - return { - "baseline": round(baseline,2), - "reform": round(reform,2), - "change": round(change,1) - } - -class AllPoverty(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - self.baseline = baseline - self.reformed = reformed - - def calculate(self): - - baseline_poverty = self.baseline.calculate("in_poverty", map_to="person") - reform_poverty = self.reformed.calculate("in_poverty", map_to="person") - - baseline = float(baseline_poverty.mean()) - reform = float(reform_poverty.mean()) - change = ((reform - baseline) / baseline) * 100 - - return { - "baseline": round(baseline,2), - "reform": round(reform,2), - "change": round(change,1) - } diff --git a/policyengine/economic_impact/winners_and_losers/__init__.py b/policyengine/economic_impact/winners_and_losers/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/policyengine/economic_impact/winners_and_losers/by_income_decile/__init__.py b/policyengine/economic_impact/winners_and_losers/by_income_decile/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/policyengine/economic_impact/winners_and_losers/by_income_decile/by_income_decile.py b/policyengine/economic_impact/winners_and_losers/by_income_decile/by_income_decile.py deleted file mode 100644 index fd8928b..0000000 --- a/policyengine/economic_impact/winners_and_losers/by_income_decile/by_income_decile.py +++ /dev/null @@ -1,55 +0,0 @@ -from policyengine.economic_impact.base_metric_calculator import BaseMetricCalculator -from policyengine_uk import Microsimulation -from microdf import MicroDataFrame, MicroSeries -import numpy as np - -class ByIncomeDecile(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - self.baseline = baseline - self.reformed = reformed - - def calculate(self): - baseline_income = MicroSeries( - self.baseline.calculate("household_net_income"), weights=self.baseline.calculate("household_weight") - ) - reform_income = MicroSeries( - self.reformed.calculate("household_net_income"), weights=baseline_income.weights - ) - people = MicroSeries( - self.baseline.calculate("household_count_people"), weights=baseline_income.weights - ) - decile = MicroSeries(self.baseline.calculate("household_income_decile")).values - absolute_change = (reform_income - baseline_income).values - capped_baseline_income = np.maximum(baseline_income.values, 1) - capped_reform_income = ( - np.maximum(reform_income.values, 1) + absolute_change - ) - income_change = ( - capped_reform_income - capped_baseline_income - ) / capped_baseline_income - - outcome_groups = {} - all_outcomes = {} - BOUNDS = [-np.inf, -0.05, -1e-3, 1e-3, 0.05, np.inf] - LABELS = [ - "Lose more than 5%", - "Lose less than 5%", - "No change", - "Gain less than 5%", - "Gain more than 5%", - ] - for lower, upper, label in zip(BOUNDS[:-1], BOUNDS[1:], LABELS): - outcome_groups[label] = [] - for i in range(1, 11): - in_decile = decile == i - in_group = (income_change > lower) & (income_change <= upper) - in_both = in_decile & in_group - outcome_groups[label].append( - round(float(people[in_both].sum() / people[in_decile].sum()) * 100, 1) - ) - all_outcomes[label] = round(sum(outcome_groups[label]) / 10, 1) - - return { - "result": dict(deciles=outcome_groups, all=all_outcomes) - } \ No newline at end of file diff --git a/policyengine/economic_impact/winners_and_losers/by_wealth_decile/__init__.py b/policyengine/economic_impact/winners_and_losers/by_wealth_decile/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/policyengine/economic_impact/winners_and_losers/by_wealth_decile/by_wealth_decile.py b/policyengine/economic_impact/winners_and_losers/by_wealth_decile/by_wealth_decile.py deleted file mode 100644 index 9ee890b..0000000 --- a/policyengine/economic_impact/winners_and_losers/by_wealth_decile/by_wealth_decile.py +++ /dev/null @@ -1,55 +0,0 @@ -from policyengine.economic_impact.base_metric_calculator import BaseMetricCalculator -from policyengine_uk import Microsimulation -from microdf import MicroDataFrame, MicroSeries -import numpy as np - -class ByWealthDecile(BaseMetricCalculator): - def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: - super().__init__(baseline, reformed, default_period) - self.baseline = baseline - self.reformed = reformed - - def calculate(self): - baseline_income = MicroSeries( - self.baseline.calculate("household_net_income"), weights=self.baseline.calculate("household_weight") - ) - reform_income = MicroSeries( - self.reformed.calculate("household_net_income"), weights=baseline_income.weights - ) - people = MicroSeries( - self.baseline.calculate("household_count_people"), weights=baseline_income.weights - ) - decile = MicroSeries(self.baseline.calculate("household_wealth_decile")).values - absolute_change = (reform_income - baseline_income).values - capped_baseline_income = np.maximum(baseline_income.values, 1) - capped_reform_income = ( - np.maximum(reform_income.values, 1) + absolute_change - ) - income_change = ( - capped_reform_income - capped_baseline_income - ) / capped_baseline_income - - outcome_groups = {} - all_outcomes = {} - BOUNDS = [-np.inf, -0.05, -1e-3, 1e-3, 0.05, np.inf] - LABELS = [ - "Lose more than 5%", - "Lose less than 5%", - "No change", - "Gain less than 5%", - "Gain more than 5%", - ] - for lower, upper, label in zip(BOUNDS[:-1], BOUNDS[1:], LABELS): - outcome_groups[label] = [] - for i in range(1, 11): - in_decile = decile == i - in_group = (income_change > lower) & (income_change <= upper) - in_both = in_decile & in_group - outcome_groups[label].append( - round(float(people[in_both].sum() / people[in_decile].sum()) * 100, 1) - ) - all_outcomes[label] = round(sum(outcome_groups[label]) / 10, 1) - - return { - "result": dict(deciles=outcome_groups, all=all_outcomes) - } \ No newline at end of file diff --git a/policyengine/outputs/macro/impact/revenue_impact.py b/policyengine/outputs/macro/impact/revenue_impact.py new file mode 100644 index 0000000..82dc471 --- /dev/null +++ b/policyengine/outputs/macro/impact/revenue_impact.py @@ -0,0 +1,12 @@ +from policyengine import Simulation + +def revenue_impact(simulation: Simulation): + """Calculate the revenue impact of the given simulation. + + Args: + simulation (Simulation): The simulation for which the revenue impact is to be calculated. + + Returns: + float: The revenue impact of the simulation. + """ + return simulation.reformed.calculate("household_tax").sum()/1e9 - simulation.baseline.calculate("household_tax").sum()/1e9 \ No newline at end of file diff --git a/policyengine/simulation.py b/policyengine/simulation.py new file mode 100644 index 0000000..ed63485 --- /dev/null +++ b/policyengine/simulation.py @@ -0,0 +1,106 @@ +from policyengine_core import Simulation as CountrySimulation +from policyengine_core.reforms import Reform + +class Simulation: + """The top-level class through which all PE usage is carried out.""" + + country: str + """The country for which the simulation is being run.""" + type: str + """The type of simulation being run (macro or household).""" + data: str + """The dataset being used for the simulation.""" + time_period: str + """The time period for the simulation. Years are applicable.""" + baseline: dict + """The baseline simulation inputs.""" + reform: dict + """The reform simulation inputs.""" + + baseline: CountrySimulation + reformed: CountrySimulation + + def __init__(self, country: str, type: str, data: str, time_period: str, reform: dict): + """Initialise the simulation with the given parameters. + + Args: + country (str): The country for which the simulation is being run. + type (str): The type of simulation being run (macro or household). + data (str): The dataset being used for the simulation. + time_period (str): The time period for the simulation. Years are applicable. + reform (dict): The reform simulation inputs. + """ + self.country = country + self.type = type + self.data = data + self.time_period = time_period + + if isinstance(reform, dict): + reform = Reform.from_dict(reform, country_id=country) + elif isinstance(reform, int): + reform = Reform.from_api(reform, country_id=country) + + self.reform = reform + + if country == "uk": + from policyengine_uk import Microsimulation as UKMicrosimulation + self.baseline = UKMicrosimulation() + self.baseline.default_calculation_period = time_period + self.reformed = UKMicrosimulation(reform=reform) + self.reformed.default_calculation_period = time_period + + self.output_functions, self.outputs = self.get_outputs() + + def calculate(self, output: str): + if output.endswith("/"): + output = output[:-1] + + node = self.outputs + for key in output.split("/")[:-1]: + node = node[key] + + parent = node + child_key = output.split("/")[-1] + node = parent[child_key] + + # Check if any descendants are None + + if parent[child_key] is None: + output_function = self.output_functions[output] + parent[child_key] = output_function(self) + + if isinstance(node, dict): + for child_key in node.keys(): + self.calculate(output + "/" + child_key) + + return node + + def get_outputs(self) -> tuple: + from pathlib import Path + import importlib.util + + output_functions = {} + for output in Path(__file__).parent.glob("outputs/**/*.py"): + module_name = output.stem + spec = importlib.util.spec_from_file_location(module_name, output) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + relative_path = str(output.relative_to(Path(__file__).parent / "outputs")).replace(".py", "") + + # Only import the function with the same name as the module, enforcing one function per file + output_functions[str(relative_path)] = getattr(module, module_name) + + # Construct the output tree, fill with Nones for now + + outputs = {} + + for output_path in output_functions.keys(): + parts = output_path.split("/") + current = outputs + for part in parts[:-1]: + if part not in current: + current[part] = {} + current = current[part] + current[parts[-1]] = None + + return output_functions, outputs diff --git a/policyengine/tests/economic_impact/budgetary_impact/by_program/by_program.yaml b/policyengine/tests/economic_impact/budgetary_impact/by_program/by_program.yaml deleted file mode 100644 index f5d2d29..0000000 --- a/policyengine/tests/economic_impact/budgetary_impact/by_program/by_program.yaml +++ /dev/null @@ -1,100 +0,0 @@ -# by_program -- test_income_tax: - reform: - gov.hmrc.income_tax.rates.uk[0].rate: - "2024-01-01.2100-12-31": 0.55 - country: uk - expected: - baseline: 291090070166.62 - reform: 496242053771.2 - change: 70.5 - -- test_national_insurance: - reform: - gov.hmrc.income_tax.rates.uk[0].rate: - "2024-01-01.2100-12-31": 0.55 - country: uk - expected: - baseline: 50826792606.89 - reform: 50826792606.89 - change: 0.0 - -- test_vat: - reform: - gov.hmrc.income_tax.rates.uk[0].rate: - "2024-01-01.2100-12-31": 0.55 - country: uk - expected: - baseline: 175581776889.21 - reform: 175581776889.21 - change: 0.0 - -- test_council_tax: - reform: - gov.hmrc.income_tax.rates.uk[0].rate: - "2024-01-01.2100-12-31": 0.55 - country: uk - expected: - baseline: 47861314826.79 - reform: 47861314826.79 - change: 0.0 - -- test_fuel_duty: - reform: - gov.hmrc.income_tax.rates.uk[0].rate: - "2024-01-01.2100-12-31": 0.55 - country: uk - expected: - baseline: 28019829809.09 - reform: 28019829809.09 - change: 0.0 - -- test_tax_credits: - reform: - gov.hmrc.income_tax.rates.uk[0].rate: - "2024-01-01.2100-12-31": 0.55 - country: uk - expected: - baseline: -208150256.01 - reform: -308166663.98 - change: 48.1 - -- test_universal_credits: - reform: - gov.hmrc.income_tax.rates.uk[0].rate: - "2024-01-01.2100-12-31": 0.55 - country: uk - expected: - baseline: -72209672284.1 - reform: -73780445681.08 - change: 2.2 - -- test_child_benefits: - reform: - gov.hmrc.income_tax.rates.uk[0].rate: - "2024-01-01.2100-12-31": 0.55 - country: uk - expected: - baseline: -15975002346.41 - reform: -15975002346.41 - change: -0.0 - -- test_state_pension: - reform: - gov.hmrc.income_tax.rates.uk[0].rate: - "2024-01-01.2100-12-31": 0.55 - country: uk - expected: - baseline: -127240166697.26 - reform: -127240166697.26 - change: -0.0 - -- test_pension_credit: - reform: - gov.hmrc.income_tax.rates.uk[0].rate: - "2024-01-01.2100-12-31": 0.55 - country: uk - expected: - baseline: -2000983943.05 - reform: -2181135212.4 - change: 9.0 \ No newline at end of file diff --git a/policyengine/tests/economic_impact/budgetary_impact/by_program/test_by_program.py b/policyengine/tests/economic_impact/budgetary_impact/by_program/test_by_program.py deleted file mode 100644 index 6bb48a0..0000000 --- a/policyengine/tests/economic_impact/budgetary_impact/by_program/test_by_program.py +++ /dev/null @@ -1,53 +0,0 @@ -import pytest -import yaml -import os -from policyengine import EconomicImpact - -def assert_dict_approx_equal(actual, expected, tolerance=1e3): - for key in expected: - assert abs(actual[key] - expected[key]) < tolerance, f"Key {key}: expected {expected[key]}, got {actual[key]}" - - -yaml_file_path = "policyengine/tests/economic_impact/budgetary_impact/by_program/by_program.yaml" - -# Check if the file exists -if not os.path.exists(yaml_file_path): - raise FileNotFoundError(f"The YAML file does not exist at: {yaml_file_path}") - -with open(yaml_file_path, 'r') as file: - test_cases = yaml.safe_load(file) - -@pytest.mark.parametrize("test_case", test_cases) -def test_economic_impact(test_case): - test_name = list(test_case.keys())[0] - test_data = test_case[test_name] - - economic_impact = EconomicImpact(test_data['reform'], test_data['country']) - - if 'income_tax' in test_name: - result = economic_impact.calculate("budgetary/by_program/income_tax") - elif 'national_insurance' in test_name: - result = economic_impact.calculate("budgetary/by_program/national_insurance") - elif 'vat' in test_name: - result = economic_impact.calculate("budgetary/by_program/vat") - elif 'council_tax' in test_name: - result = economic_impact.calculate("budgetary/by_program/council_tax") - elif 'fuel_duty' in test_name: - result = economic_impact.calculate("budgetary/by_program/fuel_duty") - elif 'tax_credits' in test_name: - result = economic_impact.calculate("budgetary/by_program/tax_credits") - elif 'universal_credits' in test_name: - result = economic_impact.calculate("budgetary/by_program/universal_credits") - elif 'child_benefits' in test_name: - result = economic_impact.calculate("budgetary/by_program/child_benefits") - elif 'state_pension' in test_name: - result = economic_impact.calculate("budgetary/by_program/state_pension") - elif 'pension_credit' in test_name: - result = economic_impact.calculate("budgetary/by_program/pension_credit") - else: - pytest.fail(f"Unknown test case: {test_name}") - - assert_dict_approx_equal(result, test_data['expected']) - -if __name__ == "__main__": - pytest.main([__file__]) diff --git a/policyengine/tests/economic_impact/budgetary_impact/overall/overall.yaml b/policyengine/tests/economic_impact/budgetary_impact/overall/overall.yaml deleted file mode 100644 index 7e9dd91..0000000 --- a/policyengine/tests/economic_impact/budgetary_impact/overall/overall.yaml +++ /dev/null @@ -1,28 +0,0 @@ -# overall -- test_budgetary_impact: - reform: - gov.hmrc.income_tax.rates.uk[0].rate: - "2024-01-01.2100-12-31": 0.55 - country: uk - expected: - budgetary_impact: 203274712297.14 - -- test_benefit_spending_impact: - reform: - gov.hmrc.income_tax.rates.uk[0].rate: - "2024-01-01.2100-12-31": 0.55 - country: uk - expected: - baseline_total_benefits: 247160184562.67 - reformed_total_benefits: 249032006583.15 - benefit_spending_impact: 1871822020.49 - -- test_tax_revenue_impact: - reform: - gov.hmrc.income_tax.rates.uk[0].rate: - "2024-01-01.2100-12-31": 0.55 - country: uk - expected: - baseline_total_tax: 447861864968.89 - reformed_total_tax: 653008399286.52 - tax_revenue_impact: 205146534317.63 \ No newline at end of file diff --git a/policyengine/tests/economic_impact/budgetary_impact/overall/test_overall.py b/policyengine/tests/economic_impact/budgetary_impact/overall/test_overall.py deleted file mode 100644 index 6c13d4c..0000000 --- a/policyengine/tests/economic_impact/budgetary_impact/overall/test_overall.py +++ /dev/null @@ -1,39 +0,0 @@ -import pytest -import yaml -import os -from policyengine import EconomicImpact - -def assert_dict_approx_equal(actual, expected, tolerance=1e3): - for key in expected: - assert abs(actual[key] - expected[key]) < tolerance, f"Key {key}: expected {expected[key]}, got {actual[key]}" - - -yaml_file_path = "policyengine/tests/economic_impact/budgetary_impact/overall/overall.yaml" - -# Check if the file exists -if not os.path.exists(yaml_file_path): - raise FileNotFoundError(f"The YAML file does not exist at: {yaml_file_path}") - -with open(yaml_file_path, 'r') as file: - test_cases = yaml.safe_load(file) - -@pytest.mark.parametrize("test_case", test_cases) -def test_economic_impact(test_case): - test_name = list(test_case.keys())[0] - test_data = test_case[test_name] - - economic_impact = EconomicImpact(test_data['reform'], test_data['country']) - - if 'budgetary' in test_name: - result = economic_impact.calculate("budgetary/overall/budgetary_impact") - elif 'benefit' in test_name: - result = economic_impact.calculate("budgetary/overall/benefit_spending_impact") - elif 'tax' in test_name: - result = economic_impact.calculate("budgetary/overall/tax_revenue_impact") - else: - pytest.fail(f"Unknown test case: {test_name}") - - assert_dict_approx_equal(result, test_data['expected']) - -if __name__ == "__main__": - pytest.main([__file__]) \ No newline at end of file diff --git a/policyengine/tests/economic_impact/distributional_impact/by_income/average/average.yaml b/policyengine/tests/economic_impact/distributional_impact/by_income/average/average.yaml deleted file mode 100644 index 0b2e8ac..0000000 --- a/policyengine/tests/economic_impact/distributional_impact/by_income/average/average.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Regular poverty by age -- test_by_income_average: - reform: - gov.hmrc.income_tax.rates.uk[0].rate: - "2024-01-01.2100-12-31": 0.55 - country: uk - expected: - average: - 1: -288.4419422028393 - 2: -936.7849231542535 - 3: -1655.8066942572789 - 4: -3040.3849413280304 - 5: -5082.727697456715 - 6: -8483.137124053015 - 7: -11510.447500088267 - 8: -14567.684605663524 - 9: -17960.33204429504 - 10: -22070.767989875396 - 11: -13195.0 \ No newline at end of file diff --git a/policyengine/tests/economic_impact/distributional_impact/by_income/average/test_average.py b/policyengine/tests/economic_impact/distributional_impact/by_income/average/test_average.py deleted file mode 100644 index 7ee0bdd..0000000 --- a/policyengine/tests/economic_impact/distributional_impact/by_income/average/test_average.py +++ /dev/null @@ -1,38 +0,0 @@ -import pytest -import yaml -import os -from policyengine import EconomicImpact - -def assert_dict_approx_equal(actual, expected, tolerance=1e-4): - assert set(actual.keys()) == set(expected.keys()), f"Keys don't match. Actual: {actual.keys()}, Expected: {expected.keys()}" - for key in expected: - if isinstance(expected[key], dict): - assert_dict_approx_equal(actual[key], expected[key], tolerance) - else: - assert abs(actual[key] - expected[key]) < tolerance, f"Key {key}: expected {expected[key]}, got {actual[key]}" - -yaml_file_path = "policyengine/tests/economic_impact/distributional_impact/by_income/average/average.yaml" - -# Check if the file exists -if not os.path.exists(yaml_file_path): - raise FileNotFoundError(f"The YAML file does not exist at: {yaml_file_path}") - -with open(yaml_file_path, 'r') as file: - test_cases = yaml.safe_load(file) - -@pytest.mark.parametrize("test_case", test_cases) -def test_economic_impact(test_case): - test_name = list(test_case.keys())[0] - test_data = test_case[test_name] - - economic_impact = EconomicImpact(test_data['reform'], test_data['country']) - - if 'average' in test_name: - result = economic_impact.calculate("distributional/by_income/average") - else: - pytest.fail(f"Unknown test case: {test_name}") - - assert_dict_approx_equal(result, test_data['expected']) - -if __name__ == "__main__": - pytest.main([__file__]) \ No newline at end of file diff --git a/policyengine/tests/economic_impact/distributional_impact/by_income/relative/relative.yaml b/policyengine/tests/economic_impact/distributional_impact/by_income/relative/relative.yaml deleted file mode 100644 index 8ab7b48..0000000 --- a/policyengine/tests/economic_impact/distributional_impact/by_income/relative/relative.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Regular poverty by age -- test_by_income_average: - reform: - gov.hmrc.income_tax.rates.uk[0].rate: - "2024-01-01.2100-12-31": 0.55 - country: uk - expected: - relative: - 1: -0.026265868982450032 - 2: -0.05321454070727573 - 3: -0.07293061898874185 - 4: -0.1046825029478255 - 5: -0.1407466605531786 - 6: -0.19129333726780962 - 7: -0.2185085314786923 - 8: -0.22920465056330844 - 9: -0.21991139800089637 - 10: -0.12790076021888797 - 11: -0.007019584080998393 \ No newline at end of file diff --git a/policyengine/tests/economic_impact/distributional_impact/by_income/relative/test_relative.py b/policyengine/tests/economic_impact/distributional_impact/by_income/relative/test_relative.py deleted file mode 100644 index bbe6fa5..0000000 --- a/policyengine/tests/economic_impact/distributional_impact/by_income/relative/test_relative.py +++ /dev/null @@ -1,38 +0,0 @@ -import pytest -import yaml -import os -from policyengine import EconomicImpact - -def assert_dict_approx_equal(actual, expected, tolerance=1e-4): - assert set(actual.keys()) == set(expected.keys()), f"Keys don't match. Actual: {actual.keys()}, Expected: {expected.keys()}" - for key in expected: - if isinstance(expected[key], dict): - assert_dict_approx_equal(actual[key], expected[key], tolerance) - else: - assert abs(actual[key] - expected[key]) < tolerance, f"Key {key}: expected {expected[key]}, got {actual[key]}" - -yaml_file_path = "policyengine/tests/economic_impact/distributional_impact/by_income/relative/relative.yaml" - -# Check if the file exists -if not os.path.exists(yaml_file_path): - raise FileNotFoundError(f"The YAML file does not exist at: {yaml_file_path}") - -with open(yaml_file_path, 'r') as file: - test_cases = yaml.safe_load(file) - -@pytest.mark.parametrize("test_case", test_cases) -def test_economic_impact(test_case): - test_name = list(test_case.keys())[0] - test_data = test_case[test_name] - - economic_impact = EconomicImpact(test_data['reform'], test_data['country']) - - if 'average' in test_name: - result = economic_impact.calculate("distributional/by_income/relative") - else: - pytest.fail(f"Unknown test case: {test_name}") - - assert_dict_approx_equal(result, test_data['expected']) - -if __name__ == "__main__": - pytest.main([__file__]) \ No newline at end of file diff --git a/policyengine/tests/economic_impact/distributional_impact/by_wealth/average/average.yaml b/policyengine/tests/economic_impact/distributional_impact/by_wealth/average/average.yaml deleted file mode 100644 index b7fa61a..0000000 --- a/policyengine/tests/economic_impact/distributional_impact/by_wealth/average/average.yaml +++ /dev/null @@ -1,18 +0,0 @@ -- test_by_wealth_average: - reform: - gov.hmrc.income_tax.rates.uk[0].rate: - "2024-01-01.2100-12-31": 0.55 - country: uk - expected: - average: - 1: -1542.5277425455492 - 2: -1982.534041798336 - 3: -4556.8445264034835 - 4: -5370.201917927786 - 5: -7216.048585689702 - 6: -7591.304204543115 - 7: -9211.025138324332 - 8: -9648.62199574695 - 9: -11506.889823053707 - 10: -14378.295708381549 - 11: -13195.0 \ No newline at end of file diff --git a/policyengine/tests/economic_impact/distributional_impact/by_wealth/average/test_average.py b/policyengine/tests/economic_impact/distributional_impact/by_wealth/average/test_average.py deleted file mode 100644 index b0c8fe9..0000000 --- a/policyengine/tests/economic_impact/distributional_impact/by_wealth/average/test_average.py +++ /dev/null @@ -1,38 +0,0 @@ -import pytest -import yaml -import os -from policyengine import EconomicImpact - -def assert_dict_approx_equal(actual, expected, tolerance=1e-4): - assert set(actual.keys()) == set(expected.keys()), f"Keys don't match. Actual: {actual.keys()}, Expected: {expected.keys()}" - for key in expected: - if isinstance(expected[key], dict): - assert_dict_approx_equal(actual[key], expected[key], tolerance) - else: - assert abs(actual[key] - expected[key]) < tolerance, f"Key {key}: expected {expected[key]}, got {actual[key]}" - -yaml_file_path = "policyengine/tests/economic_impact/distributional_impact/by_wealth/average/average.yaml" - -# Check if the file exists -if not os.path.exists(yaml_file_path): - raise FileNotFoundError(f"The YAML file does not exist at: {yaml_file_path}") - -with open(yaml_file_path, 'r') as file: - test_cases = yaml.safe_load(file) - -@pytest.mark.parametrize("test_case", test_cases) -def test_economic_impact(test_case): - test_name = list(test_case.keys())[0] - test_data = test_case[test_name] - - economic_impact = EconomicImpact(test_data['reform'], test_data['country']) - - if 'average' in test_name: - result = economic_impact.calculate("distributional/by_wealth/average") - else: - pytest.fail(f"Unknown test case: {test_name}") - - assert_dict_approx_equal(result, test_data['expected']) - -if __name__ == "__main__": - pytest.main([__file__]) \ No newline at end of file diff --git a/policyengine/tests/economic_impact/distributional_impact/by_wealth/relative/relative.yaml b/policyengine/tests/economic_impact/distributional_impact/by_wealth/relative/relative.yaml deleted file mode 100644 index 36bda21..0000000 --- a/policyengine/tests/economic_impact/distributional_impact/by_wealth/relative/relative.yaml +++ /dev/null @@ -1,18 +0,0 @@ -- test_by_wealth_relative: - reform: - gov.hmrc.income_tax.rates.uk[0].rate: - "2024-01-01.2100-12-31": 0.55 - country: uk - expected: - relative: - 1: -0.07043473659333963 - 2: -0.08091752038892668 - 3: -0.14646910570509714 - 4: -0.15592888462331828 - 5: -0.17838073974119345 - 6: -0.18231409463546466 - 7: -0.19855480132208575 - 8: -0.18318290061281753 - 9: -0.19595437624444986 - 10: -0.1391547762431559 - 11: -0.028110349900091886 \ No newline at end of file diff --git a/policyengine/tests/economic_impact/distributional_impact/by_wealth/relative/test_relative.py b/policyengine/tests/economic_impact/distributional_impact/by_wealth/relative/test_relative.py deleted file mode 100644 index c95e311..0000000 --- a/policyengine/tests/economic_impact/distributional_impact/by_wealth/relative/test_relative.py +++ /dev/null @@ -1,38 +0,0 @@ -import pytest -import yaml -import os -from policyengine import EconomicImpact - -def assert_dict_approx_equal(actual, expected, tolerance=1e-4): - assert set(actual.keys()) == set(expected.keys()), f"Keys don't match. Actual: {actual.keys()}, Expected: {expected.keys()}" - for key in expected: - if isinstance(expected[key], dict): - assert_dict_approx_equal(actual[key], expected[key], tolerance) - else: - assert abs(actual[key] - expected[key]) < tolerance, f"Key {key}: expected {expected[key]}, got {actual[key]}" - -yaml_file_path = "policyengine/tests/economic_impact/distributional_impact/by_wealth/relative/relative.yaml" - -# Check if the file exists -if not os.path.exists(yaml_file_path): - raise FileNotFoundError(f"The YAML file does not exist at: {yaml_file_path}") - -with open(yaml_file_path, 'r') as file: - test_cases = yaml.safe_load(file) - -@pytest.mark.parametrize("test_case", test_cases) -def test_economic_impact(test_case): - test_name = list(test_case.keys())[0] - test_data = test_case[test_name] - - economic_impact = EconomicImpact(test_data['reform'], test_data['country']) - - if 'relative' in test_name: - result = economic_impact.calculate("distributional/by_wealth/relative") - else: - pytest.fail(f"Unknown test case: {test_name}") - - assert_dict_approx_equal(result, test_data['expected']) - -if __name__ == "__main__": - pytest.main([__file__]) \ No newline at end of file diff --git a/policyengine/tests/economic_impact/inequality_impact.py/inequality_impact.py b/policyengine/tests/economic_impact/inequality_impact.py/inequality_impact.py deleted file mode 100644 index 32a0aec..0000000 --- a/policyengine/tests/economic_impact/inequality_impact.py/inequality_impact.py +++ /dev/null @@ -1,39 +0,0 @@ -import pytest -import yaml -import os -from policyengine import EconomicImpact - -def assert_dict_approx_equal(actual, expected, tolerance=1e-4): - for key in expected: - assert abs(actual[key] - expected[key]) < tolerance, f"Key {key}: expected {expected[key]}, got {actual[key]}" - -# Specify the path to your YAML file -yaml_file_path = "/home/tahseer/Desktop/NewFolder/policyengine.py/policyengine/tests/economic_impact/inequality_impact.py/inequality_impact.yaml" - -# Check if the file exists -if not os.path.exists(yaml_file_path): - raise FileNotFoundError(f"The YAML file does not exist at: {yaml_file_path}") - -with open(yaml_file_path, 'r') as file: - test_cases = yaml.safe_load(file) - -@pytest.mark.parametrize("test_case", test_cases) -def test_economic_impact(test_case): - test_name = list(test_case.keys())[0] - test_data = test_case[test_name] - - economic_impact = EconomicImpact(test_data['reform'], test_data['country']) - - if 'gini' in test_name: - result = economic_impact.calculate("inequality/gini") - elif 'top_1_pct' in test_name: - result = economic_impact.calculate("inequality/top_1_pct_share") - elif 'top_10_pct' in test_name: - result = economic_impact.calculate("inequality/top_10_pct_share") - else: - pytest.fail(f"Unknown test case: {test_name}") - - assert_dict_approx_equal(result, test_data['expected']) - -if __name__ == "__main__": - pytest.main([__file__]) diff --git a/policyengine/tests/economic_impact/inequality_impact.py/inequality_impact.yaml b/policyengine/tests/economic_impact/inequality_impact.py/inequality_impact.yaml deleted file mode 100644 index 2ca66a7..0000000 --- a/policyengine/tests/economic_impact/inequality_impact.py/inequality_impact.yaml +++ /dev/null @@ -1,33 +0,0 @@ -# economic_impact_tests.yaml -- test_gini_calculator: - reform: - gov.hmrc.income_tax.rates.uk[0].rate: - "2024-01-01.2100-12-31": 0.55 - country: uk - expected: - baseline: 0.42 - reform: 0.40 - change: -0.01 - change_percentage: -2.80 - -- test_top_1_pct_share_calculator: - reform: - gov.hmrc.income_tax.rates.uk[0].rate: - "2024-01-01.2100-12-31": 0.55 - country: uk - expected: - baseline: 0.12 - reform: 0.14 - change: 0.02 - change_percentage: 12.76 - -- test_top_10_pct_share_calculator: - reform: - gov.hmrc.income_tax.rates.uk[0].rate: - "2024-01-01.2100-12-31": 0.55 - country: uk - expected: - baseline: 0.35 - reform: 0.36 - change: 0.01 - change_percentage: 3.11 \ No newline at end of file diff --git a/policyengine/tests/economic_impact/poverty_impact/deep_poverty/by_age/by_age.yaml b/policyengine/tests/economic_impact/poverty_impact/deep_poverty/by_age/by_age.yaml deleted file mode 100644 index 65db7db..0000000 --- a/policyengine/tests/economic_impact/poverty_impact/deep_poverty/by_age/by_age.yaml +++ /dev/null @@ -1,40 +0,0 @@ -# Regular poverty by age -- test_child_poverty: - reform: - gov.hmrc.income_tax.rates.uk[0].rate: - "2024-01-01.2100-12-31": 0.55 - country: uk - expected: - baseline: 2.44 - reform: 2.45 - change: 0.7 - -- test_adult_poverty: - reform: - gov.hmrc.income_tax.rates.uk[0].rate: - "2024-01-01.2100-12-31": 0.55 - country: uk - expected: - baseline: 2.6 - reform: 2.7 - change: 3.9 - -- test_senior_poverty: - reform: - gov.hmrc.income_tax.rates.uk[0].rate: - "2024-01-01.2100-12-31": 0.55 - country: uk - expected: - baseline: 1.76 - reform: 1.76 - change: 0.5 - -- test_all_poverty: - reform: - gov.hmrc.income_tax.rates.uk[0].rate: - "2024-01-01.2100-12-31": 0.55 - country: uk - expected: - baseline: 2.41 - reform: 2.47 - change: 2.7 \ No newline at end of file diff --git a/policyengine/tests/economic_impact/poverty_impact/deep_poverty/by_age/test_deep_by_age.py b/policyengine/tests/economic_impact/poverty_impact/deep_poverty/by_age/test_deep_by_age.py deleted file mode 100644 index 2dc34f0..0000000 --- a/policyengine/tests/economic_impact/poverty_impact/deep_poverty/by_age/test_deep_by_age.py +++ /dev/null @@ -1,41 +0,0 @@ -import pytest -import yaml -import os -from policyengine import EconomicImpact - -def assert_dict_approx_equal(actual, expected, tolerance=1e-4): - for key in expected: - assert abs(actual[key] - expected[key]) < tolerance, f"Key {key}: expected {expected[key]}, got {actual[key]}" - - -yaml_file_path = "policyengine/tests/economic_impact/poverty_impact/deep_poverty/by_age/by_age.yaml" - -# Check if the file exists -if not os.path.exists(yaml_file_path): - raise FileNotFoundError(f"The YAML file does not exist at: {yaml_file_path}") - -with open(yaml_file_path, 'r') as file: - test_cases = yaml.safe_load(file) - -@pytest.mark.parametrize("test_case", test_cases) -def test_economic_impact(test_case): - test_name = list(test_case.keys())[0] - test_data = test_case[test_name] - - economic_impact = EconomicImpact(test_data['reform'], test_data['country']) - - if 'child' in test_name: - result = economic_impact.calculate("poverty/deep/child") - elif 'adult' in test_name: - result = economic_impact.calculate("poverty/deep/adult") - elif 'senior' in test_name: - result = economic_impact.calculate("poverty/deep/senior") - elif 'all' in test_name: - result = economic_impact.calculate("poverty/deep/age/all") - else: - pytest.fail(f"Unknown test case: {test_name}") - - assert_dict_approx_equal(result, test_data['expected']) - -if __name__ == "__main__": - pytest.main([__file__]) diff --git a/policyengine/tests/economic_impact/poverty_impact/deep_poverty/by_gender/by_gender.yaml b/policyengine/tests/economic_impact/poverty_impact/deep_poverty/by_gender/by_gender.yaml deleted file mode 100644 index 4187504..0000000 --- a/policyengine/tests/economic_impact/poverty_impact/deep_poverty/by_gender/by_gender.yaml +++ /dev/null @@ -1,30 +0,0 @@ -# Regular poverty by age -- male_poverty_test: - reform: - gov.hmrc.income_tax.rates.uk[0].rate: - "2024-01-01.2100-12-31": 0.55 - country: uk - expected: - baseline: 2.66 - reform: 2.73 - change: 2.5 - -- female_poverty_test: - reform: - gov.hmrc.income_tax.rates.uk[0].rate: - "2024-01-01.2100-12-31": 0.55 - country: uk - expected: - baseline: 2.16 - reform: 2.23 - change: 2.9 - -- all_poverty_test: - reform: - gov.hmrc.income_tax.rates.uk[0].rate: - "2024-01-01.2100-12-31": 0.55 - country: uk - expected: - baseline: 2.41 - reform: 2.47 - change: 2.7 \ No newline at end of file diff --git a/policyengine/tests/economic_impact/poverty_impact/deep_poverty/by_gender/test_deep_by_gender.py b/policyengine/tests/economic_impact/poverty_impact/deep_poverty/by_gender/test_deep_by_gender.py deleted file mode 100644 index 2dc34f0..0000000 --- a/policyengine/tests/economic_impact/poverty_impact/deep_poverty/by_gender/test_deep_by_gender.py +++ /dev/null @@ -1,41 +0,0 @@ -import pytest -import yaml -import os -from policyengine import EconomicImpact - -def assert_dict_approx_equal(actual, expected, tolerance=1e-4): - for key in expected: - assert abs(actual[key] - expected[key]) < tolerance, f"Key {key}: expected {expected[key]}, got {actual[key]}" - - -yaml_file_path = "policyengine/tests/economic_impact/poverty_impact/deep_poverty/by_age/by_age.yaml" - -# Check if the file exists -if not os.path.exists(yaml_file_path): - raise FileNotFoundError(f"The YAML file does not exist at: {yaml_file_path}") - -with open(yaml_file_path, 'r') as file: - test_cases = yaml.safe_load(file) - -@pytest.mark.parametrize("test_case", test_cases) -def test_economic_impact(test_case): - test_name = list(test_case.keys())[0] - test_data = test_case[test_name] - - economic_impact = EconomicImpact(test_data['reform'], test_data['country']) - - if 'child' in test_name: - result = economic_impact.calculate("poverty/deep/child") - elif 'adult' in test_name: - result = economic_impact.calculate("poverty/deep/adult") - elif 'senior' in test_name: - result = economic_impact.calculate("poverty/deep/senior") - elif 'all' in test_name: - result = economic_impact.calculate("poverty/deep/age/all") - else: - pytest.fail(f"Unknown test case: {test_name}") - - assert_dict_approx_equal(result, test_data['expected']) - -if __name__ == "__main__": - pytest.main([__file__]) diff --git a/policyengine/tests/economic_impact/poverty_impact/regular_poverty/by_age/by_age.yaml b/policyengine/tests/economic_impact/poverty_impact/regular_poverty/by_age/by_age.yaml deleted file mode 100644 index 8631f04..0000000 --- a/policyengine/tests/economic_impact/poverty_impact/regular_poverty/by_age/by_age.yaml +++ /dev/null @@ -1,40 +0,0 @@ -# Regular poverty by age -- test_child_poverty: - reform: - gov.hmrc.income_tax.rates.uk[0].rate: - "2024-01-01.2100-12-31": 0.55 - country: uk - expected: - baseline: 0.32 - reform: 0.36 - change: 10.1 - -- test_adult_poverty: - reform: - gov.hmrc.income_tax.rates.uk[0].rate: - "2024-01-01.2100-12-31": 0.55 - country: uk - expected: - baseline: 0.17 - reform: 0.19 - change: 8.4 - -- test_senior_poverty: - reform: - gov.hmrc.income_tax.rates.uk[0].rate: - "2024-01-01.2100-12-31": 0.55 - country: uk - expected: - baseline: 0.13 - reform: 0.17 - change: 30.5 - -- test_all_poverty: - reform: - gov.hmrc.income_tax.rates.uk[0].rate: - "2024-01-01.2100-12-31": 0.55 - country: uk - expected: - baseline: 0.2 - reform: 0.22 - change: 11.7 \ No newline at end of file diff --git a/policyengine/tests/economic_impact/poverty_impact/regular_poverty/by_age/test_by_age.py b/policyengine/tests/economic_impact/poverty_impact/regular_poverty/by_age/test_by_age.py deleted file mode 100644 index 64e61fd..0000000 --- a/policyengine/tests/economic_impact/poverty_impact/regular_poverty/by_age/test_by_age.py +++ /dev/null @@ -1,41 +0,0 @@ -import pytest -import yaml -import os -from policyengine import EconomicImpact - -def assert_dict_approx_equal(actual, expected, tolerance=1e-4): - for key in expected: - assert abs(actual[key] - expected[key]) < tolerance, f"Key {key}: expected {expected[key]}, got {actual[key]}" - - -yaml_file_path = "policyengine/tests/economic_impact/poverty_impact/regular_poverty/by_age/by_age.yaml" - -# Check if the file exists -if not os.path.exists(yaml_file_path): - raise FileNotFoundError(f"The YAML file does not exist at: {yaml_file_path}") - -with open(yaml_file_path, 'r') as file: - test_cases = yaml.safe_load(file) - -@pytest.mark.parametrize("test_case", test_cases) -def test_economic_impact(test_case): - test_name = list(test_case.keys())[0] - test_data = test_case[test_name] - - economic_impact = EconomicImpact(test_data['reform'], test_data['country']) - - if 'child' in test_name: - result = economic_impact.calculate("poverty/regular/child") - elif 'adult' in test_name: - result = economic_impact.calculate("poverty/regular/adult") - elif 'senior' in test_name: - result = economic_impact.calculate("poverty/regular/senior") - elif 'all' in test_name: - result = economic_impact.calculate("poverty/regular/age/all") - else: - pytest.fail(f"Unknown test case: {test_name}") - - assert_dict_approx_equal(result, test_data['expected']) - -if __name__ == "__main__": - pytest.main([__file__]) diff --git a/policyengine/tests/economic_impact/poverty_impact/regular_poverty/by_gender/by_gender.yaml b/policyengine/tests/economic_impact/poverty_impact/regular_poverty/by_gender/by_gender.yaml deleted file mode 100644 index 051eba8..0000000 --- a/policyengine/tests/economic_impact/poverty_impact/regular_poverty/by_gender/by_gender.yaml +++ /dev/null @@ -1,30 +0,0 @@ -# Regular poverty by age -- male_poverty_test: - reform: - gov.hmrc.income_tax.rates.uk[0].rate: - "2024-01-01.2100-12-31": 0.55 - country: uk - expected: - baseline: 0.18 - reform: 0.21 - change: 12.9 - -- female_poverty_test: - reform: - gov.hmrc.income_tax.rates.uk[0].rate: - "2024-01-01.2100-12-31": 0.55 - country: uk - expected: - baseline: 0.21 - reform: 0.23 - change: 10.7 - -- all_poverty_test: - reform: - gov.hmrc.income_tax.rates.uk[0].rate: - "2024-01-01.2100-12-31": 0.55 - country: uk - expected: - baseline: 0.2 - reform: 0.22 - change: 11.7 \ No newline at end of file diff --git a/policyengine/tests/economic_impact/poverty_impact/regular_poverty/by_gender/test_by_gender.py b/policyengine/tests/economic_impact/poverty_impact/regular_poverty/by_gender/test_by_gender.py deleted file mode 100644 index fc74fa9..0000000 --- a/policyengine/tests/economic_impact/poverty_impact/regular_poverty/by_gender/test_by_gender.py +++ /dev/null @@ -1,39 +0,0 @@ -import pytest -import yaml -import os -from policyengine import EconomicImpact - -def assert_dict_approx_equal(actual, expected, tolerance=1e-4): - for key in expected: - assert abs(actual[key] - expected[key]) < tolerance, f"Key {key}: expected {expected[key]}, got {actual[key]}" - - -yaml_file_path = "policyengine/tests/economic_impact/poverty_impact/regular_poverty/by_gender/by_gender.yaml" - -# Check if the file exists -if not os.path.exists(yaml_file_path): - raise FileNotFoundError(f"The YAML file does not exist at: {yaml_file_path}") - -with open(yaml_file_path, 'r') as file: - test_cases = yaml.safe_load(file) - -@pytest.mark.parametrize("test_case", test_cases) -def test_economic_impact(test_case): - test_name = list(test_case.keys())[0] - test_data = test_case[test_name] - - economic_impact = EconomicImpact(test_data['reform'], test_data['country']) - - if test_name.startswith('female'): - result = economic_impact.calculate("poverty/regular/female") - elif test_name.startswith('male'): - result = economic_impact.calculate("poverty/regular/male") - elif 'all' in test_name: - result = economic_impact.calculate("poverty/regular/gender/all") - else: - pytest.fail(f"Unknown test case: {test_name}") - - assert_dict_approx_equal(result, test_data['expected']) - -if __name__ == "__main__": - pytest.main([__file__]) diff --git a/policyengine/tests/economic_impact/winners_and_losers/test_by_income_decile/by_income_decile.yaml b/policyengine/tests/economic_impact/winners_and_losers/test_by_income_decile/by_income_decile.yaml deleted file mode 100644 index 1fa58c4..0000000 --- a/policyengine/tests/economic_impact/winners_and_losers/test_by_income_decile/by_income_decile.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Winners and Losers by income -- income_impact_test: - reform: - gov.irs.credits.ctc.refundable.fully_refundable: - "2024-01-01.2100-12-31": True - country: us - expected: - deciles: - Lose more than 5%: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] - Lose less than 5%: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] - No change: [82.9, 76.6, 78.3, 85.8, 89.9, 93.4, 94.7, 96.5, 96.9, 98.5] - Gain less than 5%: [0.7, 6.1, 8.2, 6.2, 4.6, 4.8, 3.7, 2.6, 2.7, 1.5] - Gain more than 5%: [16.4, 17.3, 13.4, 8.0, 5.5, 1.8, 1.6, 0.8, 0.5, 0.0] - all: - Lose more than 5%: 0.0 - Lose less than 5%: 0.0 - No change: 89.3 - Gain less than 5%: 4.1 - Gain more than 5%: 6.5 diff --git a/policyengine/tests/economic_impact/winners_and_losers/test_by_income_decile/test_by_income_decile.py b/policyengine/tests/economic_impact/winners_and_losers/test_by_income_decile/test_by_income_decile.py deleted file mode 100644 index 6f05044..0000000 --- a/policyengine/tests/economic_impact/winners_and_losers/test_by_income_decile/test_by_income_decile.py +++ /dev/null @@ -1,40 +0,0 @@ -import pytest -import yaml -import os -from policyengine import EconomicImpact - -def assert_dict_approx_equal(actual, expected, tolerance=1e-4): - if isinstance(expected, dict): - for key in expected: - assert_dict_approx_equal(actual[key], expected[key], tolerance) - elif isinstance(expected, list): - assert len(actual) == len(expected), f"List length mismatch: expected {len(expected)}, got {len(actual)}" - for a, e in zip(actual, expected): - assert abs(a - e) < tolerance, f"Expected {e}, got {a}" - else: - assert abs(actual - expected) < tolerance, f"Expected {expected}, got {actual}" - -yaml_file_path = "policyengine/tests/economic_impact/winners_and_losers/test_by_income_decile/by_income_decile.yaml" - -# Check if the file exists -if not os.path.exists(yaml_file_path): - raise FileNotFoundError(f"The YAML file does not exist at: {yaml_file_path}") - -with open(yaml_file_path, 'r') as file: - test_cases = yaml.safe_load(file) - -@pytest.mark.parametrize("test_case", test_cases) -def test_economic_impact(test_case): - test_name = list(test_case.keys())[0] - test_data = test_case[test_name] - - if test_name != 'income_impact_test': - pytest.skip(f"Skipping non-income test: {test_name}") - - economic_impact = EconomicImpact(test_data['reform'], test_data['country']) - result = economic_impact.calculate("winners_and_losers/by_income_decile") - - assert_dict_approx_equal(result['result'], test_data['expected']) - -if __name__ == "__main__": - pytest.main([__file__]) \ No newline at end of file diff --git a/policyengine/tests/economic_impact/winners_and_losers/test_by_wealth_decile/by_wealth_decile.yaml b/policyengine/tests/economic_impact/winners_and_losers/test_by_wealth_decile/by_wealth_decile.yaml deleted file mode 100644 index 7c004e1..0000000 --- a/policyengine/tests/economic_impact/winners_and_losers/test_by_wealth_decile/by_wealth_decile.yaml +++ /dev/null @@ -1,18 +0,0 @@ -- wealth_impact_test: - reform: - gov.hmrc.income_tax.rates.uk[0].rate: - "2024-01-01.2100-12-31": 0.55 - country: uk - expected: - deciles: - Lose more than 5%: [21.1, 31.6, 59.2, 64.3, 67.1, 80.1, 81.1, 82.6, 84.5, 86.4] - Lose less than 5%: [6.5, 9.7, 2.4, 2.0, 3.8, 0.8, 0.4, 2.6, 0.2, 1.2] - No change: [72.4, 58.7, 38.4, 33.6, 29.1, 19.1, 18.4, 14.8, 15.3, 12.4] - Gain less than 5%: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] - Gain more than 5%: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] - all: - Lose more than 5%: 65.8 - Lose less than 5%: 3.0 - No change: 31.2 - Gain less than 5%: 0.0 - Gain more than 5%: 0.0 \ No newline at end of file diff --git a/policyengine/tests/economic_impact/winners_and_losers/test_by_wealth_decile/test_by_wealth_decile.py b/policyengine/tests/economic_impact/winners_and_losers/test_by_wealth_decile/test_by_wealth_decile.py deleted file mode 100644 index 4c94bc3..0000000 --- a/policyengine/tests/economic_impact/winners_and_losers/test_by_wealth_decile/test_by_wealth_decile.py +++ /dev/null @@ -1,40 +0,0 @@ -import pytest -import yaml -import os -from policyengine import EconomicImpact - -def assert_dict_approx_equal(actual, expected, tolerance=5e-2): - if isinstance(expected, dict): - for key in expected: - assert_dict_approx_equal(actual[key], expected[key], tolerance) - elif isinstance(expected, list): - assert len(actual) == len(expected), f"List length mismatch: expected {len(expected)}, got {len(actual)}" - for a, e in zip(actual, expected): - assert abs(a - e) < tolerance, f"Expected {e}, got {a}" - else: - assert abs(actual - expected) < tolerance, f"Expected {expected}, got {actual}" - -yaml_file_path = "policyengine/tests/economic_impact/winners_and_losers/test_by_wealth_decile/by_wealth_decile.yaml" - -# Check if the file exists -if not os.path.exists(yaml_file_path): - raise FileNotFoundError(f"The YAML file does not exist at: {yaml_file_path}") - -with open(yaml_file_path, 'r') as file: - test_cases = yaml.safe_load(file) - -@pytest.mark.parametrize("test_case", test_cases) -def test_economic_impact(test_case): - test_name = list(test_case.keys())[0] - test_data = test_case[test_name] - - if test_name != 'wealth_impact_test': - pytest.skip(f"Skipping non-wealth test: {test_name}") - - economic_impact = EconomicImpact(test_data['reform'], test_data['country']) - result = economic_impact.calculate("winners_and_losers/by_wealth_decile") - - assert_dict_approx_equal(result['result'], test_data['expected']) - -if __name__ == "__main__": - pytest.main([__file__]) \ No newline at end of file diff --git a/simulation.py b/simulation.py new file mode 100644 index 0000000..0c46c6a --- /dev/null +++ b/simulation.py @@ -0,0 +1,48 @@ + +macro_single = Simulation( + country="uk", + type="forecast/macro", + data="enhanced_frs_2022_23", + year=2025, + reform={ + "reform": { + "gov.hmrc.income_tax.allowances.personal_allowance.amount": 0, + }, + } +) + +macro_single.calculate("budget/revenue") # -> £700 billion + +macro = Simulation( + country="uk", + type="impact/macro", + data="enhanced_frs_2022_23", + year=2025, + reform={ + "reform": { + "gov.hmrc.income_tax.allowances.personal_allowance.amount": 0, + }, + } +) + +macro.calculate("budget/revenue_impact") # -> +£100 billion + +micro = Simulation( + country="uk", + type="impact/household/general", + data={ + "employment_income": { + 2025: 30_000, + } + }, + year=2025, + reform={ + "reform": { + "gov.hmrc.income_tax.allowances.personal_allowance.amount": 0, + }, + }, +) + +micro.calculate("budget/income_impact") # -> -£5,000 +micro.calculate("budget/net_income_change/chart") # plotly.graph_objects.Figure + diff --git a/test.ipynb b/test.ipynb new file mode 100644 index 0000000..d89f4ab --- /dev/null +++ b/test.ipynb @@ -0,0 +1,93 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from policyengine import Simulation" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "sim = Simulation(\n", + " \"uk\",\n", + " \"macro\",\n", + " \"enhanced_frs_2022_23\",\n", + " 2025,\n", + " reform={\n", + " \"gov.hmrc.income_tax.allowances.personal_allowance.amount\": {\n", + " \"year:2025:1\": 0,\n", + " },\n", + " },\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'impact': {'revenue_impact': 137.76716397870905}}" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sim.calculate(\"macro\")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'revenue_impact': 137.76716397870905}" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sim.calculate(\"macro/impact\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "base", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.14" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 783293da4898e7c7467f5e06ee76cf2cd52d989a Mon Sep 17 00:00:00 2001 From: Nikhil Woodruff Date: Wed, 20 Nov 2024 15:18:49 +0000 Subject: [PATCH 3/3] Add changes --- .../macro/impact/impact_by_constituency.py | 15 + .../outputs/macro/impact/poverty_impact.py | 6 + policyengine/simulation.py | 19 +- simulation.py | 5 +- test.ipynb | 954 +++++++++++++++++- 5 files changed, 979 insertions(+), 20 deletions(-) create mode 100644 policyengine/outputs/macro/impact/impact_by_constituency.py create mode 100644 policyengine/outputs/macro/impact/poverty_impact.py diff --git a/policyengine/outputs/macro/impact/impact_by_constituency.py b/policyengine/outputs/macro/impact/impact_by_constituency.py new file mode 100644 index 0000000..a0c49b3 --- /dev/null +++ b/policyengine/outputs/macro/impact/impact_by_constituency.py @@ -0,0 +1,15 @@ +from policyengine import Simulation +import plotly.express as px +from policyengine_core.charts import * + +def impact_by_constituency(simulation: Simulation): + """Calculate the revenue impact of the given simulation. + + Args: + simulation (Simulation): The simulation for which the revenue impact is to be calculated. + + Returns: + float: The revenue impact of the simulation. + """ + fig = px.line(x=[1, 2, 3], y=[1, 3, 2]) + return format_fig(fig) \ No newline at end of file diff --git a/policyengine/outputs/macro/impact/poverty_impact.py b/policyengine/outputs/macro/impact/poverty_impact.py new file mode 100644 index 0000000..f8169e5 --- /dev/null +++ b/policyengine/outputs/macro/impact/poverty_impact.py @@ -0,0 +1,6 @@ + + +def poverty_impact(simulation): + + single_hh = simulation.custom_data[0] + return single_hh - married_hh \ No newline at end of file diff --git a/policyengine/simulation.py b/policyengine/simulation.py index ed63485..ce58e8d 100644 --- a/policyengine/simulation.py +++ b/policyengine/simulation.py @@ -1,5 +1,6 @@ from policyengine_core import Simulation as CountrySimulation from policyengine_core.reforms import Reform +from typing import Tuple class Simulation: """The top-level class through which all PE usage is carried out.""" @@ -16,6 +17,8 @@ class Simulation: """The baseline simulation inputs.""" reform: dict """The reform simulation inputs.""" + custom_data: dict + """The custom data for the simulation.""" baseline: CountrySimulation reformed: CountrySimulation @@ -52,6 +55,14 @@ def __init__(self, country: str, type: str, data: str, time_period: str, reform: self.output_functions, self.outputs = self.get_outputs() def calculate(self, output: str): + """Calculate the given output (path). + + Args: + output (str): The output to calculate. Must be a valid path in the output tree. + + Returns: + Any: The output of the calculation (using the cache if possible). + """ if output.endswith("/"): output = output[:-1] @@ -75,7 +86,12 @@ def calculate(self, output: str): return node - def get_outputs(self) -> tuple: + def get_outputs(self) -> Tuple[dict, dict]: + """Get all the output functions and construct the output tree. + + Returns: + Tuple[dict, dict]: A tuple containing the output functions and the output tree. + """ from pathlib import Path import importlib.util @@ -104,3 +120,4 @@ def get_outputs(self) -> tuple: current[parts[-1]] = None return output_functions, outputs + diff --git a/simulation.py b/simulation.py index 0c46c6a..906e8a0 100644 --- a/simulation.py +++ b/simulation.py @@ -1,4 +1,3 @@ - macro_single = Simulation( country="uk", type="forecast/macro", @@ -15,7 +14,7 @@ macro = Simulation( country="uk", - type="impact/macro", + type="macro", data="enhanced_frs_2022_23", year=2025, reform={ @@ -29,7 +28,7 @@ micro = Simulation( country="uk", - type="impact/household/general", + type="household", data={ "employment_income": { 2025: 30_000, diff --git a/test.ipynb b/test.ipynb index d89f4ab..d2ebc18 100644 --- a/test.ipynb +++ b/test.ipynb @@ -11,61 +11,983 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sim = Simulation(\n", - " \"uk\",\n", - " \"macro\",\n", - " \"enhanced_frs_2022_23\",\n", - " 2025,\n", + " country=\"uk\",\n", + " type=\"economy\",\n", + " data=None,\n", + " time_period=2025,\n", " reform={\n", " \"gov.hmrc.income_tax.allowances.personal_allowance.amount\": {\n", - " \"year:2025:1\": 0,\n", - " },\n", - " },\n", + " \"year:2025:10\": 0\n", + " }\n", + " }\n", + " custom_data={\n", + " \"single_hh\": {...},\n", + " \"married_hh\": {...},\n", + " }\n", ")" ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "{'impact': {'revenue_impact': 137.76716397870905}}" + "5" ] }, - "execution_count": 4, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "sim.calculate(\"macro\")" + "sim.calculate(\"macro/impact/poverty_impact\")" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "{'revenue_impact': 137.76716397870905}" + "137.76716397870905" ] }, - "execution_count": 5, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "sim.calculate(\"macro/impact\")" + "sim.calculate(\"macro/impact/revenue_impact\")" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.plotly.v1+json": { + "config": { + "plotlyServerURL": "https://plot.ly" + }, + "data": [ + { + "hovertemplate": "x=%{x}
y=%{y}", + "legendgroup": "", + "line": { + "color": "#636efa", + "dash": "solid" + }, + "marker": { + "symbol": "circle" + }, + "mode": "lines", + "name": "", + "orientation": "v", + "showlegend": false, + "type": "scatter", + "x": [ + 1, + 2, + 3 + ], + "xaxis": "x", + "y": [ + 1, + 3, + 2 + ], + "yaxis": "y" + } + ], + "layout": { + "font": { + "color": "black", + "family": "Roboto Serif" + }, + "height": 600, + "images": [ + { + "sizex": 0.15, + "sizey": 0.15, + "source": "https://raw.githubusercontent.com/PolicyEngine/policyengine-app/master/src/images/logos/policyengine/blue.png", + "x": 1.1, + "xanchor": "right", + "xref": "paper", + "y": -0.15, + "yanchor": "bottom", + "yref": "paper" + } + ], + "legend": { + "tracegroupgap": 0 + }, + "margin": { + "t": 60 + }, + "modebar": { + "bgcolor": "rgba(0,0,0,0)", + "color": "rgba(0,0,0,0)" + }, + "template": { + "data": { + "bar": [ + { + "error_x": { + "color": "#2a3f5f" + }, + "error_y": { + "color": "#2a3f5f" + }, + "marker": { + "line": { + "color": "white", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "bar" + } + ], + "barpolar": [ + { + "marker": { + "line": { + "color": "white", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "barpolar" + } + ], + "carpet": [ + { + "aaxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "#C8D4E3", + "linecolor": "#C8D4E3", + "minorgridcolor": "#C8D4E3", + "startlinecolor": "#2a3f5f" + }, + "baxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "#C8D4E3", + "linecolor": "#C8D4E3", + "minorgridcolor": "#C8D4E3", + "startlinecolor": "#2a3f5f" + }, + "type": "carpet" + } + ], + "choropleth": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "choropleth" + } + ], + "contour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "contour" + } + ], + "contourcarpet": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "contourcarpet" + } + ], + "heatmap": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmap" + } + ], + "heatmapgl": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmapgl" + } + ], + "histogram": [ + { + "marker": { + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "histogram" + } + ], + "histogram2d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2d" + } + ], + "histogram2dcontour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2dcontour" + } + ], + "mesh3d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "mesh3d" + } + ], + "parcoords": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "parcoords" + } + ], + "pie": [ + { + "automargin": true, + "type": "pie" + } + ], + "scatter": [ + { + "fillpattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + }, + "type": "scatter" + } + ], + "scatter3d": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatter3d" + } + ], + "scattercarpet": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattercarpet" + } + ], + "scattergeo": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergeo" + } + ], + "scattergl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergl" + } + ], + "scattermapbox": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattermapbox" + } + ], + "scatterpolar": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolar" + } + ], + "scatterpolargl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolargl" + } + ], + "scatterternary": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterternary" + } + ], + "surface": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "surface" + } + ], + "table": [ + { + "cells": { + "fill": { + "color": "#EBF0F8" + }, + "line": { + "color": "white" + } + }, + "header": { + "fill": { + "color": "#C8D4E3" + }, + "line": { + "color": "white" + } + }, + "type": "table" + } + ] + }, + "layout": { + "annotationdefaults": { + "arrowcolor": "#2a3f5f", + "arrowhead": 0, + "arrowwidth": 1 + }, + "autotypenumbers": "strict", + "coloraxis": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "colorscale": { + "diverging": [ + [ + 0, + "#8e0152" + ], + [ + 0.1, + "#c51b7d" + ], + [ + 0.2, + "#de77ae" + ], + [ + 0.3, + "#f1b6da" + ], + [ + 0.4, + "#fde0ef" + ], + [ + 0.5, + "#f7f7f7" + ], + [ + 0.6, + "#e6f5d0" + ], + [ + 0.7, + "#b8e186" + ], + [ + 0.8, + "#7fbc41" + ], + [ + 0.9, + "#4d9221" + ], + [ + 1, + "#276419" + ] + ], + "sequential": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "sequentialminus": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ] + }, + "colorway": [ + "#636efa", + "#EF553B", + "#00cc96", + "#ab63fa", + "#FFA15A", + "#19d3f3", + "#FF6692", + "#B6E880", + "#FF97FF", + "#FECB52" + ], + "font": { + "color": "#2a3f5f" + }, + "geo": { + "bgcolor": "white", + "lakecolor": "white", + "landcolor": "white", + "showlakes": true, + "showland": true, + "subunitcolor": "#C8D4E3" + }, + "hoverlabel": { + "align": "left" + }, + "hovermode": "closest", + "mapbox": { + "style": "light" + }, + "paper_bgcolor": "white", + "plot_bgcolor": "white", + "polar": { + "angularaxis": { + "gridcolor": "#EBF0F8", + "linecolor": "#EBF0F8", + "ticks": "" + }, + "bgcolor": "white", + "radialaxis": { + "gridcolor": "#EBF0F8", + "linecolor": "#EBF0F8", + "ticks": "" + } + }, + "scene": { + "xaxis": { + "backgroundcolor": "white", + "gridcolor": "#DFE8F3", + "gridwidth": 2, + "linecolor": "#EBF0F8", + "showbackground": true, + "ticks": "", + "zerolinecolor": "#EBF0F8" + }, + "yaxis": { + "backgroundcolor": "white", + "gridcolor": "#DFE8F3", + "gridwidth": 2, + "linecolor": "#EBF0F8", + "showbackground": true, + "ticks": "", + "zerolinecolor": "#EBF0F8" + }, + "zaxis": { + "backgroundcolor": "white", + "gridcolor": "#DFE8F3", + "gridwidth": 2, + "linecolor": "#EBF0F8", + "showbackground": true, + "ticks": "", + "zerolinecolor": "#EBF0F8" + } + }, + "shapedefaults": { + "line": { + "color": "#2a3f5f" + } + }, + "ternary": { + "aaxis": { + "gridcolor": "#DFE8F3", + "linecolor": "#A2B1C6", + "ticks": "" + }, + "baxis": { + "gridcolor": "#DFE8F3", + "linecolor": "#A2B1C6", + "ticks": "" + }, + "bgcolor": "white", + "caxis": { + "gridcolor": "#DFE8F3", + "linecolor": "#A2B1C6", + "ticks": "" + } + }, + "title": { + "x": 0.05 + }, + "xaxis": { + "automargin": true, + "gridcolor": "#EBF0F8", + "linecolor": "#EBF0F8", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "#EBF0F8", + "zerolinewidth": 2 + }, + "yaxis": { + "automargin": true, + "gridcolor": "#EBF0F8", + "linecolor": "#EBF0F8", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "#EBF0F8", + "zerolinewidth": 2 + } + } + }, + "width": 800, + "xaxis": { + "anchor": "y", + "domain": [ + 0, + 1 + ], + "title": { + "text": "x" + } + }, + "yaxis": { + "anchor": "x", + "domain": [ + 0, + 1 + ], + "title": { + "text": "y" + } + } + } + } + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "sim.calculate(\"macro/impact/impact_by_constituency\")" ] } ],