diff --git a/changelog_entry.yaml b/changelog_entry.yaml index e69de29bb..d6de9c8d2 100644 --- a/changelog_entry.yaml +++ b/changelog_entry.yaml @@ -0,0 +1,4 @@ +- bump: minor + changes: + added: + - Water bills projections. diff --git a/docs/book/_toc.yml b/docs/book/_toc.yml index 7914b94fb..b6c6e298d 100644 --- a/docs/book/_toc.yml +++ b/docs/book/_toc.yml @@ -7,6 +7,7 @@ parts: - caption: Economic assumptions chapters: - file: assumptions/growthfactors + - file: assumptions/water-bills - caption: Validation chapters: - file: validation/hbai diff --git a/docs/book/assumptions/water-bills.md b/docs/book/assumptions/water-bills.md new file mode 100644 index 000000000..24773a640 --- /dev/null +++ b/docs/book/assumptions/water-bills.md @@ -0,0 +1,74 @@ +# Water bills projection and uprating + +This documentation describes the water bills projection and uprating methodology used in PolicyEngine UK. + +## Overview + +We implement water bills projections through a combination of historical data analysis and regulatory projection data. The system uses: + +1. **Historical data** (2021-2025) from Ofwat average bills +2. **Regulatory projection data** from Ofwat company-specific increases (2025-2030) +3. **Economic uprating** using Consumer Price Index (CPIH) growth rates + +## Implementation + +### Data sources + +#### Historical data +The projection uses historical real water bills data from Ofwat: +- **2021**: £486 (real terms) +- **2022**: £470 (real terms) +- **2023**: £486 (real terms) +- **2024**: £492 (real terms) +- **2025**: £503 (real terms) + +**Data source**: [Ofwat average bills data for England and Wales](https://www.ofwat.gov.uk/average-bills-press-statement-2024-25/) + +#### Regulatory projections +We source company-specific water bill increases from the Consumer Council for Water (CCW) based on Ofwat's price review data for 2025-2030. The data includes projected bills for 16 water companies covering both water and sewerage services. + +**Data source**: [CCW Water Company Bill Increases 2025-30](https://www.ccw.org.uk/our-work/price-review/how-much-will-my-water-and-sewerage-bills-increase-by-2030/breakdown-of-water-companies-bill-increases-2025-30/) + +### Projection methodology + +#### Historical period (2021-2025) +1. We convert real bills to nominal terms using CPIH values +2. We calculate year-on-year nominal growth rates +3. We normalize to 2021 baseline (index = 100) + +#### Future period (2025-2030) +1. We calculate average of company-specific projected increases +2. We apply Consumer Price Index (CPIH) uprating to account for inflation +3. We compound inflation adjustments year-over-year + +The methodology ensures that: +- We preserve real increases from regulatory decisions +- We apply additional inflation adjustments using CPIH forecasts +- Future projections account for both policy changes and economic conditions + +### Code structure + +#### Core implementation +- **Location**: `policyengine_uk/utils/water/forecast_water_bills.py` +- **Function**: `project_water_bills()` +- **Data**: `policyengine_uk/utils/water/ofwat_increases.csv` + +#### Parameters integration +We store water bills year-on-year growth rates in: +- **Location**: `policyengine_uk/parameters/gov/economic_assumptions/yoy_growth.yaml` +- **Parameter**: `ofwat.water_bills` + +### Growth rates + +The model uses these resulting year-on-year growth rates: + +| Year | Growth rate | +|------|-------------| +| 2022 | 5.2% | +| 2023 | 9.2% | +| 2024 | 4.4% | +| 2025 | 6.1% | +| 2026 | 6.1% | +| 2027 | 5.1% | +| 2028 | 3.8% | +| 2029 | 4.3% | diff --git a/policyengine_uk/data/dataset_schema.py b/policyengine_uk/data/dataset_schema.py index fdfb9abd1..683913aea 100644 --- a/policyengine_uk/data/dataset_schema.py +++ b/policyengine_uk/data/dataset_schema.py @@ -86,6 +86,13 @@ def copy(self): household=self.household.copy(), ) + def validate(self): + # Check for NaNs in the tables + for df in self.tables: + for col in df.columns: + if df[col].isna().any(): + raise ValueError(f"Column '{col}' contains NaN values.") + @staticmethod def from_simulation( simulation: "Microsimulation", fiscal_year: int = 2025 diff --git a/policyengine_uk/parameters/gov/economic_assumptions/yoy_growth.yaml b/policyengine_uk/parameters/gov/economic_assumptions/yoy_growth.yaml index 227aedfd4..5483dbc15 100644 --- a/policyengine_uk/parameters/gov/economic_assumptions/yoy_growth.yaml +++ b/policyengine_uk/parameters/gov/economic_assumptions/yoy_growth.yaml @@ -263,3 +263,21 @@ ons: reference: - title: ONS Population Projections href: https://www.ons.gov.uk/ +ofwat: + water_bills: + description: Water and sewerage bills year-on-year growth. + values: + 2022-01-01: 0.052 + 2023-01-01: 0.092 + 2024-01-01: 0.044 + 2025-01-01: 0.061 + 2026-01-01: 0.061 + 2027-01-01: 0.051 + 2028-01-01: 0.038 + 2029-01-01: 0.043 + metadata: + unit: /1 + label: water bills growth + reference: + - title: Ofwat (and custom projections) + href: https://www.ofwat.gov.uk/price-review/ \ No newline at end of file diff --git a/policyengine_uk/utils/water/README.md b/policyengine_uk/utils/water/README.md new file mode 100644 index 000000000..121f5425a --- /dev/null +++ b/policyengine_uk/utils/water/README.md @@ -0,0 +1,6 @@ +# Water bills projections + +In this folder, we have: + +* `forecast_water_bills.py` - A script that projects water bills based on historical data and proposed increases. +* `ofwat_increases.csv` A CSV with the data from [here](https://www.ccw.org.uk/our-work/price-review/how-much-will-my-water-and-sewerage-bills-increase-by-2030/breakdown-of-water-companies-bill-increases-2025-30/) with proposed pre-inflation increases for each water company. diff --git a/policyengine_uk/utils/water/forecast_water_bills.py b/policyengine_uk/utils/water/forecast_water_bills.py new file mode 100644 index 000000000..f44c90d9c --- /dev/null +++ b/policyengine_uk/utils/water/forecast_water_bills.py @@ -0,0 +1,80 @@ +import pandas as pd +from pathlib import Path + + +def project_water_bills(): + df_pre_2025 = pd.DataFrame( + { + "Year": [2021, 2022, 2023, 2024, 2025], + "Oftwat avg bills (real)": [486, 470, 486, 492, 503], + "CPIH": [113.1, 123.0, 129.9, 134.0, 139.0], + } + ) + + df_pre_2025["Oftwat avg bills (nominal)"] = ( + df_pre_2025["Oftwat avg bills (real)"] * df_pre_2025["CPIH"] / 100 + ) + df_pre_2025["Oftwat avg bills (nominal)"] = ( + df_pre_2025["Oftwat avg bills (nominal)"] + / df_pre_2025["Oftwat avg bills (nominal)"].iloc[0] + * 100 + ).round(1) + df_pre_2025["Nominal YoY change"] = ( + df_pre_2025["Oftwat avg bills (nominal)"].pct_change() * 100 + ).round(1) + + proposed_increases = pd.read_csv( + Path(__file__).parent / "ofwat_increases.csv" + ) + avg_bills_2025_onwards = ( + proposed_increases[proposed_increases.columns[1:]].mean()[1:].values + ) + + df_post_2025 = pd.DataFrame( + { + "Year": [2025, 2026, 2027, 2028, 2029], + "CPIH": [139.0, 142.2, 145.2, 148.2, 151.3], + "Pre-inflation avg bills (nominal)": avg_bills_2025_onwards, + } + ) + + # Add CPIH to each year's change + + df_post_2025["Avg bills (nominal)"] = df_post_2025[ + "Pre-inflation avg bills (nominal)" + ].values + df_post_2025["CPIH change"] = df_post_2025["CPIH"].pct_change() * 100 + + for year in range(2026, 2030): + row = df_post_2025[df_post_2025["Year"] == year].iloc[0] + cpi_change = ( + row["CPIH"] + / df_post_2025[df_post_2025["Year"] == year - 1]["CPIH"].values[0] + - 1 + ) * 100 + # Increase the nominal bills by the CPIH change + addition = df_post_2025.loc[ + df_post_2025["Year"] == year - 1, "Avg bills (nominal)" + ].values[0] * (cpi_change / 100) + # Add addition to this and future years + df_post_2025.loc[ + df_post_2025["Year"] >= year, "Avg bills (nominal)" + ] += addition + + df_post_2025["Relative change"] = ( + df_post_2025["Avg bills (nominal)"].pct_change() * 100 + ).round(1) + + df_post_2025 + + combined_water_forecast = pd.DataFrame( + { + "Year": list(range(2022, 2030)), + "Average nominal bills YoY change": df_pre_2025[ + "Nominal YoY change" + ].tolist()[1:] + + df_post_2025["Relative change"].tolist()[1:], + } + ) + + print(combined_water_forecast.to_markdown(index=False)) diff --git a/policyengine_uk/variables/input/consumption/property/water_and_sewerage_charges.py b/policyengine_uk/variables/input/consumption/property/water_and_sewerage_charges.py index df05a0929..beaa846a8 100644 --- a/policyengine_uk/variables/input/consumption/property/water_and_sewerage_charges.py +++ b/policyengine_uk/variables/input/consumption/property/water_and_sewerage_charges.py @@ -7,3 +7,4 @@ class water_and_sewerage_charges(Variable): label = "water and sewerage charges" documentation = "Total amount spent on water and sewerage charges" definition_period = YEAR + uprating = "gov.economic_assumptions.indices.ofwat.water_bills"