Power Flow Time Series Data¶
This tutorial covers how to access and analyze power flow time series data from UK Power Networks using the powerflow orchestrator.
What is Power Flow Data?
Power flow data represents operational time series measurements from circuits and transformers across the UK Power Networks distribution network. This data is essential for:
- Understanding network loading patterns
- Analyzing peak demand periods
- Planning network reinforcement
- Studying seasonal variations in power flow
What You'll Learn:
- Introduction to power flow datasets
- Using the powerflow orchestrator
- Listing available datasets
- Getting circuit time series data (132kV, 33kV)
- Getting transformer time series data (grid, primary)
- Discovering circuits and transformers
- Filtering by licence area (EPN, SPN, LPN)
- Exporting data for analysis
Prerequisites: Complete 01-getting-started.ipynb first.
- These tutorials require additional dependencies. Install them with
pip install "ukpyn[all]"— see Tutorial 01 for full setup instructions
import ukpyn
ukpyn.check_api_key()
print("API key configured!")
1. Introduction to Power Flow Data¶
UK Power Networks publishes operational time series data for:
Circuit Data¶
- 132kV Circuits - Extra High Voltage circuits connecting grid substations
- 33kV Circuits - High Voltage circuits connecting primary substations
Transformer Data¶
- Grid Transformers - Transform 132kV to 33kV at grid substations
- Primary Transformers - Transform 33kV to 11kV at primary substations
Data Granularity¶
- Monthly - Aggregated monthly statistics (good for trend analysis)
- Half-hourly - 30-minute interval readings (detailed operational data)
Licence Areas¶
UK Power Networks operates three Distribution Network Operator (DNO) licence areas:
- EPN - Eastern Power Networks
- SPN - South Eastern Power Networks
- LPN - London Power Networks
2. Using the Powerflow Orchestrator¶
The powerflow orchestrator provides a simple interface for accessing power flow time series data.
Import it directly from ukpyn:
from ukpyn import powerflow
print("Powerflow orchestrator imported successfully!")
# We have a convenient way to check that the orchestrator is working, by printing the methods on the object.
print(repr(powerflow))
3. Listing Available Datasets¶
The available_datasets attribute shows all powerflow datasets you can access.
# List all available powerflow datasets
print("Available Powerflow Datasets:")
print("=" * 50)
for dataset in powerflow.available_datasets:
print(f" - {dataset}")
print(f"\nTotal: {len(powerflow.available_datasets)} datasets")
# Expected output:
# Available Powerflow Datasets:
# ==================================================
# - 132kv_monthly
# - 132kv_half_hourly
# - 33kv_monthly
# - 33kv_half_hourly_epn (Note the Specificty here for area at this granularity)
# - 33kv_half_hourly_spn (Note the Specificty here for area at this granularity)
# - grid_monthly
# - grid_half_hourly
# - grid_transformer_monthly
# - grid_transformer_half_hourly
# - primary_monthly
# - primary_half_hourly_epn (Note the Specificty here for area at this granularity)
# - primary_half_hourly_spn (Note the Specificty here for area at this granularity)
# - primary_transformer_monthly
# - primary_transformer_half_hourly
# - 132kv_monthly_epn
# - 132kv_monthly_spn
#
# Total: 14 datasets
# Group datasets by category
print("Datasets by Category:")
print("=" * 50)
categories = {
"132kV Circuits": [
d for d in powerflow.available_datasets if d.startswith("132kv")
],
"33kV Circuits": [d for d in powerflow.available_datasets if d.startswith("33kv")],
"Grid Transformers": [
d for d in powerflow.available_datasets if d.startswith("grid")
],
"Primary Transformers": [
d for d in powerflow.available_datasets if d.startswith("primary")
],
}
for category, datasets in categories.items():
print(f"\n{category}:")
for ds in datasets:
print(f" - {ds}")
# Expected output:
# Datasets by Category:
# ==================================================
#
# 132kV Circuits:
# - 132kv_monthly
# - 132kv_half_hourly
# - 132kv_monthly_epn
# - 132kv_monthly_spn
#
# 33kV Circuits:
# - 33kv_monthly
# - 33kv_half_hourly
#
# Grid Transformers:
# - grid_monthly
# - grid_half_hourly
# - grid_transformer_monthly
# - grid_transformer_half_hourly
#
# Primary Transformers:
# - primary_monthly
# - primary_half_hourly
# - primary_transformer_monthly
# - primary_transformer_half_hourly
4. Getting Circuit Time Series Data¶
Use get_circuit_timeseries() to retrieve operational data for 132kV and 33kV circuits.
Parameters:¶
voltage:'132kv'or'33kv'granularity:'monthly'or'half_hourly'circuit_id: Filter by specific circuit (optional)licence_area: Filter by'EPN','SPN', or'LPN'(optional)start_date: Start date in ISO format (optional)end_date: End date in ISO format (optional)limit: Maximum records to return (default 1000)
# Get 132kV circuit data (monthly granularity)
data_132kv = powerflow.get_circuit_timeseries(
voltage="132kv", granularity="monthly", limit=10
)
print("132kV Circuit Monthly Data")
print(f"Total records available: {data_132kv.total_count}")
print(f"Records returned: {len(data_132kv.records)}")
print("\n" + "-" * 60)
# Display first few records
for i, record in enumerate(data_132kv.records[:3], 1):
print(f"\nRecord {i}:")
if record.fields:
for key, value in list(record.fields.items())[:6]:
print(f" {key}: {value}")
# Expected output:
# 132kV Circuit Monthly Data
# Total records available: 15432
# Records returned: 10
#
# ------------------------------------------------------------
#
# Record 1:
# circuit_id: ABC123
# licence_area: EPN
# timestamp: 2024-01-01T00:00:00+00:00
# active_power_mw: 45.2
# reactive_power_mvar: 12.1
# apparent_power_mva: 46.8
# ...
# Get 33kV circuit data (half-hourly granularity)
# Note: 33kV half-hourly data is split by licence area, so we must specify one
data_33kv = powerflow.get_circuit_timeseries(
voltage="33kv",
granularity="half_hourly",
licence_area="EPN", # Required for half-hourly data
limit=10,
)
print("33kV Circuit Half-Hourly Data (EPN)")
print(f"Total records available: {data_33kv.total_count}")
print(f"Records returned: {len(data_33kv.records)}")
print("\n" + "-" * 60)
# Display sample record structure
if data_33kv.records:
sample = data_33kv.records[0]
print("\nSample record fields:")
if sample.fields:
for key in sample.fields:
print(f" - {key}")
# Expected output:
# 33kV Circuit Half-Hourly Data (EPN)
# Total records available: 1234567
# Records returned: 10
#
# ------------------------------------------------------------
#
# Sample record fields:
# - circuit_id
# - licence_area
# - timestamp
# - active_power_mw
# - reactive_power_mvar
# - ...
Important Note on Data Availability:
Some datasets are only available split by licence area:
- 33kV half-hourly - Available as
33kv_half_hourly_epnand33kv_half_hourly_spn(not combined) - Primary transformer half-hourly - Available as
primary_half_hourly_epnandprimary_half_hourly_spn(not combined)
For these datasets, you must specify the licence_area parameter ('EPN' or 'SPN') when using granularity='half_hourly'.
# Filter circuit data by date range
data_filtered = powerflow.get_circuit_timeseries(
voltage="132kv",
granularity="monthly",
start_date="2024-01-01",
end_date="2024-06-30",
limit=20,
)
print("132kV Circuits (Jan-Jun 2024)")
print(f"Total records: {data_filtered.total_count}")
print(f"Records returned: {len(data_filtered.records)}")
# Expected output:
# 132kV Circuits (Jan-Jun 2024)
# Total records: 1284
# Records returned: 20
5. Getting Transformer Time Series Data¶
Use get_transformer_timeseries() to retrieve operational data for grid and primary transformers.
Parameters:¶
transformer_type:'grid'or'primary'granularity:'monthly'or'half_hourly'transformer_id: Filter by specific transformer (optional)licence_area: Filter by'EPN','SPN', or'LPN'(optional)start_date: Start date in ISO format (optional)end_date: End date in ISO format (optional)limit: Maximum records to return (default 1000)
# Get grid transformer data (monthly)
grid_data = powerflow.get_transformer_timeseries(
transformer_type="grid", granularity="monthly", limit=10
)
print("Grid Transformer Monthly Data")
print(f"Total records available: {grid_data.total_count}")
print(f"Records returned: {len(grid_data.records)}")
print("\n" + "-" * 60)
# Display first few records
for i, record in enumerate(grid_data.records[:3], 1):
print(f"\nRecord {i}:")
if record.fields:
for key, value in list(record.fields.items())[:6]:
print(f" {key}: {value}")
# Expected output:
# Grid Transformer Monthly Data
# Total records available: 8765
# Records returned: 10
#
# ------------------------------------------------------------
#
# Record 1:
# transformer_id: GT001
# licence_area: LPN
# timestamp: 2024-01-01T00:00:00+00:00
# loading_percent: 62.5
# active_power_mw: 78.3
# ...
# Get primary transformer data (half-hourly)
# Note: Primary half-hourly data is split by licence area, so we must specify one
primary_data = powerflow.get_transformer_timeseries(
transformer_type="primary",
granularity="half_hourly",
licence_area="SPN", # Required for primary half-hourly data
limit=10,
)
print("Primary Transformer Half-Hourly Data (SPN)")
print(f"Total records available: {primary_data.total_count}")
print(f"Records returned: {len(primary_data.records)}")
print("\n" + "-" * 60)
# Display sample record structure
if primary_data.records:
sample = primary_data.records[0]
print("\nSample record fields:")
if sample.fields:
for key in sample.fields:
print(f" - {key}")
# Expected output:
# Primary Transformer Half-Hourly Data (SPN)
# Total records available: 2345678
# Records returned: 10
#
# ------------------------------------------------------------
#
# Sample record fields:
# - transformer_id
# - licence_area
# - timestamp
# - loading_percent
# - ...
# Compare grid vs primary transformer counts
grid_monthly = powerflow.get_transformer_timeseries(
transformer_type="grid", granularity="monthly", limit=1
)
primary_monthly = powerflow.get_transformer_timeseries(
transformer_type="primary", granularity="monthly", limit=1
)
print("Transformer Data Comparison (Monthly)")
print("=" * 50)
print(f"Grid transformers: {grid_monthly.total_count:>10,} records")
print(f"Primary transformers: {primary_monthly.total_count:>10,} records")
# Expected output:
# Transformer Data Comparison (Monthly)
# ==================================================
# Grid transformers: 8,765 records
# Primary transformers: 34,521 records
6. Discovering Circuits and Transformers¶
Before fetching detailed half-hourly data, use the discovery functions to explore what circuits and transformers are available.
# Discover available 132kV circuits
circuits_132kv = powerflow.discover_circuits(voltage="132kv", limit=20)
print("Discovering 132kV Circuits")
print(f"Total records: {circuits_132kv.total_count}")
print("\n" + "-" * 60)
# Extract unique circuit IDs from the results
circuit_ids = set()
for record in circuits_132kv.records:
if record.fields and "circuit_id" in record.fields:
circuit_ids.add(record.fields["circuit_id"])
print(f"\nUnique circuits in sample: {len(circuit_ids)}")
print("\nSample circuit IDs:")
for cid in list(circuit_ids)[:5]:
print(f" - {cid}")
# Expected output:
# Discovering 132kV Circuits
# Total records: 15432
#
# ------------------------------------------------------------
#
# Unique circuits in sample: 18
#
# Sample circuit IDs:
# - CKT_132_001
# - CKT_132_002
# - CKT_132_003
# - CKT_132_004
# - CKT_132_005
# Discover available 33kV circuits
circuits_33kv = powerflow.discover_circuits(voltage="33kv", limit=20)
print("Discovering 33kV Circuits")
print(f"Total records: {circuits_33kv.total_count}")
# Expected output:
# Discovering 33kV Circuits
# Total records: 45678
# Discover available transformers
grid_transformers = powerflow.discover_transformers(transformer_type="grid", limit=20)
primary_transformers = powerflow.discover_transformers(
transformer_type="primary", limit=20
)
print("Transformer Discovery")
print("=" * 50)
print(f"Grid transformer records: {grid_transformers.total_count:>10,}")
print(f"Primary transformer records: {primary_transformers.total_count:>10,}")
# Extract unique transformer IDs
grid_ids = set()
for record in grid_transformers.records:
if record.fields and "transformer_id" in record.fields:
grid_ids.add(record.fields["transformer_id"])
print(f"\nUnique grid transformers in sample: {len(grid_ids)}")
# Expected output:
# Transformer Discovery
# ==================================================
# Grid transformer records: 8,765
# Primary transformer records: 34,521
#
# Unique grid transformers in sample: 15
7. Filtering by Licence Area¶
UK Power Networks operates three licence areas. You can filter data by licence area to focus on a specific region.
| Code | Area | Region |
|---|---|---|
| EPN | Eastern Power Networks | East of England |
| SPN | South Eastern Power Networks | South East England |
| LPN | London Power Networks | Greater London |
# Get circuit data for Eastern Power Networks (EPN)
epn_circuits = powerflow.get_circuit_timeseries(
voltage="132kv", granularity="monthly", licence_area="EPN", limit=10
)
print("EPN 132kV Circuits")
print(f"Total records: {epn_circuits.total_count}")
# Verify all records are from EPN
print("\nVerifying licence areas:")
areas = set()
for record in epn_circuits.records:
if record.fields and "licence_area" in record.fields:
areas.add(record.fields["licence_area"])
print(f" Areas in results: {areas}")
# Expected output:
# EPN 132kV Circuits
# Total records: 5234
#
# Verifying licence areas:
# Areas in results: {'EPN'}
# Compare data across all three licence areas
licence_areas = ["EPN", "SPN", "LPN"]
print("132kV Circuit Records by Licence Area")
print("=" * 50)
for area in licence_areas:
data = powerflow.get_circuit_timeseries(
voltage="132kv", granularity="monthly", licence_area=area, limit=1
)
print(f"{area}: {data.total_count:>10,} records")
# Expected output:
# 132kV Circuit Records by Licence Area
# ==================================================
# EPN: 5,234 records
# SPN: 4,123 records
# LPN: 6,075 records
# Filter transformers by licence area
lpn_transformers = powerflow.get_transformer_timeseries(
transformer_type="primary", granularity="monthly", licence_area="LPN", limit=10
)
print("LPN Primary Transformers")
print(f"Total records: {lpn_transformers.total_count}")
print(f"Records returned: {len(lpn_transformers.records)}")
# Expected output:
# LPN Primary Transformers
# Total records: 12456
# Records returned: 10
# Discover circuits in a specific licence area
spn_circuits = powerflow.discover_circuits(voltage="33kv", licence_area="SPN", limit=50)
print("Discovering 33kV Circuits in SPN")
print(f"Total records: {spn_circuits.total_count}")
# Extract unique circuit IDs
spn_circuit_ids = set()
for record in spn_circuits.records:
if record.fields and "circuit_id" in record.fields:
spn_circuit_ids.add(record.fields["circuit_id"])
print(f"Unique circuits in sample: {len(spn_circuit_ids)}")
# Expected output:
# Discovering 33kV Circuits in SPN
# Total records: 15678
# Unique circuits in sample: 42
# Export 132kV circuit data to CSV
csv_data = powerflow.export(dataset="132kv_monthly", format="csv", limit=100)
print(f"Exported {len(csv_data)} bytes of CSV data")
print("\nPreview (first 500 characters):")
print("-" * 60)
print(csv_data.decode("utf-8")[:500])
# Expected output:
# Exported 12456 bytes of CSV data
#
# Preview (first 500 characters):
# ------------------------------------------------------------
# circuit_id;licence_area;timestamp;active_power_mw;reactive_power_mvar
# CKT_001;EPN;2024-01-01T00:00:00+00:00;45.2;12.1
# ...
# Save exported data to file (optional)
from pathlib import Path
csv_data = powerflow.export(
dataset="132kv_monthly",
format="csv",
limit=1000,
)
save_dir = None # Set to a directory (e.g. "exports") to enable writing files.
if save_dir:
output_file = Path(save_dir) / "powerflow_132kv_export.csv"
output_file.parent.mkdir(parents=True, exist_ok=True)
with open(output_file, "wb") as f:
f.write(csv_data)
print(f"Saved {len(csv_data):,} bytes to {output_file}")
else:
print(
f"Exported {len(csv_data):,} bytes (file save skipped; set save_dir to enable writing)."
)
# Expected output:
# Saved 124,567 bytes to powerflow_132kv_export.csv
# Export to JSON format
import json
json_data = powerflow.export(dataset="grid_monthly", format="json", limit=10)
# Parse and display
records = json.loads(json_data)
print(f"Exported {len(records)} records as JSON")
print("\nFirst record:")
print(json.dumps(records[0], indent=2))
# Expected output:
# Exported 10 records as JSON
#
# First record:
# {
# "transformer_id": "GT001",
# "licence_area": "LPN",
# "timestamp": "2024-01-01T00:00:00+00:00",
# "loading_percent": 62.5,
# ...
# }
# Working with pandas (optional)
# Requires: pip install pandas
try:
from io import BytesIO
import pandas as pd
# Export to CSV and load into pandas
csv_data = powerflow.export(dataset="132kv_monthly", format="csv", limit=500)
# Create DataFrame (note: ODS uses semicolon separator)
df = pd.read_csv(BytesIO(csv_data), sep=";")
print(f"DataFrame shape: {df.shape}")
print(f"\nColumns: {list(df.columns)}")
print("\nData types:")
print(df.dtypes)
print("\nFirst 5 rows:")
display(df.head())
except ImportError:
print("pandas not installed. Install with: pip install pandas")
# Expected output:
# DataFrame shape: (500, 8)
#
# Columns: ['circuit_id', 'licence_area', 'timestamp', 'active_power_mw', ...]
#
# Data types:
# circuit_id object
# licence_area object
# timestamp object
# active_power_mw float64
# ...
#
# First 5 rows:
# (DataFrame output)
# Using the generic get() function for direct dataset access
# This provides low-level access to any powerflow dataset
data = powerflow.get(dataset="primary_monthly", limit=5)
print("Direct access to 'primary_monthly' dataset")
print(f"Total records: {data.total_count}")
print(f"Records returned: {len(data.records)}")
# Expected output:
# Direct access to 'primary_monthly' dataset
# Total records: 34521
# Records returned: 5
Summary¶
You've learned how to:
- Import the powerflow orchestrator -
from ukpyn import powerflow - List available datasets -
powerflow.available_datasets - Get circuit time series -
powerflow.get_circuit_timeseries(voltage, granularity, ...) - Get transformer time series -
powerflow.get_transformer_timeseries(transformer_type, granularity, ...) - Discover assets -
powerflow.discover_circuits()andpowerflow.discover_transformers() - Filter by licence area - Use
licence_area='EPN','SPN', or'LPN' - Export data -
powerflow.export(dataset, format, ...)
Key Functions Reference¶
| Function | Purpose |
|---|---|
powerflow.available_datasets |
List all available dataset keys |
powerflow.get_circuit_timeseries() |
Get circuit operational data |
powerflow.get_transformer_timeseries() |
Get transformer operational data |
powerflow.discover_circuits() |
Discover available circuits |
powerflow.discover_transformers() |
Discover available transformers |
powerflow.get() |
Generic dataset access |
powerflow.export() |
Export data to CSV/JSON/XLSX |
Parameter Options¶
| Parameter | Options |
|---|---|
voltage |
'132kv', '33kv' |
granularity |
'monthly', 'half_hourly' |
transformer_type |
'grid', 'primary' |
licence_area |
'EPN', 'SPN', 'LPN' |
Next Steps¶
- Explore the LTDS orchestrator for network topology data
- Combine powerflow data with network models for comprehensive analysis
- Use pandas for advanced time series analysis and visualization
# Clean up - remove exported files (optional)
import os
files_to_remove = ["powerflow_132kv_export.csv"]
for f in files_to_remove:
if os.path.exists(f):
os.remove(f)
print(f"Removed {f}")
print("\nTutorial complete!")
# Expected output:
# Removed powerflow_132kv_export.csv
#
# Tutorial complete!