policyengine-uk 2.41.4__py3-none-any.whl → 2.43.0__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_uk/data/dataset_schema.py +63 -15
- policyengine_uk/data/economic_assumptions.py +18 -1
- policyengine_uk/model_api.py +1 -0
- policyengine_uk/parameters/gov/economic_assumptions/yoy_growth.yaml +23 -1
- policyengine_uk/scenarios/__init__.py +3 -0
- policyengine_uk/scenarios/pip_reform.py +23 -0
- policyengine_uk/scenarios/reindex_benefit_cap.py +32 -0
- policyengine_uk/scenarios/repeal_two_child_limit.py +10 -0
- policyengine_uk/system.py +543 -186
- policyengine_uk/tests/microsimulation/reforms_config.yaml +1 -1
- policyengine_uk/utils/create_ahc_deflator.py +170 -0
- policyengine_uk/utils/scenario.py +184 -0
- {policyengine_uk-2.41.4.data → policyengine_uk-2.43.0.data}/data/share/openfisca/openfisca-country-template/CHANGELOG.md +16 -0
- {policyengine_uk-2.41.4.dist-info → policyengine_uk-2.43.0.dist-info}/METADATA +2 -2
- {policyengine_uk-2.41.4.dist-info → policyengine_uk-2.43.0.dist-info}/RECORD +19 -15
- policyengine_uk/repo.py +0 -3
- policyengine_uk/tests/policy/baseline/gov/abolitions/abolition_parameters.yaml +0 -250
- {policyengine_uk-2.41.4.data → policyengine_uk-2.43.0.data}/data/share/openfisca/openfisca-country-template/LICENSE +0 -0
- {policyengine_uk-2.41.4.data → policyengine_uk-2.43.0.data}/data/share/openfisca/openfisca-country-template/README.md +0 -0
- {policyengine_uk-2.41.4.dist-info → policyengine_uk-2.43.0.dist-info}/WHEEL +0 -0
- {policyengine_uk-2.41.4.dist-info → policyengine_uk-2.43.0.dist-info}/licenses/LICENSE +0 -0
@@ -14,11 +14,17 @@ class UKSingleYearDataset:
|
|
14
14
|
household: pd.DataFrame
|
15
15
|
|
16
16
|
@staticmethod
|
17
|
-
def validate_file_path(file_path: str):
|
17
|
+
def validate_file_path(file_path: str, raise_exception: bool = True):
|
18
18
|
if not file_path.endswith(".h5"):
|
19
|
-
|
19
|
+
if raise_exception:
|
20
|
+
raise ValueError(
|
21
|
+
"File path must end with '.h5' for UKDataset."
|
22
|
+
)
|
23
|
+
return False
|
20
24
|
if not Path(file_path).exists():
|
21
|
-
|
25
|
+
if raise_exception:
|
26
|
+
raise FileNotFoundError(f"File not found: {file_path}")
|
27
|
+
return False
|
22
28
|
|
23
29
|
# Check if the file contains time_period, person, benunit, and household datasets
|
24
30
|
with h5py.File(file_path, "r") as f:
|
@@ -30,9 +36,14 @@ class UKSingleYearDataset:
|
|
30
36
|
]
|
31
37
|
for dataset in required_datasets:
|
32
38
|
if dataset not in f:
|
33
|
-
|
34
|
-
|
35
|
-
|
39
|
+
if raise_exception:
|
40
|
+
raise ValueError(
|
41
|
+
f"Dataset '{dataset}' not found in the file: {file_path}"
|
42
|
+
)
|
43
|
+
else:
|
44
|
+
return False
|
45
|
+
|
46
|
+
return True
|
36
47
|
|
37
48
|
def __init__(
|
38
49
|
self,
|
@@ -137,7 +148,7 @@ class UKMultiYearDataset:
|
|
137
148
|
self.datasets[year] = dataset
|
138
149
|
|
139
150
|
if file_path is not None:
|
140
|
-
|
151
|
+
UKMultiYearDataset.validate_file_path(file_path)
|
141
152
|
with pd.HDFStore(file_path) as f:
|
142
153
|
self.datasets = {}
|
143
154
|
for year in f.keys():
|
@@ -162,6 +173,10 @@ class UKMultiYearDataset:
|
|
162
173
|
else:
|
163
174
|
raise ValueError(f"No dataset found for year {fiscal_year}.")
|
164
175
|
|
176
|
+
@property
|
177
|
+
def years(self):
|
178
|
+
return list(self.datasets.keys())
|
179
|
+
|
165
180
|
def __getitem__(self, fiscal_year: int):
|
166
181
|
return self.get_year(fiscal_year)
|
167
182
|
|
@@ -203,21 +218,49 @@ class UKMultiYearDataset:
|
|
203
218
|
return UKMultiYearDataset(datasets=list(new_datasets.values()))
|
204
219
|
|
205
220
|
@staticmethod
|
206
|
-
def validate_file_path(file_path: str):
|
221
|
+
def validate_file_path(file_path: str, raise_exception: bool = False):
|
207
222
|
if not file_path.endswith(".h5"):
|
208
|
-
|
209
|
-
|
210
|
-
|
223
|
+
if raise_exception:
|
224
|
+
raise ValueError(
|
225
|
+
"File path must end with '.h5' for UKMultiYearDataset."
|
226
|
+
)
|
227
|
+
else:
|
228
|
+
return False
|
211
229
|
if not Path(file_path).exists():
|
212
|
-
|
230
|
+
if raise_exception:
|
231
|
+
raise FileNotFoundError(f"File not found: {file_path}")
|
232
|
+
else:
|
233
|
+
return False
|
213
234
|
|
214
235
|
# Check if the file contains datasets for multiple years
|
215
236
|
with h5py.File(file_path, "r") as f:
|
216
237
|
for required_dataset in ["person", "benunit", "household"]:
|
217
238
|
if not any(f"{required_dataset}" in key for key in f.keys()):
|
218
|
-
|
219
|
-
|
220
|
-
|
239
|
+
if raise_exception:
|
240
|
+
raise ValueError(
|
241
|
+
f"Dataset '{required_dataset}' not found in the file: {file_path}"
|
242
|
+
)
|
243
|
+
else:
|
244
|
+
return False
|
245
|
+
|
246
|
+
# Check that there is at least one dataset year in the folder (keys include e.g. person/2025)
|
247
|
+
|
248
|
+
# Check that there is at least one dataset year in the folder
|
249
|
+
years_found = False
|
250
|
+
for key in f.keys():
|
251
|
+
parts = key.split("/")
|
252
|
+
if len(parts) >= 2 and required_dataset == parts[0]:
|
253
|
+
years_found = True
|
254
|
+
break
|
255
|
+
|
256
|
+
if not years_found:
|
257
|
+
if raise_exception:
|
258
|
+
raise ValueError(
|
259
|
+
f"No yearly data found for '{required_dataset}' in file: {file_path}"
|
260
|
+
)
|
261
|
+
else:
|
262
|
+
return False
|
263
|
+
return True
|
221
264
|
|
222
265
|
def load(self):
|
223
266
|
data = {}
|
@@ -228,3 +271,8 @@ class UKMultiYearDataset:
|
|
228
271
|
data[col] = {}
|
229
272
|
data[col][year] = df[col].values
|
230
273
|
return data
|
274
|
+
|
275
|
+
def reset_uprating(self):
|
276
|
+
from policyengine_uk.data.economic_assumptions import reset_uprating
|
277
|
+
|
278
|
+
reset_uprating(self)
|
@@ -1,14 +1,31 @@
|
|
1
1
|
from policyengine_uk.data import UKMultiYearDataset, UKSingleYearDataset
|
2
|
-
|
2
|
+
|
3
3
|
import yaml
|
4
4
|
from policyengine_core.parameters import ParameterNode
|
5
5
|
from pathlib import Path
|
6
6
|
import numpy as np
|
7
7
|
|
8
8
|
|
9
|
+
def extend_single_year_dataset(
|
10
|
+
dataset: UKSingleYearDataset,
|
11
|
+
end_year: int = 2029,
|
12
|
+
) -> UKMultiYearDataset:
|
13
|
+
# Extend years and uprate
|
14
|
+
start_year = int(dataset.time_period)
|
15
|
+
datasets = [dataset]
|
16
|
+
for year in range(start_year, end_year + 1):
|
17
|
+
next_year = dataset.copy()
|
18
|
+
next_year.time_period = str(year)
|
19
|
+
datasets.append(next_year)
|
20
|
+
multi_year_dataset = UKMultiYearDataset(datasets=datasets)
|
21
|
+
return apply_uprating(multi_year_dataset)
|
22
|
+
|
23
|
+
|
9
24
|
def apply_uprating(
|
10
25
|
dataset: UKMultiYearDataset,
|
11
26
|
):
|
27
|
+
from policyengine_uk.system import system
|
28
|
+
|
12
29
|
# Apply uprating to the dataset.
|
13
30
|
dataset = dataset.copy()
|
14
31
|
|
policyengine_uk/model_api.py
CHANGED
@@ -91,6 +91,28 @@ obr:
|
|
91
91
|
- title: OBR EFO March 2025
|
92
92
|
href: https://obr.uk/efo/economic-and-fiscal-outlook-march-2025/
|
93
93
|
|
94
|
+
consumer_price_index_ahc:
|
95
|
+
description: Consumer price index year-on-year growth, modified to remove housing costs.
|
96
|
+
values:
|
97
|
+
2022-01-01: 0.107
|
98
|
+
2023-01-01: 0.057
|
99
|
+
2024-01-01: 0.018
|
100
|
+
2025-01-01: 0.027
|
101
|
+
2026-01-01: 0.017
|
102
|
+
2027-01-01: 0.019
|
103
|
+
2028-01-01: 0.019
|
104
|
+
2029-01-01: 0.019
|
105
|
+
metadata:
|
106
|
+
unit: /1
|
107
|
+
label: after housing costs consumer price index growth
|
108
|
+
reference:
|
109
|
+
- title: OBR EFO March 2025
|
110
|
+
href: https://obr.uk/efo/economic-and-fiscal-outlook-march-2025/
|
111
|
+
- title: ONS CPI weights
|
112
|
+
href: https://www.ons.gov.uk/economy/inflationandpriceindices/datasets/consumerpriceinflationupdatingweightsannexatablesw1tow3
|
113
|
+
- title: ONS CPI series excluding housing costs
|
114
|
+
href: https://www.ons.gov.uk/economy/inflationandpriceindices/adhocs/2863consumerpriceindicesseriesexcludingrentsmaintenancerepairsandwaterchargesfortheperiodjanuary1996toapril2025
|
115
|
+
|
94
116
|
non_labour_income:
|
95
117
|
description: Non-labour income year-on-year growth.
|
96
118
|
values:
|
@@ -516,4 +538,4 @@ finance_ni:
|
|
516
538
|
label: domestic rates growth
|
517
539
|
reference:
|
518
540
|
- title: Finance NI (based on outturn)
|
519
|
-
href: https://www.finance-ni.gov.uk/
|
541
|
+
href: https://www.finance-ni.gov.uk/
|
@@ -0,0 +1,23 @@
|
|
1
|
+
from policyengine_uk.model_api import Scenario
|
2
|
+
from policyengine_uk import Simulation
|
3
|
+
import numpy as np
|
4
|
+
|
5
|
+
|
6
|
+
def modify_simulation(sim: Simulation):
|
7
|
+
np.random.seed(42) # For reproducibility
|
8
|
+
pip_seed = np.random.random(len(sim.calculate("person_id")))
|
9
|
+
|
10
|
+
start_year = 2025
|
11
|
+
end_year = 2029
|
12
|
+
|
13
|
+
for year in range(start_year, end_year + 1):
|
14
|
+
current_pip = sim.calculate("pip", year)
|
15
|
+
percent_along_phase_in = (year - start_year) / (end_year - start_year)
|
16
|
+
current_pip[pip_seed < 0.25 * percent_along_phase_in] = 0
|
17
|
+
sim.set_input("pip", year, current_pip)
|
18
|
+
return sim
|
19
|
+
|
20
|
+
|
21
|
+
reform_pip_phase_in = Scenario(
|
22
|
+
simulation_modifier=modify_simulation,
|
23
|
+
)
|
@@ -0,0 +1,32 @@
|
|
1
|
+
from policyengine_uk.model_api import Scenario
|
2
|
+
from policyengine_uk import Simulation
|
3
|
+
from policyengine_core.parameters import Parameter
|
4
|
+
|
5
|
+
|
6
|
+
def index_benefit_cap(simulation: Simulation):
|
7
|
+
# Identify benefit cap parameters
|
8
|
+
simulation.tax_benefit_system.reset_parameters()
|
9
|
+
|
10
|
+
params = (
|
11
|
+
simulation.tax_benefit_system.parameters.gov.dwp.benefit_cap.get_descendants()
|
12
|
+
)
|
13
|
+
# We just want the leaf nodes of the parameter tree
|
14
|
+
# TODO: add a get-descendants-leaf-nodes-only function
|
15
|
+
|
16
|
+
params = [param for param in params if isinstance(param, Parameter)]
|
17
|
+
|
18
|
+
for parameter in params:
|
19
|
+
parameter: Parameter # Type annotation
|
20
|
+
# Delete all values after 2025
|
21
|
+
parameter.values_list = [
|
22
|
+
entry
|
23
|
+
for entry in parameter.values_list
|
24
|
+
if entry.instant_str < "2026-01-01"
|
25
|
+
]
|
26
|
+
parameter.metadata.update(
|
27
|
+
uprating="gov.benefit_uprating_cpi",
|
28
|
+
)
|
29
|
+
simulation.tax_benefit_system.process_parameters()
|
30
|
+
|
31
|
+
|
32
|
+
reindex_benefit_cap = Scenario(simulation_modifier=index_benefit_cap)
|