Skip to content

Commit 5d73916

Browse files
committed
Calibrate fuel litre proxies to road clearances
1 parent 3dc97b4 commit 5d73916

7 files changed

Lines changed: 292 additions & 38 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Calibrate final petrol and diesel litre proxies to HMRC/OBR road-fuel clearances, and uprate fuel-spending proxies by road-fuel litres and pump prices.

policyengine_uk_data/datasets/create_datasets.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ def main():
6969
"Calibrate constituency weights",
7070
"Calibrate local authority weights",
7171
"Downrate to 2023",
72+
"Calibrate fuel litres",
7273
"Save final dataset",
7374
"Create tiny datasets",
7475
]
@@ -217,6 +218,14 @@ def main():
217218
frs_calibrated = uprate_dataset(frs_calibrated_constituencies, 2023)
218219
update_dataset("Downrate to 2023", "completed")
219220

221+
update_dataset("Calibrate fuel litres", "processing")
222+
from policyengine_uk_data.datasets.imputations.consumption import (
223+
calibrate_dataset_fuel_litre_proxies_to_road_fuel,
224+
)
225+
226+
calibrate_dataset_fuel_litre_proxies_to_road_fuel(frs_calibrated)
227+
update_dataset("Calibrate fuel litres", "completed")
228+
220229
update_dataset("Save final dataset", "processing")
221230
strip_internal_disability_reported_amounts(frs_calibrated).save(
222231
STORAGE_FOLDER / "enhanced_frs_2023_24.h5"

policyengine_uk_data/datasets/imputations/consumption.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -610,13 +610,79 @@ def fuel_spending_litre_proxy_uprating(
610610
parameters.household.consumption.fuel.prices,
611611
FUEL_PRICE_PARAMETER_NAME[variable],
612612
)
613+
population = parameters.gov.economic_assumptions.indices.ons.population
613614
return (
614615
road_fuel_volume_uprating(start_year=start_year, end_year=end_year)
616+
/ (population(end_year) / population(start_year))
615617
* model_price(end_year)
616618
/ lcfs_price
617619
)
618620

619621

622+
def _fuel_litre_proxy_mlitres(
623+
household: pd.DataFrame,
624+
fiscal_year: int,
625+
parameters=None,
626+
) -> float:
627+
"""Return weighted petrol + diesel litres represented by spending proxies."""
628+
from policyengine_uk.system import system
629+
630+
if parameters is None:
631+
parameters = system.parameters
632+
633+
total_litres = 0.0
634+
for variable, price_parameter_name in FUEL_PRICE_PARAMETER_NAME.items():
635+
price = getattr(
636+
parameters.household.consumption.fuel.prices,
637+
price_parameter_name,
638+
)(fiscal_year)
639+
total_litres += household[variable] / price
640+
return (total_litres * household["household_weight"]).sum() / 1_000_000
641+
642+
643+
def calibrate_fuel_litre_proxies_to_road_fuel(
644+
household: pd.DataFrame,
645+
fiscal_year: int,
646+
parameters=None,
647+
) -> float:
648+
"""Scale imputed fuel proxies to HMRC/OBR road-fuel litre controls.
649+
650+
PolicyEngine UK derives petrol and diesel litres from spending divided by
651+
pump prices. Applying one multiplicative factor to petrol and diesel
652+
spending preserves the household distribution while making the resulting
653+
litres reconcile to the external fuel-duty volume benchmark.
654+
"""
655+
from policyengine_uk_data.sources.road_fuel_volume import (
656+
road_fuel_clearances_mlitres,
657+
)
658+
659+
actual_mlitres = _fuel_litre_proxy_mlitres(
660+
household,
661+
fiscal_year,
662+
parameters=parameters,
663+
)
664+
if actual_mlitres <= 0:
665+
return 1.0
666+
667+
target_mlitres = road_fuel_clearances_mlitres(end_year=fiscal_year)[fiscal_year]
668+
scale = target_mlitres / actual_mlitres
669+
for variable in FUEL_PRICE_PARAMETER_NAME:
670+
household[variable] *= scale
671+
return scale
672+
673+
674+
def calibrate_dataset_fuel_litre_proxies_to_road_fuel(
675+
dataset: UKSingleYearDataset,
676+
parameters=None,
677+
) -> float:
678+
"""Scale a dataset's fuel proxies after final household weights are set."""
679+
return calibrate_fuel_litre_proxies_to_road_fuel(
680+
dataset.household,
681+
int(dataset.time_period),
682+
parameters=parameters,
683+
)
684+
685+
620686
def save_imputation_models():
621687
from policyengine_uk_data.utils.qrf import QRF
622688

policyengine_uk_data/storage/uprating_factors.csv

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ childcare_expenses,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,
1212
clothing_and_footwear_consumption,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38
1313
communication_consumption,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38
1414
corporate_wealth,1.0,1.0,1.092,1.147,1.19,1.223,1.258,1.297,1.34,1.384,1.384,1.384,1.384,1.384,1.384
15-
diesel_spending,1.0,1.244,1.322,1.314,1.313,1.287,1.268,1.237,1.204,1.159,1.099,1.099,1.099,1.099,1.099
15+
diesel_spending,1.0,1.138,1.56,1.531,1.579,1.537,1.509,1.467,1.422,1.363,1.286,1.28,1.275,1.27,1.265
1616
dividend_income,1.0,1.0,1.092,1.147,1.19,1.223,1.258,1.297,1.34,1.384,1.384,1.384,1.384,1.384,1.384
1717
domestic_energy_consumption,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38
1818
education_consumption,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38
@@ -30,7 +30,7 @@ free_school_milk,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.
3030
gross_financial_wealth,1.0,1.0,1.092,1.147,1.19,1.223,1.258,1.297,1.34,1.384,1.384,1.384,1.384,1.384,1.384
3131
health_consumption,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38
3232
household_furnishings_consumption,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38
33-
household_weight,1.0,1.0,1.003,1.017,1.027,1.039,1.046,1.054,1.058,1.064,1.064,1.064,1.064,1.064,1.064
33+
household_weight,1.0,1.0,1.009,1.023,1.033,1.041,1.045,1.049,1.053,1.058,1.062,1.067,1.071,1.076,1.08
3434
housing_benefit_reported,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38
3535
housing_service_charges,1.0,1.0,1.0,1.064,1.137,1.191,1.235,1.262,1.289,1.318,1.318,1.318,1.318,1.318,1.318
3636
housing_water_and_electricity_consumption,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38
@@ -56,7 +56,7 @@ owned_land,1.0,1.0,1.092,1.147,1.19,1.223,1.258,1.297,1.34,1.384,1.384,1.384,1.3
5656
pension_credit_reported,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38
5757
pension_income,1.0,1.0,1.092,1.147,1.19,1.223,1.258,1.297,1.34,1.384,1.384,1.384,1.384,1.384,1.384
5858
personal_pension_contributions,1.0,1.059,1.127,1.205,1.261,1.308,1.337,1.365,1.396,1.431,1.431,1.431,1.431,1.431,1.431
59-
petrol_spending,1.0,1.244,1.322,1.314,1.313,1.287,1.268,1.237,1.204,1.159,1.099,1.099,1.099,1.099,1.099
59+
petrol_spending,1.0,1.104,1.624,1.786,1.521,1.48,1.454,1.413,1.37,1.313,1.239,1.233,1.228,1.223,1.218
6060
private_pension_income,1.0,1.003,1.053,1.106,1.161,1.216,1.261,1.288,1.315,1.346,1.346,1.346,1.346,1.346,1.346
6161
private_transfer_income,1.0,1.0,1.092,1.147,1.19,1.223,1.258,1.297,1.34,1.384,1.384,1.384,1.384,1.384,1.384
6262
property_income,1.0,1.0,1.092,1.147,1.19,1.223,1.258,1.297,1.34,1.384,1.384,1.384,1.384,1.384,1.384

policyengine_uk_data/storage/uprating_growth_factors.csv

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ childcare_expenses,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0
1212
clothing_and_footwear_consumption,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0
1313
communication_consumption,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0
1414
corporate_wealth,0,0.0,0.092,0.05,0.037,0.028,0.029,0.031,0.033,0.033,0.0,0.0,0.0,0.0,0.0
15-
diesel_spending,0,0.244,0.063,-0.006,-0.001,-0.02,-0.015,-0.024,-0.027,-0.037,-0.052,0.0,0.0,0.0,0.0
15+
diesel_spending,0,0.138,0.371,-0.019,0.031,-0.027,-0.018,-0.028,-0.031,-0.041,-0.056,-0.005,-0.004,-0.004,-0.004
1616
dividend_income,0,0.0,0.092,0.05,0.037,0.028,0.029,0.031,0.033,0.033,0.0,0.0,0.0,0.0,0.0
1717
domestic_energy_consumption,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0
1818
education_consumption,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0
@@ -30,7 +30,7 @@ free_school_milk,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0
3030
gross_financial_wealth,0,0.0,0.092,0.05,0.037,0.028,0.029,0.031,0.033,0.033,0.0,0.0,0.0,0.0,0.0
3131
health_consumption,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0
3232
household_furnishings_consumption,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0
33-
household_weight,0,0.0,0.003,0.014,0.01,0.012,0.007,0.008,0.004,0.006,0.0,0.0,0.0,0.0,0.0
33+
household_weight,0,0.0,0.009,0.014,0.01,0.008,0.004,0.004,0.004,0.005,0.004,0.005,0.004,0.005,0.004
3434
housing_benefit_reported,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0
3535
housing_service_charges,0,0.0,0.0,0.064,0.069,0.047,0.037,0.022,0.021,0.022,0.0,0.0,0.0,0.0,0.0
3636
housing_water_and_electricity_consumption,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0
@@ -56,7 +56,7 @@ owned_land,0,0.0,0.092,0.05,0.037,0.028,0.029,0.031,0.033,0.033,0.0,0.0,0.0,0.0,
5656
pension_credit_reported,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0
5757
pension_income,0,0.0,0.092,0.05,0.037,0.028,0.029,0.031,0.033,0.033,0.0,0.0,0.0,0.0,0.0
5858
personal_pension_contributions,0,0.059,0.064,0.069,0.046,0.037,0.022,0.021,0.023,0.025,0.0,0.0,0.0,0.0,0.0
59-
petrol_spending,0,0.244,0.063,-0.006,-0.001,-0.02,-0.015,-0.024,-0.027,-0.037,-0.052,0.0,0.0,0.0,0.0
59+
petrol_spending,0,0.104,0.471,0.1,-0.148,-0.027,-0.018,-0.028,-0.03,-0.042,-0.056,-0.005,-0.004,-0.004,-0.004
6060
private_pension_income,0,0.003,0.05,0.05,0.05,0.047,0.037,0.021,0.021,0.024,0.0,0.0,0.0,0.0,0.0
6161
private_transfer_income,0,0.0,0.092,0.05,0.037,0.028,0.029,0.031,0.033,0.033,0.0,0.0,0.0,0.0,0.0
6262
property_income,0,0.0,0.092,0.05,0.037,0.028,0.029,0.031,0.033,0.033,0.0,0.0,0.0,0.0,0.0

policyengine_uk_data/tests/test_road_fuel_volume_uprating.py

Lines changed: 134 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
"""Tests for road-fuel volume uprating. See #402."""
22

33
import pandas as pd
4+
import pytest
5+
from policyengine_uk.data import UKSingleYearDataset
46

57
from policyengine_uk_data.datasets.imputations.consumption import (
68
CONSUMPTION_MODEL_FILENAME,
79
IMPUTATIONS,
10+
_fuel_litre_proxy_mlitres,
11+
calibrate_dataset_fuel_litre_proxies_to_road_fuel,
12+
calibrate_fuel_litre_proxies_to_road_fuel,
813
fuel_spending_litre_proxy_uprating,
914
uprate_lcfs_table,
1015
)
@@ -23,7 +28,9 @@
2328
END_YEAR,
2429
START_YEAR,
2530
VOLUME_OVERRIDDEN_VARIABLES,
26-
_apply_road_fuel_volume_override,
31+
_apply_road_fuel_litre_proxy_override,
32+
fuel_spending_litre_proxy_index,
33+
uprate_dataset,
2734
)
2835

2936

@@ -76,24 +83,32 @@ def test__given_uprating_table__then_only_fuel_rows_are_overridden():
7683
)
7784

7885
# When
79-
out = _apply_road_fuel_volume_override(df.copy())
80-
expected = road_fuel_volume_index(base_year=START_YEAR, end_year=END_YEAR)
86+
out = _apply_road_fuel_litre_proxy_override(df.copy())
8187

8288
# Then
8389
for year in range(START_YEAR, END_YEAR + 1):
8490
assert out.loc["some_other_variable", year] == 1.0
8591
for variable in VOLUME_OVERRIDDEN_VARIABLES:
92+
expected = fuel_spending_litre_proxy_index(
93+
variable=variable,
94+
base_year=START_YEAR,
95+
end_year=END_YEAR,
96+
)
8697
for year in range(START_YEAR, END_YEAR + 1):
8798
assert out.loc[variable, year] == round(expected[year], 3)
8899

89100

90-
def test__given_storage_csv__then_fuel_rows_reflect_volume_index():
101+
def test__given_storage_csv__then_fuel_rows_reflect_litre_proxy_index():
91102
# Given
92103
df = pd.read_csv(STORAGE_FOLDER / "uprating_factors.csv").set_index("Variable")
93-
expected = road_fuel_volume_index(base_year=START_YEAR, end_year=END_YEAR)
94104

95105
# Then
96106
for variable in VOLUME_OVERRIDDEN_VARIABLES:
107+
expected = fuel_spending_litre_proxy_index(
108+
variable=variable,
109+
base_year=START_YEAR,
110+
end_year=END_YEAR,
111+
)
97112
assert variable in df.index
98113
for year in range(START_YEAR, END_YEAR + 1):
99114
assert df.loc[variable, str(year)] == round(expected[year], 3)
@@ -148,10 +163,124 @@ def test__given_obr_2027_volume__then_rate_difference_matches_cost_benchmark():
148163
assert round(benchmark_cost_gbp_bn, 2) == 3.12
149164

150165

166+
def test__given_imputed_fuel_proxies__then_calibration_matches_road_fuel_litres():
167+
# Given
168+
from policyengine_uk.system import system
169+
170+
year = 2027
171+
target_mlitres = road_fuel_clearances_mlitres()[year]
172+
petrol_price = system.parameters.household.consumption.fuel.prices.petrol(year)
173+
diesel_price = system.parameters.household.consumption.fuel.prices.diesel(year)
174+
household = pd.DataFrame(
175+
{
176+
"household_weight": [1.0, 1.0],
177+
"petrol_spending": [
178+
target_mlitres * 1_000_000 * petrol_price,
179+
0.0,
180+
],
181+
"diesel_spending": [
182+
0.0,
183+
target_mlitres * 1_000_000 * diesel_price,
184+
],
185+
}
186+
)
187+
188+
# When
189+
scale = calibrate_fuel_litre_proxies_to_road_fuel(household, year)
190+
191+
# Then
192+
assert scale == pytest.approx(0.5)
193+
assert _fuel_litre_proxy_mlitres(household, year) == pytest.approx(target_mlitres)
194+
195+
196+
def test__given_final_weight_changes__then_dataset_calibration_matches_litres():
197+
# Given
198+
from policyengine_uk.system import system
199+
200+
year = 2023
201+
target_mlitres = road_fuel_clearances_mlitres()[year]
202+
petrol_price = system.parameters.household.consumption.fuel.prices.petrol(year)
203+
dataset = _minimal_fuel_dataset(
204+
fiscal_year=year,
205+
petrol_spending=target_mlitres * 1_000_000 * petrol_price,
206+
diesel_spending=0.0,
207+
household_weight=2.0,
208+
)
209+
210+
# When
211+
scale = calibrate_dataset_fuel_litre_proxies_to_road_fuel(dataset)
212+
213+
# Then
214+
assert scale == pytest.approx(0.5)
215+
assert _fuel_litre_proxy_mlitres(dataset.household, year) == pytest.approx(
216+
target_mlitres
217+
)
218+
219+
220+
def test__given_calibrated_dataset_uprated__then_litres_track_forecast_target():
221+
# Given
222+
from policyengine_uk.system import system
223+
224+
start_year = 2023
225+
target_year = 2027
226+
start_mlitres = road_fuel_clearances_mlitres()[start_year]
227+
petrol_price = system.parameters.household.consumption.fuel.prices.petrol(
228+
start_year
229+
)
230+
dataset = _minimal_fuel_dataset(
231+
fiscal_year=start_year,
232+
petrol_spending=start_mlitres * 1_000_000 * petrol_price,
233+
diesel_spending=0.0,
234+
)
235+
calibrate_dataset_fuel_litre_proxies_to_road_fuel(dataset)
236+
237+
# When
238+
uprated = uprate_dataset(dataset, target_year)
239+
240+
# Then
241+
assert _fuel_litre_proxy_mlitres(
242+
uprated.household,
243+
target_year,
244+
) == pytest.approx(road_fuel_clearances_mlitres()[target_year], rel=1e-3)
245+
246+
151247
def test__given_year_after_obr_horizon__then_last_forecast_is_carried_forward():
152248
# When
153249
series = road_fuel_clearances_mlitres(end_year=END_YEAR)
154250

155251
# Then
156252
assert series[2031] == series[2030]
157253
assert series[2034] == series[2030]
254+
255+
256+
def _minimal_fuel_dataset(
257+
*,
258+
fiscal_year: int,
259+
petrol_spending: float,
260+
diesel_spending: float,
261+
household_weight: float = 1.0,
262+
) -> UKSingleYearDataset:
263+
return UKSingleYearDataset(
264+
person=pd.DataFrame(
265+
{
266+
"person_id": [0],
267+
"benunit_id": [0],
268+
"household_id": [0],
269+
}
270+
),
271+
benunit=pd.DataFrame(
272+
{
273+
"benunit_id": [0],
274+
"household_id": [0],
275+
}
276+
),
277+
household=pd.DataFrame(
278+
{
279+
"household_id": [0],
280+
"household_weight": [household_weight],
281+
"petrol_spending": [petrol_spending],
282+
"diesel_spending": [diesel_spending],
283+
}
284+
),
285+
fiscal_year=fiscal_year,
286+
)

0 commit comments

Comments
 (0)