policyengine 2.2.0__tar.gz → 2.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. {policyengine-2.2.0 → policyengine-2.2.2}/CHANGELOG.md +14 -0
  2. {policyengine-2.2.0 → policyengine-2.2.2}/PKG-INFO +1 -1
  3. {policyengine-2.2.0 → policyengine-2.2.2}/changelog.yaml +10 -0
  4. {policyengine-2.2.0 → policyengine-2.2.2}/docs/_config.yml +3 -10
  5. {policyengine-2.2.0 → policyengine-2.2.2}/policyengine/outputs/macro/comparison/budget/general.py +3 -1
  6. policyengine-2.2.2/policyengine/outputs/macro/comparison/local_areas/parliamentary_constituencies.py +46 -0
  7. policyengine-2.2.2/policyengine/outputs/macro/single/gov/local_areas/parliamentary_constituencies.py +85 -0
  8. {policyengine-2.2.0 → policyengine-2.2.2}/policyengine/outputs/macro/single/household/income_distribution.py +1 -1
  9. {policyengine-2.2.0 → policyengine-2.2.2}/policyengine/simulation.py +11 -0
  10. policyengine-2.2.2/policyengine/utils/constituency_maps.py +91 -0
  11. {policyengine-2.2.0 → policyengine-2.2.2}/policyengine.egg-info/PKG-INFO +1 -1
  12. {policyengine-2.2.0 → policyengine-2.2.2}/policyengine.egg-info/SOURCES.txt +1 -0
  13. {policyengine-2.2.0 → policyengine-2.2.2}/pyproject.toml +1 -1
  14. policyengine-2.2.0/policyengine/outputs/macro/comparison/local_areas/parliamentary_constituencies.py +0 -167
  15. policyengine-2.2.0/policyengine/outputs/macro/single/gov/local_areas/parliamentary_constituencies.py +0 -47
  16. {policyengine-2.2.0 → policyengine-2.2.2}/.github/CONTRIBUTING.md +0 -0
  17. {policyengine-2.2.0 → policyengine-2.2.2}/.github/changelog_template.md +0 -0
  18. {policyengine-2.2.0 → policyengine-2.2.2}/.github/fetch_version.py +0 -0
  19. {policyengine-2.2.0 → policyengine-2.2.2}/.github/get-changelog-diff.sh +0 -0
  20. {policyengine-2.2.0 → policyengine-2.2.2}/.github/has-functional-changes.sh +0 -0
  21. {policyengine-2.2.0 → policyengine-2.2.2}/.github/is-version-number-acceptable.sh +0 -0
  22. {policyengine-2.2.0 → policyengine-2.2.2}/.github/publish-git-tag.sh +0 -0
  23. {policyengine-2.2.0 → policyengine-2.2.2}/.github/workflows/pr.yml +0 -0
  24. {policyengine-2.2.0 → policyengine-2.2.2}/.github/workflows/push.yml +0 -0
  25. {policyengine-2.2.0 → policyengine-2.2.2}/.gitignore +0 -0
  26. {policyengine-2.2.0 → policyengine-2.2.2}/LICENSE +0 -0
  27. {policyengine-2.2.0 → policyengine-2.2.2}/Makefile +0 -0
  28. {policyengine-2.2.0 → policyengine-2.2.2}/README.md +0 -0
  29. {policyengine-2.2.0 → policyengine-2.2.2}/changelog_entry.yaml +0 -0
  30. {policyengine-2.2.0 → policyengine-2.2.2}/docs/_static/style.css +0 -0
  31. {policyengine-2.2.0 → policyengine-2.2.2}/docs/_toc.yml +0 -0
  32. {policyengine-2.2.0 → policyengine-2.2.2}/docs/index.ipynb +0 -0
  33. {policyengine-2.2.0 → policyengine-2.2.2}/docs/logo.png +0 -0
  34. {policyengine-2.2.0 → policyengine-2.2.2}/docs/maintaining.ipynb +0 -0
  35. {policyengine-2.2.0 → policyengine-2.2.2}/docs/schema.ipynb +0 -0
  36. {policyengine-2.2.0 → policyengine-2.2.2}/policyengine/__init__.py +0 -0
  37. {policyengine-2.2.0 → policyengine-2.2.2}/policyengine/constants.py +0 -0
  38. {policyengine-2.2.0 → policyengine-2.2.2}/policyengine/outputs/household/comparison/net_income_change.py +0 -0
  39. {policyengine-2.2.0 → policyengine-2.2.2}/policyengine/outputs/household/single/net_income.py +0 -0
  40. {policyengine-2.2.0 → policyengine-2.2.2}/policyengine/outputs/macro/comparison/budget/programs.py +0 -0
  41. {policyengine-2.2.0 → policyengine-2.2.2}/policyengine/outputs/macro/comparison/budget/window.py +0 -0
  42. {policyengine-2.2.0 → policyengine-2.2.2}/policyengine/outputs/macro/comparison/decile/income.py +0 -0
  43. {policyengine-2.2.0 → policyengine-2.2.2}/policyengine/outputs/macro/comparison/decile/wealth.py +0 -0
  44. {policyengine-2.2.0 → policyengine-2.2.2}/policyengine/outputs/macro/comparison/inequality.py +0 -0
  45. {policyengine-2.2.0 → policyengine-2.2.2}/policyengine/outputs/macro/comparison/labor_supply_response.py +0 -0
  46. {policyengine-2.2.0 → policyengine-2.2.2}/policyengine/outputs/macro/comparison/poverty/age.py +0 -0
  47. {policyengine-2.2.0 → policyengine-2.2.2}/policyengine/outputs/macro/comparison/poverty/gender.py +0 -0
  48. {policyengine-2.2.0 → policyengine-2.2.2}/policyengine/outputs/macro/comparison/poverty/race.py +0 -0
  49. {policyengine-2.2.0 → policyengine-2.2.2}/policyengine/outputs/macro/comparison/winners/income_decile.py +0 -0
  50. {policyengine-2.2.0 → policyengine-2.2.2}/policyengine/outputs/macro/comparison/winners/wealth_decile.py +0 -0
  51. {policyengine-2.2.0 → policyengine-2.2.2}/policyengine/outputs/macro/single/gov/balance.py +0 -0
  52. {policyengine-2.2.0 → policyengine-2.2.2}/policyengine/outputs/macro/single/gov/budget_window.py +0 -0
  53. {policyengine-2.2.0 → policyengine-2.2.2}/policyengine/outputs/macro/single/gov/programs.py +0 -0
  54. {policyengine-2.2.0 → policyengine-2.2.2}/policyengine/outputs/macro/single/household/demographics.py +0 -0
  55. {policyengine-2.2.0 → policyengine-2.2.2}/policyengine/outputs/macro/single/household/finance.py +0 -0
  56. {policyengine-2.2.0 → policyengine-2.2.2}/policyengine/outputs/macro/single/household/inequality.py +0 -0
  57. {policyengine-2.2.0 → policyengine-2.2.2}/policyengine/outputs/macro/single/household/labor_supply.py +0 -0
  58. {policyengine-2.2.0 → policyengine-2.2.2}/policyengine/utils/__init__.py +0 -0
  59. {policyengine-2.2.0 → policyengine-2.2.2}/policyengine/utils/charts.py +0 -0
  60. {policyengine-2.2.0 → policyengine-2.2.2}/policyengine/utils/huggingface.py +0 -0
  61. {policyengine-2.2.0 → policyengine-2.2.2}/policyengine.egg-info/dependency_links.txt +0 -0
  62. {policyengine-2.2.0 → policyengine-2.2.2}/policyengine.egg-info/requires.txt +0 -0
  63. {policyengine-2.2.0 → policyengine-2.2.2}/policyengine.egg-info/top_level.txt +0 -0
  64. {policyengine-2.2.0 → policyengine-2.2.2}/setup.cfg +0 -0
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.2.2] - 2024-12-06 14:02:41
9
+
10
+ ### Fixed
11
+
12
+ - Household simulation example broken due to a type error.
13
+
14
+ ## [2.2.1] - 2024-12-03 13:16:27
15
+
16
+ ### Fixed
17
+
18
+ - Formatting in constituency charts.
19
+
8
20
  ## [2.2.0] - 2024-12-02 13:48:27
9
21
 
10
22
  ### Added
@@ -39,6 +51,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
39
51
 
40
52
 
41
53
 
54
+ [2.2.2]: https://github.com/PolicyEngine/policyengine.py/compare/2.2.1...2.2.2
55
+ [2.2.1]: https://github.com/PolicyEngine/policyengine.py/compare/2.2.0...2.2.1
42
56
  [2.2.0]: https://github.com/PolicyEngine/policyengine.py/compare/2.1.0...2.2.0
43
57
  [2.1.0]: https://github.com/PolicyEngine/policyengine.py/compare/2.0.0...2.1.0
44
58
  [2.0.0]: https://github.com/PolicyEngine/policyengine.py/compare/1.0.1...2.0.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: policyengine
3
- Version: 2.2.0
3
+ Version: 2.2.2
4
4
  Summary: A package to conduct policy analysis using PolicyEngine tax-benefit models.
5
5
  Author-email: PolicyEngine <hello@policyengine.org>
6
6
  License: GNU AFFERO GENERAL PUBLIC LICENSE
@@ -26,3 +26,13 @@
26
26
  - State level subsets.
27
27
  - Huggingface token CLI entry.
28
28
  date: 2024-12-02 13:48:27
29
+ - bump: patch
30
+ changes:
31
+ fixed:
32
+ - Formatting in constituency charts.
33
+ date: 2024-12-03 13:16:27
34
+ - bump: patch
35
+ changes:
36
+ fixed:
37
+ - Household simulation example broken due to a type error.
38
+ date: 2024-12-06 14:02:41
@@ -1,7 +1,7 @@
1
1
  # _config.yml
2
- title: PolicyEngine
3
2
  author: PolicyEngine
4
- copyright: "2022"
3
+ title: PolicyEngine
4
+ copyright: "2025"
5
5
  logo: logo.png
6
6
 
7
7
  execute:
@@ -19,11 +19,4 @@ sphinx:
19
19
  html_theme: furo
20
20
  pygments_style: default
21
21
  html_css_files:
22
- - style.css
23
- # extra_extensions:
24
- # - sphinx.ext.autodoc
25
- # - sphinxarg.ext
26
- # - sphinx.ext.viewcode
27
- # - sphinx.ext.napoleon
28
- # - sphinx_math_dollar
29
- # - sphinx.ext.mathjax
22
+ - style.css
@@ -104,6 +104,8 @@ def budget_chart(simulation: Simulation, data: dict) -> go.Figure:
104
104
  xaxis_title="",
105
105
  yaxis_title="Budgetary impact (£ billions)",
106
106
  yaxis_tickformat=",.0f",
107
+ uniformtext_minsize=12,
108
+ uniformtext_mode="hide",
107
109
  )
108
110
 
109
- return format_fig(fig)
111
+ return format_fig(fig, simulation.country)
@@ -0,0 +1,46 @@
1
+ from policyengine import Simulation
2
+ import pandas as pd
3
+ from policyengine.utils.huggingface import download
4
+ import plotly.express as px
5
+ from policyengine.utils.charts import *
6
+ from policyengine.utils.constituency_maps import plot_hex_map
7
+ from typing import Callable
8
+ from policyengine_core import Microsimulation
9
+ from microdf import MicroSeries
10
+
11
+
12
+ def parliamentary_constituencies(
13
+ simulation: Simulation,
14
+ chart: bool = False,
15
+ metric: Callable[[Microsimulation], MicroSeries] = None,
16
+ comparator: bool = None,
17
+ ) -> dict:
18
+ if not simulation.options.get("include_constituencies"):
19
+ return {}
20
+
21
+ kwargs = {}
22
+ if metric is not None:
23
+ kwargs["metric"] = metric
24
+
25
+ if comparator is None:
26
+ comparator = lambda x, y: (y / x) - 1
27
+
28
+ constituency_baseline = simulation.calculate(
29
+ "macro/baseline/gov/local_areas/parliamentary_constituencies", **kwargs
30
+ )
31
+ constituency_reform = simulation.calculate(
32
+ "macro/reform/gov/local_areas/parliamentary_constituencies", **kwargs
33
+ )
34
+
35
+ result = {}
36
+
37
+ for constituency in constituency_baseline:
38
+ result[constituency] = comparator(
39
+ constituency_baseline[constituency],
40
+ constituency_reform[constituency],
41
+ )
42
+
43
+ if chart:
44
+ return plot_hex_map(result)
45
+
46
+ return result
@@ -0,0 +1,85 @@
1
+ from policyengine import Simulation
2
+ from policyengine.utils.huggingface import download
3
+ import h5py
4
+ from microdf import MicroSeries
5
+ import pandas as pd
6
+ from typing import Callable
7
+ from policyengine_core import Microsimulation
8
+ from policyengine.utils.constituency_maps import plot_hex_map
9
+
10
+ DEFAULT_VARIABLES = [
11
+ "household_net_income",
12
+ ]
13
+
14
+
15
+ def parliamentary_constituencies(
16
+ simulation: Simulation,
17
+ metric: Callable[[Microsimulation], MicroSeries] = None,
18
+ chart: bool = False,
19
+ ) -> dict:
20
+ """Calculate the impact of the reform on parliamentary constituencies.
21
+
22
+ Args:
23
+ simulation (Simulation): The simulation for which the impact is to be calculated.
24
+ custom_function (Callable[[Microsimulation], [float]]): A custom function to calculate the impact. This must be called on a Microsimulation and return a float (we will call it for each constituency weight set).
25
+
26
+ """
27
+ if not simulation.options.get("include_constituencies"):
28
+ return {}
29
+
30
+ if metric is None:
31
+ metric = lambda sim: sim.calculate("household_net_income").median()
32
+ weights_file_path = download(
33
+ repo="policyengine/policyengine-uk-data",
34
+ repo_filename="parliamentary_constituency_weights.h5",
35
+ local_folder=None,
36
+ version=None,
37
+ )
38
+ constituency_names_file_path = download(
39
+ repo="policyengine/policyengine-uk-data",
40
+ repo_filename="constituencies_2024.csv",
41
+ local_folder=None,
42
+ version=None,
43
+ )
44
+ constituency_names = pd.read_csv(constituency_names_file_path)
45
+
46
+ with h5py.File(weights_file_path, "r") as f:
47
+ weights = f[str(simulation.time_period)][...]
48
+
49
+ result = {}
50
+
51
+ sim = simulation.selected
52
+ original_hh_weight = sim.calculate("household_weight").values
53
+
54
+ for constituency_id in range(weights.shape[0]):
55
+ sim.set_input(
56
+ "household_weight",
57
+ sim.default_calculation_period,
58
+ weights[constituency_id],
59
+ )
60
+ sim.get_holder("person_weight").delete_arrays(
61
+ sim.default_calculation_period
62
+ )
63
+ sim.get_holder("benunit_weight").delete_arrays(
64
+ sim.default_calculation_period
65
+ )
66
+ calculation_result = metric(simulation.selected)
67
+ code = constituency_names.code.iloc[constituency_id]
68
+ result[constituency_names.set_index("code").loc[code]["name"]] = (
69
+ calculation_result
70
+ )
71
+
72
+ sim.get_holder("person_weight").delete_arrays(
73
+ sim.default_calculation_period
74
+ )
75
+ sim.get_holder("benunit_weight").delete_arrays(
76
+ sim.default_calculation_period
77
+ )
78
+ sim.set_input(
79
+ "household_weight", sim.default_calculation_period, original_hh_weight
80
+ )
81
+
82
+ if chart:
83
+ return plot_hex_map(result)
84
+
85
+ return result
@@ -29,7 +29,7 @@ def income_distribution_chart(simulation: Simulation) -> go.Figure:
29
29
  color_discrete_sequence=[BLUE],
30
30
  )
31
31
  fig.update_layout(
32
- title="Number of household by net income band",
32
+ title="Number of households by net income band",
33
33
  xaxis_title="Household net income",
34
34
  yaxis_title="Number of households",
35
35
  )
@@ -39,6 +39,8 @@ class Simulation:
39
39
  """The tax-benefit simulation for the baseline scenario."""
40
40
  reformed: CountrySimulation = None
41
41
  """The tax-benefit simulation for the reformed scenario."""
42
+ selected: CountryMicrosimulation = None
43
+ """The selected simulation for the current calculation."""
42
44
  verbose: bool = False
43
45
  """Whether to print out progress messages."""
44
46
 
@@ -74,6 +76,11 @@ class Simulation:
74
76
  elif isinstance(reform, int):
75
77
  reform = Reform.from_api(reform, country_id=country)
76
78
 
79
+ if isinstance(baseline, dict):
80
+ baseline = Reform.from_dict(baseline, country_id=country)
81
+ elif isinstance(baseline, int):
82
+ baseline = Reform.from_api(baseline, country_id=country)
83
+
77
84
  self.baseline = baseline
78
85
  self.reform = reform
79
86
 
@@ -84,6 +91,10 @@ class Simulation:
84
91
  self._initialise_simulations()
85
92
 
86
93
  def _set_dataset(self, dataset: str):
94
+ if isinstance(dataset, dict):
95
+ self.data = dataset
96
+ return
97
+
87
98
  if dataset in DATASETS[self.country]:
88
99
  self.data = DATASETS[self.country][dataset]
89
100
  elif dataset is None:
@@ -0,0 +1,91 @@
1
+ import pandas as pd
2
+ import plotly.express as px
3
+ from policyengine import Simulation
4
+ import pandas as pd
5
+ from policyengine.utils.huggingface import download
6
+ import plotly.express as px
7
+ from policyengine.utils.charts import *
8
+
9
+
10
+ def plot_hex_map(value_by_constituency_name: dict) -> dict:
11
+ constituency_names_file_path = download(
12
+ repo="policyengine/policyengine-uk-data",
13
+ repo_filename="constituencies_2024.csv",
14
+ local_folder=None,
15
+ version=None,
16
+ )
17
+ constituency_names = pd.read_csv(constituency_names_file_path)
18
+
19
+ x_range = constituency_names["x"].max() - constituency_names["x"].min()
20
+ y_range = constituency_names["y"].max() - constituency_names["y"].min()
21
+ # Expand x range to preserve aspect ratio
22
+ expanded_lower_x_range = -(y_range - x_range) / 2
23
+ expanded_upper_x_range = x_range - expanded_lower_x_range
24
+ constituency_names.x = (
25
+ constituency_names.x - (constituency_names.y % 2 == 0) * 0.5
26
+ )
27
+ constituency_names["Value"] = (
28
+ pd.Series(
29
+ list(value_by_constituency_name.values()),
30
+ index=list(value_by_constituency_name.keys()),
31
+ )
32
+ .loc[constituency_names["name"]]
33
+ .values
34
+ )
35
+
36
+ fig = px.scatter(
37
+ constituency_names,
38
+ x="x",
39
+ y="y",
40
+ color="Value",
41
+ hover_name="name",
42
+ )
43
+
44
+ format_fig(fig)
45
+
46
+ # Show hexagons on scatter points
47
+
48
+ fig.update_traces(
49
+ marker=dict(
50
+ symbol="hexagon", line=dict(width=0, color="lightgray"), size=15
51
+ )
52
+ )
53
+ fig.update_layout(
54
+ xaxis_tickvals=[],
55
+ xaxis_title="",
56
+ yaxis_tickvals=[],
57
+ yaxis_title="",
58
+ xaxis_range=[expanded_lower_x_range, expanded_upper_x_range],
59
+ yaxis_range=[
60
+ constituency_names["y"].min(),
61
+ constituency_names["y"].max(),
62
+ ],
63
+ ).update_traces(marker_size=10).update_layout(
64
+ xaxis_range=[30, 85], yaxis_range=[-50, 2]
65
+ )
66
+
67
+ x_min = fig.data[0]["marker"]["color"].min()
68
+ x_max = fig.data[0]["marker"]["color"].max()
69
+ max_abs = max(abs(x_min), abs(x_max))
70
+
71
+ if x_min < 0:
72
+ colorscale = [
73
+ [0, DARK_GRAY],
74
+ [0.5, "lightgray"],
75
+ [1, BLUE],
76
+ ]
77
+ else:
78
+ colorscale = [
79
+ [0, "lightgray"],
80
+ [1, BLUE],
81
+ ]
82
+
83
+ fig.update_layout(
84
+ coloraxis=dict(
85
+ cmin=-max_abs if x_min < 0 else 0,
86
+ cmax=max_abs,
87
+ colorscale=colorscale,
88
+ )
89
+ )
90
+
91
+ return fig
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: policyengine
3
- Version: 2.2.0
3
+ Version: 2.2.2
4
4
  Summary: A package to conduct policy analysis using PolicyEngine tax-benefit models.
5
5
  Author-email: PolicyEngine <hello@policyengine.org>
6
6
  License: GNU AFFERO GENERAL PUBLIC LICENSE
@@ -56,4 +56,5 @@ policyengine/outputs/macro/single/household/inequality.py
56
56
  policyengine/outputs/macro/single/household/labor_supply.py
57
57
  policyengine/utils/__init__.py
58
58
  policyengine/utils/charts.py
59
+ policyengine/utils/constituency_maps.py
59
60
  policyengine/utils/huggingface.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "policyengine"
7
- version = "2.2.0"
7
+ version = "2.2.2"
8
8
  description = "A package to conduct policy analysis using PolicyEngine tax-benefit models."
9
9
  readme = "README.md"
10
10
  authors = [
@@ -1,167 +0,0 @@
1
- from policyengine import Simulation
2
- import pandas as pd
3
- from policyengine.utils.huggingface import download
4
- import plotly.express as px
5
- from policyengine.utils.charts import *
6
-
7
-
8
- def parliamentary_constituencies(
9
- simulation: Simulation,
10
- chart: bool = False,
11
- variable: str = None,
12
- aggregator: str = None,
13
- relative: bool = None,
14
- ) -> dict:
15
- if not simulation.options.get("include_constituencies"):
16
- return {}
17
-
18
- if chart:
19
- return heatmap(
20
- simulation=simulation,
21
- variable=variable,
22
- aggregator=aggregator,
23
- relative=relative,
24
- )
25
-
26
- constituency_baseline = simulation.calculate(
27
- "macro/baseline/gov/local_areas/parliamentary_constituencies"
28
- )
29
- constituency_reform = simulation.calculate(
30
- "macro/reform/gov/local_areas/parliamentary_constituencies"
31
- )
32
-
33
- result = {}
34
-
35
- for constituency in constituency_baseline:
36
- result[constituency] = {}
37
- for key in constituency_baseline[constituency]:
38
- result[constituency][key] = {
39
- "change": constituency_reform[constituency][key]
40
- - constituency_baseline[constituency][key],
41
- "baseline": constituency_baseline[constituency][key],
42
- "reform": constituency_reform[constituency][key],
43
- }
44
-
45
- return result
46
-
47
-
48
- def heatmap(
49
- simulation: Simulation,
50
- variable: str = None,
51
- aggregator: str = None,
52
- relative: bool = None,
53
- ) -> dict:
54
- if not simulation.options.get("include_constituencies"):
55
- return {}
56
-
57
- options = {}
58
-
59
- if variable is not None:
60
- options["variables"] = [variable]
61
- if aggregator is not None:
62
- options["aggregator"] = aggregator
63
-
64
- constituency_baseline = simulation.calculate(
65
- "macro/baseline/gov/local_areas/parliamentary_constituencies",
66
- **options,
67
- )
68
- constituency_reform = simulation.calculate(
69
- "macro/reform/gov/local_areas/parliamentary_constituencies", **options
70
- )
71
-
72
- result = {}
73
-
74
- constituency_names_file_path = download(
75
- repo="policyengine/policyengine-uk-data",
76
- repo_filename="constituencies_2024.csv",
77
- local_folder=None,
78
- version=None,
79
- )
80
- constituency_names = pd.read_csv(constituency_names_file_path)
81
-
82
- if variable is None:
83
- variable = "household_net_income"
84
- if relative is None:
85
- relative = True
86
-
87
- for constituency in constituency_baseline:
88
- if relative:
89
- result[constituency] = (
90
- constituency_reform[constituency][variable]
91
- / constituency_baseline[constituency][variable]
92
- - 1
93
- )
94
- else:
95
- result[constituency] = (
96
- constituency_reform[constituency][variable]
97
- - constituency_baseline[constituency][variable]
98
- )
99
-
100
- x_range = constituency_names["x"].max() - constituency_names["x"].min()
101
- y_range = constituency_names["y"].max() - constituency_names["y"].min()
102
- # Expand x range to preserve aspect ratio
103
- expanded_lower_x_range = -(y_range - x_range) / 2
104
- expanded_upper_x_range = x_range - expanded_lower_x_range
105
- constituency_names.x = (
106
- constituency_names.x - (constituency_names.y % 2 == 0) * 0.5
107
- )
108
- constituency_names["Relative change"] = (
109
- pd.Series(list(result.values()), index=list(result.keys()))
110
- .loc[constituency_names["name"]]
111
- .values
112
- )
113
-
114
- label = simulation.baseline.tax_benefit_system.variables[variable].label
115
-
116
- fig = px.scatter(
117
- constituency_names,
118
- x="x",
119
- y="y",
120
- color="Relative change",
121
- hover_name="name",
122
- title=f"{'Relative change' if relative else 'Change'} in {label} by parliamentary constituency",
123
- )
124
-
125
- format_fig(fig)
126
-
127
- # Show hexagons on scatter points
128
-
129
- fig.update_traces(
130
- marker=dict(
131
- symbol="hexagon", line=dict(width=0, color="lightgray"), size=15
132
- )
133
- )
134
- fig.update_layout(
135
- xaxis_tickvals=[],
136
- xaxis_title="",
137
- yaxis_tickvals=[],
138
- yaxis_title="",
139
- xaxis_range=[expanded_lower_x_range, expanded_upper_x_range],
140
- yaxis_range=[
141
- constituency_names["y"].min(),
142
- constituency_names["y"].max(),
143
- ],
144
- ).update_traces(marker_size=10).update_layout(
145
- xaxis_range=[30, 85], yaxis_range=[-50, 2]
146
- )
147
-
148
- x_min = fig.data[0]["marker"]["color"].min()
149
- x_max = fig.data[0]["marker"]["color"].max()
150
- max_abs = max(abs(x_min), abs(x_max))
151
-
152
- fig.update_layout(
153
- coloraxis=dict(
154
- cmin=-max_abs,
155
- cmax=max_abs,
156
- colorscale=[
157
- [0, DARK_GRAY],
158
- [0.5, "lightgray"],
159
- [1, BLUE],
160
- ],
161
- colorbar=dict(
162
- tickformat=".0%" if relative else ",.0f",
163
- ),
164
- )
165
- )
166
-
167
- return fig
@@ -1,47 +0,0 @@
1
- from policyengine import Simulation
2
- from policyengine.utils.huggingface import download
3
- import h5py
4
- from microdf import MicroDataFrame
5
- import pandas as pd
6
-
7
- DEFAULT_VARIABLES = [
8
- "household_net_income",
9
- ]
10
-
11
-
12
- def parliamentary_constituencies(
13
- simulation: Simulation,
14
- variables: list = DEFAULT_VARIABLES,
15
- aggregator: str = "sum",
16
- ) -> dict:
17
- if not simulation.options.get("include_constituencies"):
18
- return {}
19
- weights_file_path = download(
20
- repo="policyengine/policyengine-uk-data",
21
- repo_filename="parliamentary_constituency_weights.h5",
22
- local_folder=None,
23
- version=None,
24
- )
25
- constituency_names_file_path = download(
26
- repo="policyengine/policyengine-uk-data",
27
- repo_filename="constituencies_2024.csv",
28
- local_folder=None,
29
- version=None,
30
- )
31
- constituency_names = pd.read_csv(constituency_names_file_path)
32
-
33
- with h5py.File(weights_file_path, "r") as f:
34
- weights = f[str(simulation.time_period)][...]
35
-
36
- sim_df = simulation.selected.calculate_dataframe(variables)
37
-
38
- result = {}
39
-
40
- for constituency_id in range(weights.shape[0]):
41
- weighted_df = MicroDataFrame(sim_df, weights=weights[constituency_id])
42
- code = constituency_names.code.iloc[constituency_id]
43
- result[constituency_names.set_index("code").loc[code]["name"]] = (
44
- getattr(weighted_df, aggregator)().to_dict()
45
- )
46
-
47
- return result
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes