Skip to content

Commit 53d9251

Browse files
Fix parliamentary constituency impacts
1 parent e365b01 commit 53d9251

File tree

1 file changed

+165
-0
lines changed

1 file changed

+165
-0
lines changed
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
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+
49+
def heatmap(
50+
simulation: Simulation,
51+
) -> dict:
52+
if not simulation.options.get("include_constituencies"):
53+
return {}
54+
55+
options = {}
56+
57+
if variable is not None:
58+
options["variables"] = [variable]
59+
if aggregator is not None:
60+
options["aggregator"] = aggregator
61+
62+
constituency_baseline = simulation.calculate(
63+
"macro/baseline/gov/local_areas/parliamentary_constituencies",
64+
**options,
65+
)
66+
constituency_reform = simulation.calculate(
67+
"macro/reform/gov/local_areas/parliamentary_constituencies", **options
68+
)
69+
70+
result = {}
71+
72+
constituency_names_file_path = download(
73+
repo="policyengine/policyengine-uk-data",
74+
repo_filename="constituencies_2024.csv",
75+
local_folder=None,
76+
version=None,
77+
)
78+
constituency_names = pd.read_csv(constituency_names_file_path)
79+
80+
if variable is None:
81+
variable = "household_net_income"
82+
if relative is None:
83+
relative = True
84+
85+
for constituency in constituency_baseline:
86+
if relative:
87+
result[constituency] = (
88+
constituency_reform[constituency][variable]
89+
/ constituency_baseline[constituency][variable]
90+
- 1
91+
)
92+
else:
93+
result[constituency] = (
94+
constituency_reform[constituency][variable]
95+
- constituency_baseline[constituency][variable]
96+
)
97+
98+
x_range = constituency_names["x"].max() - constituency_names["x"].min()
99+
y_range = constituency_names["y"].max() - constituency_names["y"].min()
100+
# Expand x range to preserve aspect ratio
101+
expanded_lower_x_range = -(y_range - x_range) / 2
102+
expanded_upper_x_range = x_range - expanded_lower_x_range
103+
constituency_names.x = (
104+
constituency_names.x - (constituency_names.y % 2 == 0) * 0.5
105+
)
106+
constituency_names["Relative change"] = (
107+
pd.Series(list(result.values()), index=list(result.keys()))
108+
.loc[constituency_names["name"]]
109+
.values
110+
)
111+
112+
label = simulation.baseline.tax_benefit_system.variables[variable].label
113+
114+
fig = px.scatter(
115+
constituency_names,
116+
x="x",
117+
y="y",
118+
color="Relative change",
119+
hover_name="name",
120+
title=f"{'Relative change' if relative else 'Change'} in {label} by parliamentary constituency",
121+
)
122+
123+
format_fig(fig)
124+
125+
# Show hexagons on scatter points
126+
127+
fig.update_traces(
128+
marker=dict(
129+
symbol="hexagon", line=dict(width=0, color="lightgray"), size=15
130+
)
131+
)
132+
fig.update_layout(
133+
xaxis_tickvals=[],
134+
xaxis_title="",
135+
yaxis_tickvals=[],
136+
yaxis_title="",
137+
xaxis_range=[expanded_lower_x_range, expanded_upper_x_range],
138+
yaxis_range=[
139+
constituency_names["y"].min(),
140+
constituency_names["y"].max(),
141+
],
142+
).update_traces(marker_size=10).update_layout(
143+
xaxis_range=[30, 85], yaxis_range=[-50, 2]
144+
)
145+
146+
x_min = fig.data[0]["marker"]["color"].min()
147+
x_max = fig.data[0]["marker"]["color"].max()
148+
max_abs = max(abs(x_min), abs(x_max))
149+
150+
fig.update_layout(
151+
coloraxis=dict(
152+
cmin=-max_abs,
153+
cmax=max_abs,
154+
colorscale=[
155+
[0, DARK_GRAY],
156+
[0.5, "lightgray"],
157+
[1, BLUE],
158+
],
159+
colorbar=dict(
160+
tickformat=".0%" if relative else ",.0f",
161+
),
162+
)
163+
)
164+
165+
return fig

0 commit comments

Comments
 (0)