Skip to content
Merged
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
4 changes: 4 additions & 0 deletions changelog_entry.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- bump: minor
changes:
added:
- Water bills projections.
1 change: 1 addition & 0 deletions docs/book/_toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ parts:
- caption: Economic assumptions
chapters:
- file: assumptions/growthfactors
- file: assumptions/water-bills
- caption: Validation
chapters:
- file: validation/hbai
Expand Down
74 changes: 74 additions & 0 deletions docs/book/assumptions/water-bills.md
Original file line number Diff line number Diff line change
@@ -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% |
7 changes: 7 additions & 0 deletions policyengine_uk/data/dataset_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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/
6 changes: 6 additions & 0 deletions policyengine_uk/utils/water/README.md
Original file line number Diff line number Diff line change
@@ -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.
80 changes: 80 additions & 0 deletions policyengine_uk/utils/water/forecast_water_bills.py
Original file line number Diff line number Diff line change
@@ -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))
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Loading