Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/2692-index-first-growth-year.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Anchor ten growth indices (per-person GDP and mixed income, house prices, mortgage interest, petrol and diesel spending, rail journeys per person, population, the private pension index and lagged earnings) one year before their first listed growth year, so a direct input dated before that year is carried forward by that year's growth instead of a factor of 1. For example, a 2020 `rail_usage` input queried for 2021 grows by 2.5433. Dataset projection, which reads the growth rates, is unchanged, except that residential property values read the rounded house-price index, so their yearly ratios can move slightly (2021 to 2022: 1.0926 before, about 1.0925970 now).
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ def add_private_pension_uprating_factor(
"gov.economic_assumptions.yoy_growth.obr.private_pension_index",
data={
"values": values,
# Anchor the index at 1 on 1 January 2019 so it includes the
# 2020 growth, the first listed (#2692).
"metadata": {"index_base_year": YEARS[0] - 1},
},
)

Expand Down
15 changes: 15 additions & 0 deletions policyengine_uk/parameters/gov/economic_assumptions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,21 @@ calibrate the input dataset. The source of the default economic series is
generated from it by
[`create_economic_assumption_indices.py`](./create_economic_assumption_indices.py).

An index is 1 in its series' first listed year by default, so that year's
growth is not in it. A series that sets `index_base_year` to the year before
is 1 there instead, and its first step is the first listed growth (#2692).
`ons.per_capita.household_interest_income`, `obr.per_capita.gdp`,
`obr.per_capita.mixed_income`, `obr.house_prices`, `obr.mortgage_interest`,
`obr.petrol_spending_litre_proxy`, `obr.diesel_spending_litre_proxy`,
`orr.per_capita.rail_passenger_journeys` and `ons.population` set it to 2020,
`obr.private_pension_index` to 2019 and `obr.lagged_average_earnings` to
2021. Only a direct input dated before the first growth year reads that step.
Dataset projection mostly uses the growth rates themselves, so it is
unchanged. The exception is residential property, which reads the
`obr.house_prices` index, rounded to five decimals, so its year-to-year
ratios can move slightly (2021 to 2022: 1.0926 before, about 1.0925970
now).

The dedicated `capital_gains` growth/index paths copy GDP per capita by
default. A simulation can instead supply a versioned aggregate gains
projection, converted to per-record factors using population growth after
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,14 @@ def add_lagged_earnings(
Add lagged average earnings to the economic assumptions.
"""
obr = parameters.gov.economic_assumptions.yoy_growth.obr
# Anchor the index at 1 on 1 January 2021 so it includes the 2022
# growth (2021 earnings growth), the first listed (#2692).
add_lagged_parameter(
obr, "average_earnings", "lagged_average_earnings", first_year=2022
obr,
"average_earnings",
"lagged_average_earnings",
first_year=2022,
metadata={"index_base_year": 2021},
)

return parameters
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,13 @@ def add_lagged_parameter(
source_name: str,
lagged_name: str,
first_year: int,
metadata: dict = None,
) -> Parameter:
"""Add a one-year-lagged copy of ``source_name`` as a child of ``node``."""
"""Add a one-year-lagged copy of ``source_name`` as a child of ``node``.

``metadata`` is set on the lagged parameter (for example
``index_base_year``).
"""
source = getattr(node, source_name)

lagged = Parameter(
Expand All @@ -30,6 +35,7 @@ def add_lagged_parameter(
f"{year}-01-01": source(year - 1)
for year in lag_source_years(source, first_year)
},
**({"metadata": metadata} if metadata else {}),
},
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,9 @@ obr:
2072-01-01: 0.0010010010010010895
2073-01-01: 0.0010010010010010895
metadata:
# Anchor the index at 1 on 1 January 2020 so it includes the
# 2021 growth, the first listed (#2692).
index_base_year: 2020
unit: /1
label: Petrol spending litre-proxy growth
reference:
Expand Down Expand Up @@ -478,6 +481,9 @@ obr:
2072-01-01: 0.0010010010010010895
2073-01-01: 0.0010010010010010895
metadata:
# Anchor the index at 1 on 1 January 2020 so it includes the
# 2021 growth, the first listed (#2692).
index_base_year: 2020
unit: /1
label: Diesel spending litre-proxy growth
reference:
Expand Down Expand Up @@ -854,6 +860,9 @@ obr:
2029-01-01: 0.0306
2030-01-01: 0.0315
metadata:
# Anchor the index at 1 on 1 January 2020 so it includes the
# 2021 growth, the first listed (#2692).
index_base_year: 2020
unit: /1
label: Per capita GDP growth
reference:
Expand All @@ -878,6 +887,9 @@ obr:
2029-01-01: 0.0364
2030-01-01: 0.0370
metadata:
# Anchor the index at 1 on 1 January 2020 so it includes the
# 2021 growth, the first listed (#2692).
index_base_year: 2020
unit: /1
label: Per capita mixed income growth
reference:
Expand Down Expand Up @@ -923,6 +935,9 @@ obr:
2029-01-01: 0.026
2030-01-01: 0.024
metadata:
# Anchor the index at 1 on 1 January 2020 so it includes the
# 2021 growth, the first listed (#2692).
index_base_year: 2020
unit: /1
label: House prices growth
reference:
Expand All @@ -949,6 +964,9 @@ obr:
# figure; its 2030 Q1 rate is 5.40% and its 2029-30 rate 5.05%.
2030-01-01: 0.0553
metadata:
# Anchor the index at 1 on 1 January 2020 so it includes the
# 2021 growth, the first listed (#2692).
index_base_year: 2020
unit: /1
label: Mortgage interest growth
reference:
Expand Down Expand Up @@ -1098,6 +1116,9 @@ ons:
2072-01-01: -0.0010
2073-01-01: -0.0010
metadata:
# Anchor the index at 1 on 1 January 2020 so it includes the
# 2021 growth, the first listed (#2692).
index_base_year: 2020
unit: /1
label: Population growth
reference:
Expand Down Expand Up @@ -1694,6 +1715,9 @@ orr:
2072-01-01: 0.0010 # 1 / 0.9990 - 1
2073-01-01: 0.0010 # 1 / 0.9990 - 1
metadata:
# Anchor the index at 1 on 1 January 2020 so it includes the
# 2021 growth, the first listed (#2692).
index_base_year: 2020
unit: /1
label: Rail passenger journeys growth per person
reference:
Expand Down
104 changes: 104 additions & 0 deletions policyengine_uk/tests/test_growth_index_first_year.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
"""Growth indices include their first listed year's growth (#2692).

The index generator puts an index of 1 in a series' first listed year and
multiplies growth only from the next year, so the first listed growth was
never in the index. Each series below sets ``index_base_year`` to the year
before, so the index is 1 there and its first step is that growth. Only a
direct input dated before the first growth year read the dropped step.
Dataset projection reads ``yoy_growth`` directly, except for residential
property, which reads the five-decimal ``obr.house_prices`` index, so its
yearly ratios can move slightly.
"""

import pytest

from policyengine_uk import parameters

YOY = "gov.economic_assumptions.yoy_growth."
INDICES = "gov.economic_assumptions.indices."

# Series and their first listed growth year, from the #2692 table.
FIRST_GROWTH_YEAR = {
"obr.per_capita.gdp": 2021,
"obr.per_capita.mixed_income": 2021,
"obr.house_prices": 2021,
"obr.mortgage_interest": 2021,
"obr.petrol_spending_litre_proxy": 2021,
"obr.diesel_spending_litre_proxy": 2021,
"orr.per_capita.rail_passenger_journeys": 2021,
"ons.population": 2021,
# Generated: min(RPI in the previous year, 5%) from 2020.
"obr.private_pension_index": 2020,
# Generated: the previous year's earnings growth from 2022.
"obr.lagged_average_earnings": 2022,
}

# Index levels are rounded to 5 decimal places as they cumulate, so a ratio
# of two levels matches the growth only to about 1e-5.
ROUNDING = 2e-5


def growth(series, year):
return parameters.get_child(YOY + series)(f"{year}-01-01")


def index(series, year):
return parameters.get_child(INDICES + series)(f"{year}-01-01")


@pytest.mark.parametrize("series", FIRST_GROWTH_YEAR)
def test_index_includes_the_first_growth_year(series):
# For example rail: 2021 / 2020 = 2.5433, not 1.
first = FIRST_GROWTH_YEAR[series]
assert index(series, first - 1) == 1
assert growth(series, first) != 0
assert index(series, first) / index(series, first - 1) == pytest.approx(
1 + growth(series, first), rel=ROUNDING
)


@pytest.mark.parametrize("series", FIRST_GROWTH_YEAR)
def test_index_is_flat_before_its_base_year(series):
# Backdating carries the base year's 1 back, so no growth is invented
# before the first listed year.
first = FIRST_GROWTH_YEAR[series]
for year in range(2015, first):
assert index(series, year) == 1


@pytest.mark.parametrize("series", FIRST_GROWTH_YEAR)
def test_later_steps_still_follow_their_growth(series):
first = FIRST_GROWTH_YEAR[series]
for year in range(first + 1, 2040):
assert index(series, year) / index(series, year - 1) == pytest.approx(
1 + growth(series, year), rel=ROUNDING
), year


def test_capital_gains_index_still_copies_gdp_per_person():
# capital_gains_projection.py copies the per-person GDP index exactly.
for year in range(2015, 2040):
assert index("capital_gains", year) == index("obr.per_capita.gdp", year)


def test_direct_rail_input_dated_2020_grows_by_the_2021_step():
# A 2020 input queried for 2021 is carried forward by the index ratio.
# Before #2692 the 2020 and 2021 levels were both 1, so it stayed at
# 100 rather than 100 x 2.5433.
from policyengine_uk import Simulation

simulation = Simulation(
situation={
"people": {"adult": {"age": {2020: 40, 2021: 41}}},
"benunits": {"benunit": {"members": ["adult"]}},
"households": {
"household": {
"members": ["adult"],
"rail_usage": {2020: 100.0},
}
},
}
)
assert float(simulation.calculate("rail_usage", 2021)[0]) == pytest.approx(
254.33, abs=0.01
)