policyengine 1.0.1__py3-none-any.whl
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.
- policyengine/__init__.py +1 -0
- policyengine/constants.py +20 -0
- policyengine/outputs/household/comparison/net_income_change.py +4 -0
- policyengine/outputs/household/single/net_income.py +2 -0
- policyengine/outputs/macro/comparison/budget.py +44 -0
- policyengine/outputs/macro/comparison/decile/income.py +51 -0
- policyengine/outputs/macro/comparison/decile/wealth.py +52 -0
- policyengine/outputs/macro/comparison/detailed_budget.py +28 -0
- policyengine/outputs/macro/comparison/inequality.py +34 -0
- policyengine/outputs/macro/comparison/labor_supply_response.py +109 -0
- policyengine/outputs/macro/comparison/local_areas/parliamentary_constituencies/data.py +27 -0
- policyengine/outputs/macro/comparison/local_areas/parliamentary_constituencies/heatmap.py +136 -0
- policyengine/outputs/macro/comparison/poverty/age.py +81 -0
- policyengine/outputs/macro/comparison/poverty/gender.py +65 -0
- policyengine/outputs/macro/comparison/poverty/race.py +55 -0
- policyengine/outputs/macro/comparison/winners/income_decile.py +71 -0
- policyengine/outputs/macro/comparison/winners/wealth_decile.py +71 -0
- policyengine/outputs/macro/single/gov/balance.py +18 -0
- policyengine/outputs/macro/single/gov/local_areas/parliamentary_constituencies.py +47 -0
- policyengine/outputs/macro/single/gov/programs.py +35 -0
- policyengine/outputs/macro/single/household/demographics.py +24 -0
- policyengine/outputs/macro/single/household/finance.py +65 -0
- policyengine/outputs/macro/single/household/inequality.py +32 -0
- policyengine/outputs/macro/single/household/labor_supply.py +62 -0
- policyengine/simulation.py +256 -0
- policyengine/utils/charts.py +125 -0
- policyengine/utils/huggingface.py +18 -0
- policyengine-1.0.1.dist-info/LICENSE +661 -0
- policyengine-1.0.1.dist-info/METADATA +686 -0
- policyengine-1.0.1.dist-info/RECORD +32 -0
- policyengine-1.0.1.dist-info/WHEEL +5 -0
- policyengine-1.0.1.dist-info/top_level.txt +1 -0
policyengine/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .simulation import Simulation
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Mainly simulation options and parameters."""
|
|
2
|
+
|
|
3
|
+
# Datasets
|
|
4
|
+
|
|
5
|
+
ENHANCED_FRS = "hf://policyengine/policyengine-uk-data/enhanced_frs_2022_23.h5"
|
|
6
|
+
FRS = "hf://policyengine/policyengine-uk-data/frs_2022_23.h5"
|
|
7
|
+
|
|
8
|
+
ENHANCED_CPS = "hf://policyengine/policyengine-us-data/enhanced_cps_2024.h5"
|
|
9
|
+
CPS = "hf://policyengine/policyengine-us-data/cps_2023.h5"
|
|
10
|
+
POOLED_CPS = "hf://policyengine/policyengine-us-data/pooled_3_year_cps_2023.h5"
|
|
11
|
+
|
|
12
|
+
DATASETS = {
|
|
13
|
+
"uk": {"frs": FRS, "enhanced_frs": ENHANCED_FRS},
|
|
14
|
+
"us": {"cps": CPS, "enhanced_cps": ENHANCED_CPS, "pooled_cps": POOLED_CPS},
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
DEFAULT_DATASETS = {
|
|
18
|
+
"uk": ENHANCED_FRS,
|
|
19
|
+
"us": CPS,
|
|
20
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from policyengine import Simulation
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def budget(simulation: Simulation):
|
|
5
|
+
"""Calculate the budgetary impact of the given simulation.
|
|
6
|
+
|
|
7
|
+
Args:
|
|
8
|
+
simulation (Simulation): The simulation for which the revenue impact is to be calculated.
|
|
9
|
+
|
|
10
|
+
Returns:
|
|
11
|
+
dict: A dictionary containing the budgetary impact details with the following keys:
|
|
12
|
+
- budgetary_impact (float): The overall budgetary impact.
|
|
13
|
+
- tax_revenue_impact (float): The impact on tax revenue.
|
|
14
|
+
- state_tax_revenue_impact (float): The impact on state tax revenue.
|
|
15
|
+
- benefit_spending_impact (float): The impact on benefit spending.
|
|
16
|
+
- households (int): The number of households.
|
|
17
|
+
- baseline_net_income (float): The total net income in the baseline scenario.
|
|
18
|
+
"""
|
|
19
|
+
baseline = simulation.calculate("macro/baseline")
|
|
20
|
+
reform = simulation.calculate("macro/reform")
|
|
21
|
+
|
|
22
|
+
tax_revenue_impact = (
|
|
23
|
+
reform["gov"]["balance"]["total_tax"]
|
|
24
|
+
- baseline["gov"]["balance"]["total_tax"]
|
|
25
|
+
)
|
|
26
|
+
state_tax_revenue_impact = (
|
|
27
|
+
reform["gov"]["balance"]["total_state_tax"]
|
|
28
|
+
- baseline["gov"]["balance"]["total_state_tax"]
|
|
29
|
+
)
|
|
30
|
+
benefit_spending_impact = (
|
|
31
|
+
reform["gov"]["balance"]["total_spending"]
|
|
32
|
+
- baseline["gov"]["balance"]["total_spending"]
|
|
33
|
+
)
|
|
34
|
+
budgetary_impact = tax_revenue_impact - benefit_spending_impact
|
|
35
|
+
households = sum(baseline["household"]["demographics"]["household_weight"])
|
|
36
|
+
baseline_net_income = baseline["household"]["finance"]["total_net_income"]
|
|
37
|
+
return dict(
|
|
38
|
+
budgetary_impact=budgetary_impact,
|
|
39
|
+
tax_revenue_impact=tax_revenue_impact,
|
|
40
|
+
state_tax_revenue_impact=state_tax_revenue_impact,
|
|
41
|
+
benefit_spending_impact=benefit_spending_impact,
|
|
42
|
+
households=households,
|
|
43
|
+
baseline_net_income=baseline_net_income,
|
|
44
|
+
)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from policyengine import Simulation
|
|
2
|
+
from microdf import MicroSeries
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def income(simulation: Simulation):
|
|
6
|
+
"""Calculate the impact of the reform on income deciles.
|
|
7
|
+
|
|
8
|
+
Args:
|
|
9
|
+
simulation (Simulation): The simulation for which the impact is to be calculated.
|
|
10
|
+
|
|
11
|
+
Returns:
|
|
12
|
+
dict: A dictionary containing the impact details with the following keys:
|
|
13
|
+
- relative (dict): A dictionary with keys representing deciles and values as relative income changes.
|
|
14
|
+
- average (dict): A dictionary with keys representing deciles and values as average income changes.
|
|
15
|
+
"""
|
|
16
|
+
baseline = simulation.calculate("macro/baseline")
|
|
17
|
+
reform = simulation.calculate("macro/reform")
|
|
18
|
+
|
|
19
|
+
baseline_income = MicroSeries(
|
|
20
|
+
baseline["household"]["finance"]["household_net_income"],
|
|
21
|
+
weights=baseline["household"]["demographics"]["household_weight"],
|
|
22
|
+
)
|
|
23
|
+
reform_income = MicroSeries(
|
|
24
|
+
reform["household"]["finance"]["household_net_income"],
|
|
25
|
+
weights=baseline_income.weights,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# Filter out negative decile values
|
|
29
|
+
decile = MicroSeries(
|
|
30
|
+
baseline["household"]["finance"]["household_income_decile"]
|
|
31
|
+
)
|
|
32
|
+
baseline_income_filtered = baseline_income[decile >= 0]
|
|
33
|
+
reform_income_filtered = reform_income[decile >= 0]
|
|
34
|
+
|
|
35
|
+
income_change = reform_income_filtered - baseline_income_filtered
|
|
36
|
+
rel_income_change_by_decile = (
|
|
37
|
+
income_change.groupby(decile).sum()
|
|
38
|
+
/ baseline_income_filtered.groupby(decile).sum()
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
avg_income_change_by_decile = (
|
|
42
|
+
income_change.groupby(decile).sum()
|
|
43
|
+
/ baseline_income_filtered.groupby(decile).count()
|
|
44
|
+
)
|
|
45
|
+
rel_decile_dict = rel_income_change_by_decile.to_dict()
|
|
46
|
+
avg_decile_dict = avg_income_change_by_decile.to_dict()
|
|
47
|
+
result = dict(
|
|
48
|
+
relative={int(k): v for k, v in rel_decile_dict.items()},
|
|
49
|
+
average={int(k): v for k, v in avg_decile_dict.items()},
|
|
50
|
+
)
|
|
51
|
+
return result
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from policyengine import Simulation
|
|
2
|
+
from microdf import MicroSeries
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def wealth(simulation: Simulation):
|
|
6
|
+
"""Calculate the impact of the reform on wealth deciles.
|
|
7
|
+
|
|
8
|
+
Args:
|
|
9
|
+
simulation (Simulation): The simulation for which the impact is to be calculated.
|
|
10
|
+
|
|
11
|
+
Returns:
|
|
12
|
+
dict: A dictionary containing the impact details with the following keys:
|
|
13
|
+
- relative (dict): A dictionary with keys representing deciles and values as relative income changes.
|
|
14
|
+
- average (dict): A dictionary with keys representing deciles and values as average income changes.
|
|
15
|
+
"""
|
|
16
|
+
if simulation.country != "uk":
|
|
17
|
+
return {}
|
|
18
|
+
|
|
19
|
+
baseline = simulation.calculate("macro/baseline")
|
|
20
|
+
reform = simulation.calculate("macro/reform")
|
|
21
|
+
|
|
22
|
+
baseline_income = MicroSeries(
|
|
23
|
+
baseline["household"]["finance"]["household_net_income"],
|
|
24
|
+
weights=baseline["household"]["demographics"]["household_weight"],
|
|
25
|
+
)
|
|
26
|
+
reform_income = MicroSeries(
|
|
27
|
+
reform["household"]["finance"]["household_net_income"],
|
|
28
|
+
weights=baseline_income.weights,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# Filter out negative decile values
|
|
32
|
+
decile = MicroSeries(baseline["household"]["finance"]["wealth_decile"])
|
|
33
|
+
baseline_income_filtered = baseline_income[decile >= 0]
|
|
34
|
+
reform_income_filtered = reform_income[decile >= 0]
|
|
35
|
+
|
|
36
|
+
income_change = reform_income_filtered - baseline_income_filtered
|
|
37
|
+
rel_income_change_by_decile = (
|
|
38
|
+
income_change.groupby(decile).sum()
|
|
39
|
+
/ baseline_income_filtered.groupby(decile).sum()
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
avg_income_change_by_decile = (
|
|
43
|
+
income_change.groupby(decile).sum()
|
|
44
|
+
/ baseline_income_filtered.groupby(decile).count()
|
|
45
|
+
)
|
|
46
|
+
rel_decile_dict = rel_income_change_by_decile.to_dict()
|
|
47
|
+
avg_decile_dict = avg_income_change_by_decile.to_dict()
|
|
48
|
+
result = dict(
|
|
49
|
+
relative={int(k): v for k, v in rel_decile_dict.items()},
|
|
50
|
+
average={int(k): v for k, v in avg_decile_dict.items()},
|
|
51
|
+
)
|
|
52
|
+
return result
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from policyengine import Simulation
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def detailed_budget(simulation: Simulation):
|
|
5
|
+
"""Calculate the detailed budgetary impact of the given simulation.
|
|
6
|
+
|
|
7
|
+
Args:
|
|
8
|
+
simulation (Simulation): The simulation for which the budgetary impact is to be calculated.
|
|
9
|
+
|
|
10
|
+
Returns:
|
|
11
|
+
dict: A dictionary containing the detailed budgetary impact for each program with the following keys:
|
|
12
|
+
- baseline (float): The baseline budgetary impact of the program.
|
|
13
|
+
- reform (float): The reform budgetary impact of the program.
|
|
14
|
+
- difference (float): The difference between the reform and baseline budgetary impacts.
|
|
15
|
+
"""
|
|
16
|
+
baseline = simulation.calculate("macro/baseline")
|
|
17
|
+
reform = simulation.calculate("macro/reform")
|
|
18
|
+
result = {}
|
|
19
|
+
if simulation.country == "uk":
|
|
20
|
+
for program in baseline["gov"]["programs"]:
|
|
21
|
+
# baseline[programs][program] = total budgetary impact of program
|
|
22
|
+
result[program] = dict(
|
|
23
|
+
baseline=baseline["gov"]["programs"][program],
|
|
24
|
+
reform=reform["gov"]["programs"][program],
|
|
25
|
+
difference=reform["gov"]["programs"][program]
|
|
26
|
+
- baseline["gov"]["programs"][program],
|
|
27
|
+
)
|
|
28
|
+
return result
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from policyengine import Simulation
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def inequality(simulation: Simulation):
|
|
5
|
+
"""Calculate the impact of the reform on inequality.
|
|
6
|
+
|
|
7
|
+
Args:
|
|
8
|
+
simulation (Simulation): The simulation for which the impact is to be calculated.
|
|
9
|
+
|
|
10
|
+
Returns:
|
|
11
|
+
dict: A dictionary containing the inequality impact details with the following keys:
|
|
12
|
+
- gini (dict): A dictionary with baseline and reform Gini coefficients.
|
|
13
|
+
- top_10_pct_share (dict): A dictionary with baseline and reform top 10% income share.
|
|
14
|
+
- top_1_pct_share (dict): A dictionary with baseline and reform top 1% income share.
|
|
15
|
+
"""
|
|
16
|
+
baseline = simulation.calculate("macro/baseline")["household"][
|
|
17
|
+
"inequality"
|
|
18
|
+
]
|
|
19
|
+
reform = simulation.calculate("macro/reform")["household"]["inequality"]
|
|
20
|
+
|
|
21
|
+
return dict(
|
|
22
|
+
gini=dict(
|
|
23
|
+
baseline=baseline["gini"],
|
|
24
|
+
reform=reform["gini"],
|
|
25
|
+
),
|
|
26
|
+
top_10_pct_share=dict(
|
|
27
|
+
baseline=baseline["top_10_percent_share"],
|
|
28
|
+
reform=reform["top_10_percent_share"],
|
|
29
|
+
),
|
|
30
|
+
top_1_pct_share=dict(
|
|
31
|
+
baseline=baseline["top_1_percent_share"],
|
|
32
|
+
reform=reform["top_1_percent_share"],
|
|
33
|
+
),
|
|
34
|
+
)
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
from policyengine import Simulation
|
|
2
|
+
from microdf import MicroSeries
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def labor_supply_response(simulation: Simulation) -> dict:
|
|
7
|
+
baseline = simulation.calculate("macro/baseline")
|
|
8
|
+
reform = simulation.calculate("macro/reform")
|
|
9
|
+
|
|
10
|
+
substitution_lsr = (
|
|
11
|
+
reform["household"]["labor_supply"]["substitution_lsr"]
|
|
12
|
+
- baseline["household"]["labor_supply"]["substitution_lsr"]
|
|
13
|
+
)
|
|
14
|
+
income_lsr = (
|
|
15
|
+
reform["household"]["labor_supply"]["income_lsr"]
|
|
16
|
+
- baseline["household"]["labor_supply"]["income_lsr"]
|
|
17
|
+
)
|
|
18
|
+
total_change = substitution_lsr + income_lsr
|
|
19
|
+
revenue_change = (
|
|
20
|
+
reform["household"]["labor_supply"]["budgetary_impact_lsr"]
|
|
21
|
+
- baseline["household"]["labor_supply"]["budgetary_impact_lsr"]
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
substitution_lsr_hh = np.array(
|
|
25
|
+
reform["household"]["labor_supply"]["substitution_lsr_hh"]
|
|
26
|
+
) - np.array(baseline["household"]["labor_supply"]["substitution_lsr_hh"])
|
|
27
|
+
income_lsr_hh = np.array(
|
|
28
|
+
reform["household"]["labor_supply"]["income_lsr_hh"]
|
|
29
|
+
) - np.array(baseline["household"]["labor_supply"]["income_lsr_hh"])
|
|
30
|
+
decile = np.array(
|
|
31
|
+
baseline["household"]["finance"]["household_income_decile"]
|
|
32
|
+
)
|
|
33
|
+
household_weight = baseline["household"]["demographics"][
|
|
34
|
+
"household_weight"
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
total_lsr_hh = substitution_lsr_hh + income_lsr_hh
|
|
38
|
+
|
|
39
|
+
emp_income = MicroSeries(
|
|
40
|
+
baseline["household"]["finance"]["employment_income_hh"],
|
|
41
|
+
weights=household_weight,
|
|
42
|
+
)
|
|
43
|
+
self_emp_income = MicroSeries(
|
|
44
|
+
baseline["household"]["finance"]["self_employment_income_hh"],
|
|
45
|
+
weights=household_weight,
|
|
46
|
+
)
|
|
47
|
+
earnings = emp_income + self_emp_income
|
|
48
|
+
original_earnings = earnings - total_lsr_hh
|
|
49
|
+
substitution_lsr_hh = MicroSeries(
|
|
50
|
+
substitution_lsr_hh, weights=household_weight
|
|
51
|
+
)
|
|
52
|
+
income_lsr_hh = MicroSeries(income_lsr_hh, weights=household_weight)
|
|
53
|
+
|
|
54
|
+
decile_avg = dict(
|
|
55
|
+
income=income_lsr_hh.groupby(decile).mean().to_dict(),
|
|
56
|
+
substitution=substitution_lsr_hh.groupby(decile).mean().to_dict(),
|
|
57
|
+
)
|
|
58
|
+
decile_rel = dict(
|
|
59
|
+
income=(
|
|
60
|
+
income_lsr_hh.groupby(decile).sum()
|
|
61
|
+
/ original_earnings.groupby(decile).sum()
|
|
62
|
+
).to_dict(),
|
|
63
|
+
substitution=(
|
|
64
|
+
substitution_lsr_hh.groupby(decile).sum()
|
|
65
|
+
/ original_earnings.groupby(decile).sum()
|
|
66
|
+
).to_dict(),
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
relative_lsr = dict(
|
|
70
|
+
income=(income_lsr_hh.sum() / original_earnings.sum()),
|
|
71
|
+
substitution=(substitution_lsr_hh.sum() / original_earnings.sum()),
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
decile_rel["income"] = {
|
|
75
|
+
int(k): v for k, v in decile_rel["income"].items() if k > 0
|
|
76
|
+
}
|
|
77
|
+
decile_rel["substitution"] = {
|
|
78
|
+
int(k): v for k, v in decile_rel["substitution"].items() if k > 0
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
hours = dict(
|
|
82
|
+
baseline=baseline["household"]["labor_supply"]["weekly_hours"],
|
|
83
|
+
reform=reform["household"]["labor_supply"]["weekly_hours"],
|
|
84
|
+
change=reform["household"]["labor_supply"]["weekly_hours"]
|
|
85
|
+
- baseline["household"]["labor_supply"]["weekly_hours"],
|
|
86
|
+
income_effect=reform["household"]["labor_supply"][
|
|
87
|
+
"weekly_hours_income_effect"
|
|
88
|
+
]
|
|
89
|
+
- baseline["household"]["labor_supply"]["weekly_hours_income_effect"],
|
|
90
|
+
substitution_effect=reform["household"]["labor_supply"][
|
|
91
|
+
"weekly_hours_substitution_effect"
|
|
92
|
+
]
|
|
93
|
+
- baseline["household"]["labor_supply"][
|
|
94
|
+
"weekly_hours_substitution_effect"
|
|
95
|
+
],
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
return dict(
|
|
99
|
+
substitution_lsr=substitution_lsr,
|
|
100
|
+
income_lsr=income_lsr,
|
|
101
|
+
relative_lsr=relative_lsr,
|
|
102
|
+
total_change=total_change,
|
|
103
|
+
revenue_change=revenue_change,
|
|
104
|
+
decile=dict(
|
|
105
|
+
average=decile_avg,
|
|
106
|
+
relative=decile_rel,
|
|
107
|
+
),
|
|
108
|
+
hours=hours,
|
|
109
|
+
)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from policyengine import Simulation
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def data(simulation: Simulation) -> dict:
|
|
5
|
+
if not simulation.options.get("include_constituencies"):
|
|
6
|
+
return {}
|
|
7
|
+
|
|
8
|
+
constituency_baseline = simulation.calculate(
|
|
9
|
+
"macro/baseline/gov/local_areas/parliamentary_constituencies"
|
|
10
|
+
)
|
|
11
|
+
constituency_reform = simulation.calculate(
|
|
12
|
+
"macro/reform/gov/local_areas/parliamentary_constituencies"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
result = {}
|
|
16
|
+
|
|
17
|
+
for constituency in constituency_baseline:
|
|
18
|
+
result[constituency] = {}
|
|
19
|
+
for key in constituency_baseline[constituency]:
|
|
20
|
+
result[constituency][key] = {
|
|
21
|
+
"change": constituency_reform[constituency][key]
|
|
22
|
+
- constituency_baseline[constituency][key],
|
|
23
|
+
"baseline": constituency_baseline[constituency][key],
|
|
24
|
+
"reform": constituency_reform[constituency][key],
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return result
|
|
@@ -0,0 +1,136 @@
|
|
|
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 heatmap(
|
|
9
|
+
simulation: Simulation,
|
|
10
|
+
variable: str = None,
|
|
11
|
+
aggregator: str = None,
|
|
12
|
+
relative: bool = None,
|
|
13
|
+
) -> dict:
|
|
14
|
+
if not simulation.options.get("include_constituencies"):
|
|
15
|
+
return {}
|
|
16
|
+
|
|
17
|
+
options = {}
|
|
18
|
+
|
|
19
|
+
if variable is not None:
|
|
20
|
+
options["variables"] = [variable]
|
|
21
|
+
if aggregator is not None:
|
|
22
|
+
options["aggregator"] = aggregator
|
|
23
|
+
|
|
24
|
+
constituency_baseline = simulation.calculate(
|
|
25
|
+
"macro/baseline/gov/local_areas/parliamentary_constituencies",
|
|
26
|
+
**options,
|
|
27
|
+
)
|
|
28
|
+
constituency_reform = simulation.calculate(
|
|
29
|
+
"macro/reform/gov/local_areas/parliamentary_constituencies", **options
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
result = {}
|
|
33
|
+
|
|
34
|
+
constituency_names_file_path = download(
|
|
35
|
+
repo="policyengine/policyengine-uk-data",
|
|
36
|
+
repo_filename="constituencies_2024.csv",
|
|
37
|
+
local_folder=None,
|
|
38
|
+
version=None,
|
|
39
|
+
)
|
|
40
|
+
constituency_names = pd.read_csv(constituency_names_file_path)
|
|
41
|
+
hex_map_locations = pd.read_csv(
|
|
42
|
+
"/Users/nikhilwoodruff/uk-local-area-calibration/policyengine_uk_local_areas/hex_map/hex_map_2024.csv"
|
|
43
|
+
).set_index("code")
|
|
44
|
+
|
|
45
|
+
if variable is None:
|
|
46
|
+
variable = "household_net_income"
|
|
47
|
+
if relative is None:
|
|
48
|
+
relative = True
|
|
49
|
+
|
|
50
|
+
for constituency in constituency_baseline:
|
|
51
|
+
if relative:
|
|
52
|
+
result[constituency] = (
|
|
53
|
+
constituency_reform[constituency][variable]
|
|
54
|
+
/ constituency_baseline[constituency][variable]
|
|
55
|
+
- 1
|
|
56
|
+
)
|
|
57
|
+
else:
|
|
58
|
+
result[constituency] = (
|
|
59
|
+
constituency_reform[constituency][variable]
|
|
60
|
+
- constituency_baseline[constituency][variable]
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
constituency_names["x"] = hex_map_locations.loc[
|
|
64
|
+
constituency_names["code"]
|
|
65
|
+
]["x"].values
|
|
66
|
+
constituency_names["y"] = hex_map_locations.loc[
|
|
67
|
+
constituency_names["code"]
|
|
68
|
+
]["y"].values
|
|
69
|
+
x_range = constituency_names["x"].max() - constituency_names["x"].min()
|
|
70
|
+
y_range = constituency_names["y"].max() - constituency_names["y"].min()
|
|
71
|
+
# Expand x range to preserve aspect ratio
|
|
72
|
+
expanded_lower_x_range = -(y_range - x_range) / 2
|
|
73
|
+
expanded_upper_x_range = x_range - expanded_lower_x_range
|
|
74
|
+
constituency_names.x = (
|
|
75
|
+
constituency_names.x - (constituency_names.y % 2 == 0) * 0.5
|
|
76
|
+
)
|
|
77
|
+
constituency_names["Relative change"] = (
|
|
78
|
+
pd.Series(list(result.values()), index=list(result.keys()))
|
|
79
|
+
.loc[constituency_names["name"]]
|
|
80
|
+
.values
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
label = simulation.baseline.tax_benefit_system.variables[variable].label
|
|
84
|
+
|
|
85
|
+
fig = px.scatter(
|
|
86
|
+
constituency_names,
|
|
87
|
+
x="x",
|
|
88
|
+
y="y",
|
|
89
|
+
color="Relative change",
|
|
90
|
+
hover_name="name",
|
|
91
|
+
title=f"{'Relative change' if relative else 'Change'} in {label} by parliamentary constituency",
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
format_fig(fig)
|
|
95
|
+
|
|
96
|
+
# Show hexagons on scatter points
|
|
97
|
+
|
|
98
|
+
fig.update_traces(
|
|
99
|
+
marker=dict(
|
|
100
|
+
symbol="hexagon", line=dict(width=0, color="lightgray"), size=15
|
|
101
|
+
)
|
|
102
|
+
)
|
|
103
|
+
fig.update_layout(
|
|
104
|
+
xaxis_tickvals=[],
|
|
105
|
+
xaxis_title="",
|
|
106
|
+
yaxis_tickvals=[],
|
|
107
|
+
yaxis_title="",
|
|
108
|
+
xaxis_range=[expanded_lower_x_range, expanded_upper_x_range],
|
|
109
|
+
yaxis_range=[
|
|
110
|
+
constituency_names["y"].min(),
|
|
111
|
+
constituency_names["y"].max(),
|
|
112
|
+
],
|
|
113
|
+
).update_traces(marker_size=10).update_layout(
|
|
114
|
+
xaxis_range=[30, 85], yaxis_range=[-50, 2]
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
x_min = fig.data[0]["marker"]["color"].min()
|
|
118
|
+
x_max = fig.data[0]["marker"]["color"].max()
|
|
119
|
+
max_abs = max(abs(x_min), abs(x_max))
|
|
120
|
+
|
|
121
|
+
fig.update_layout(
|
|
122
|
+
coloraxis=dict(
|
|
123
|
+
cmin=-max_abs,
|
|
124
|
+
cmax=max_abs,
|
|
125
|
+
colorscale=[
|
|
126
|
+
[0, DARK_GRAY],
|
|
127
|
+
[0.5, "lightgray"],
|
|
128
|
+
[1, BLUE],
|
|
129
|
+
],
|
|
130
|
+
colorbar=dict(
|
|
131
|
+
tickformat=".0%" if relative else ",.0f",
|
|
132
|
+
),
|
|
133
|
+
)
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
return fig
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from policyengine import Simulation
|
|
2
|
+
from microdf import MicroSeries
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def age(simulation: Simulation):
|
|
6
|
+
"""Calculate the impact of the reform on poverty by age.
|
|
7
|
+
|
|
8
|
+
Args:
|
|
9
|
+
simulation (Simulation): The simulation for which the impact is to be calculated.
|
|
10
|
+
|
|
11
|
+
Returns:
|
|
12
|
+
dict: A dictionary containing the poverty and deep poverty impact details with the following keys:
|
|
13
|
+
- poverty (dict): A dictionary with keys representing age groups and values as dictionaries with baseline and reform poverty rates.
|
|
14
|
+
- deep_poverty (dict): A dictionary with keys representing age groups and values as dictionaries with baseline and reform deep poverty rates.
|
|
15
|
+
"""
|
|
16
|
+
baseline = simulation.calculate("macro/baseline")["household"]["finance"]
|
|
17
|
+
reform = simulation.calculate("macro/reform")["household"]["finance"]
|
|
18
|
+
baseline_demographics = simulation.calculate("macro/baseline")[
|
|
19
|
+
"household"
|
|
20
|
+
]["demographics"]
|
|
21
|
+
|
|
22
|
+
baseline_poverty = MicroSeries(
|
|
23
|
+
baseline["person_in_poverty"],
|
|
24
|
+
weights=baseline_demographics["person_weight"],
|
|
25
|
+
)
|
|
26
|
+
baseline_deep_poverty = MicroSeries(
|
|
27
|
+
baseline["person_in_deep_poverty"],
|
|
28
|
+
weights=baseline_demographics["person_weight"],
|
|
29
|
+
)
|
|
30
|
+
reform_poverty = MicroSeries(
|
|
31
|
+
reform["person_in_poverty"], weights=baseline_poverty.weights
|
|
32
|
+
)
|
|
33
|
+
reform_deep_poverty = MicroSeries(
|
|
34
|
+
reform["person_in_deep_poverty"], weights=baseline_poverty.weights
|
|
35
|
+
)
|
|
36
|
+
age = MicroSeries(baseline_demographics["age"])
|
|
37
|
+
|
|
38
|
+
poverty = dict(
|
|
39
|
+
child=dict(
|
|
40
|
+
baseline=float(baseline_poverty[age < 18].mean()),
|
|
41
|
+
reform=float(reform_poverty[age < 18].mean()),
|
|
42
|
+
),
|
|
43
|
+
adult=dict(
|
|
44
|
+
baseline=float(baseline_poverty[(age >= 18) & (age < 65)].mean()),
|
|
45
|
+
reform=float(reform_poverty[(age >= 18) & (age < 65)].mean()),
|
|
46
|
+
),
|
|
47
|
+
senior=dict(
|
|
48
|
+
baseline=float(baseline_poverty[age >= 65].mean()),
|
|
49
|
+
reform=float(reform_poverty[age >= 65].mean()),
|
|
50
|
+
),
|
|
51
|
+
all=dict(
|
|
52
|
+
baseline=float(baseline_poverty.mean()),
|
|
53
|
+
reform=float(reform_poverty.mean()),
|
|
54
|
+
),
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
deep_poverty = dict(
|
|
58
|
+
child=dict(
|
|
59
|
+
baseline=float(baseline_deep_poverty[age < 18].mean()),
|
|
60
|
+
reform=float(reform_deep_poverty[age < 18].mean()),
|
|
61
|
+
),
|
|
62
|
+
adult=dict(
|
|
63
|
+
baseline=float(
|
|
64
|
+
baseline_deep_poverty[(age >= 18) & (age < 65)].mean()
|
|
65
|
+
),
|
|
66
|
+
reform=float(reform_deep_poverty[(age >= 18) & (age < 65)].mean()),
|
|
67
|
+
),
|
|
68
|
+
senior=dict(
|
|
69
|
+
baseline=float(baseline_deep_poverty[age >= 65].mean()),
|
|
70
|
+
reform=float(reform_deep_poverty[age >= 65].mean()),
|
|
71
|
+
),
|
|
72
|
+
all=dict(
|
|
73
|
+
baseline=float(baseline_deep_poverty.mean()),
|
|
74
|
+
reform=float(reform_deep_poverty.mean()),
|
|
75
|
+
),
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
return dict(
|
|
79
|
+
poverty=poverty,
|
|
80
|
+
deep_poverty=deep_poverty,
|
|
81
|
+
)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from policyengine import Simulation
|
|
2
|
+
from microdf import MicroSeries
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def gender(simulation: Simulation):
|
|
6
|
+
"""Calculate the impact of the reform on poverty by gender.
|
|
7
|
+
|
|
8
|
+
Args:
|
|
9
|
+
simulation (Simulation): The simulation for which the impact is to be calculated.
|
|
10
|
+
|
|
11
|
+
Returns:
|
|
12
|
+
dict: A dictionary containing the poverty and deep poverty impact details with the following keys:
|
|
13
|
+
- poverty (dict): A dictionary with keys representing genders and values as dictionaries with baseline and reform poverty rates.
|
|
14
|
+
- deep_poverty (dict): A dictionary with keys representing genders and values as dictionaries with baseline and reform deep poverty rates.
|
|
15
|
+
"""
|
|
16
|
+
baseline = simulation.calculate("macro/baseline")["household"]["finance"]
|
|
17
|
+
reform = simulation.calculate("macro/reform")["household"]["finance"]
|
|
18
|
+
baseline_demographics = simulation.calculate("macro/baseline")[
|
|
19
|
+
"household"
|
|
20
|
+
]["demographics"]
|
|
21
|
+
|
|
22
|
+
if baseline_demographics["is_male"] is None:
|
|
23
|
+
return {}
|
|
24
|
+
baseline_poverty = MicroSeries(
|
|
25
|
+
baseline["person_in_poverty"],
|
|
26
|
+
weights=baseline_demographics["person_weight"],
|
|
27
|
+
)
|
|
28
|
+
baseline_deep_poverty = MicroSeries(
|
|
29
|
+
baseline["person_in_deep_poverty"],
|
|
30
|
+
weights=baseline_demographics["person_weight"],
|
|
31
|
+
)
|
|
32
|
+
reform_poverty = MicroSeries(
|
|
33
|
+
reform["person_in_poverty"], weights=baseline_poverty.weights
|
|
34
|
+
)
|
|
35
|
+
reform_deep_poverty = MicroSeries(
|
|
36
|
+
reform["person_in_deep_poverty"], weights=baseline_poverty.weights
|
|
37
|
+
)
|
|
38
|
+
is_male = MicroSeries(baseline_demographics["is_male"])
|
|
39
|
+
|
|
40
|
+
poverty = dict(
|
|
41
|
+
male=dict(
|
|
42
|
+
baseline=float(baseline_poverty[is_male].mean()),
|
|
43
|
+
reform=float(reform_poverty[is_male].mean()),
|
|
44
|
+
),
|
|
45
|
+
female=dict(
|
|
46
|
+
baseline=float(baseline_poverty[~is_male].mean()),
|
|
47
|
+
reform=float(reform_poverty[~is_male].mean()),
|
|
48
|
+
),
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
deep_poverty = dict(
|
|
52
|
+
male=dict(
|
|
53
|
+
baseline=float(baseline_deep_poverty[is_male].mean()),
|
|
54
|
+
reform=float(reform_deep_poverty[is_male].mean()),
|
|
55
|
+
),
|
|
56
|
+
female=dict(
|
|
57
|
+
baseline=float(baseline_deep_poverty[~is_male].mean()),
|
|
58
|
+
reform=float(reform_deep_poverty[~is_male].mean()),
|
|
59
|
+
),
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
return dict(
|
|
63
|
+
poverty=poverty,
|
|
64
|
+
deep_poverty=deep_poverty,
|
|
65
|
+
)
|