|
3 | 3 | from policyengine.utils.huggingface import download
|
4 | 4 | import plotly.express as px
|
5 | 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 |
6 | 10 |
|
7 | 11 |
|
8 | 12 | def parliamentary_constituencies(
|
9 | 13 | simulation: Simulation,
|
10 | 14 | chart: bool = False,
|
11 |
| - variable: str = None, |
12 |
| - aggregator: str = None, |
13 |
| - relative: bool = None, |
| 15 | + metric: Callable[[Microsimulation], MicroSeries] = None, |
| 16 | + comparator: bool = None, |
14 | 17 | ) -> dict:
|
15 | 18 | if not simulation.options.get("include_constituencies"):
|
16 | 19 | return {}
|
17 | 20 |
|
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 = {} |
| 21 | + kwargs = {} |
| 22 | + if metric is not None: |
| 23 | + kwargs["metric"] = metric |
58 | 24 |
|
59 |
| - if variable is not None: |
60 |
| - options["variables"] = [variable] |
61 |
| - if aggregator is not None: |
62 |
| - options["aggregator"] = aggregator |
| 25 | + if comparator is None: |
| 26 | + comparator = lambda x, y: (y / x) - 1 |
63 | 27 |
|
64 | 28 | constituency_baseline = simulation.calculate(
|
65 |
| - "macro/baseline/gov/local_areas/parliamentary_constituencies", |
66 |
| - **options, |
| 29 | + "macro/baseline/gov/local_areas/parliamentary_constituencies", **kwargs |
67 | 30 | )
|
68 | 31 | constituency_reform = simulation.calculate(
|
69 |
| - "macro/reform/gov/local_areas/parliamentary_constituencies", **options |
| 32 | + "macro/reform/gov/local_areas/parliamentary_constituencies", **kwargs |
70 | 33 | )
|
71 | 34 |
|
72 | 35 | result = {}
|
73 | 36 |
|
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 | 37 | 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 |
| 38 | + result[constituency] = comparator( |
| 39 | + constituency_baseline[constituency], |
| 40 | + constituency_reform[constituency], |
132 | 41 | )
|
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 | 42 |
|
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 |
| - ) |
| 43 | + if chart: |
| 44 | + return plot_hex_map(result) |
166 | 45 |
|
167 |
| - return fig |
| 46 | + return result |
0 commit comments