Load Forecasting and Demand Analytics

The Business Problem: Balancing Supply and Demand in Real Time

Electric power systems are unique among industries in that supply and demand must remain balanced continuously. Electricity is consumed the instant it is produced, and large-scale storage remains limited. This makes accurate demand forecasting central to every decision a utility makes.

If forecasts overestimate demand, utilities commit more generation than needed, incurring unnecessary costs and running plants inefficiently. If forecasts underestimate demand, the grid faces shortages, risking frequency drops, emergency dispatch of expensive peaking units, or even load shedding. This balancing act is complicated by rising demand volatility: distributed solar generation shifts net load profiles, electric vehicles create new evening peaks, and extreme weather events drive sudden spikes in heating and cooling loads.

Historically, utilities relied on deterministic or statistical models built on historical patterns. These methods assumed that tomorrow would look much like yesterday, adjusted for seasonal effects and known events. That assumption no longer holds in an era of climate-driven weather extremes, rapid electrification, and high DER penetration. The cost of forecast errors is increasing, not just financially but in terms of reliability and customer trust.

The Analytics Solution: Data-Driven Load Forecasting

Modern load forecasting applies machine learning to capture complex relationships between weather, calendar effects, and consumption behavior. Temperature remains the single largest driver of demand in most regions, but other factors—humidity, wind, cloud cover, time of day, day of week, and holidays—play significant roles. For grids with significant rooftop solar, net load must account for both consumption and behind-the-meter generation.

Short-term forecasts (minutes to hours ahead) help operators adjust dispatch and manage ramping constraints. Day-ahead forecasts guide market bids and generator commitments. Long-term forecasts inform capital planning, feeder upgrades, and rate design. Each horizon demands different models and data granularity, but they share a common goal: reduce uncertainty and enable better operational and financial decisions.

Machine learning enhances forecasting by learning nonlinear interactions. For example, demand rises sharply once temperatures cross certain thresholds, reflecting air conditioning saturation effects. Neural networks can capture such nonlinearities better than traditional linear regression. Time series models like ARIMA or SARIMA remain valuable for capturing autoregressive patterns, while ensemble methods blend multiple models to improve accuracy.

Linking Forecasting to Utility Operations

Accurate forecasts drive every part of utility operations. For system operators, improved short-term forecasts reduce reliance on expensive reserves and minimize frequency excursions. For distribution planners, neighborhood-level forecasts flag feeders at risk of overload as electric vehicle adoption accelerates. For energy traders, better day-ahead forecasts sharpen market positions, reducing exposure to price volatility.

Even customer-facing programs rely on forecasting. Demand response events require accurate predictions of when peak conditions will occur to maximize participation and avoid unnecessary interruptions. Time-of-use rates depend on understanding how customers shift load in response to pricing signals.

Load forecasting is also intertwined with renewable integration. As rooftop solar expands, midday net load dips, while steep evening ramps strain peaking plants. Forecasting both gross load and distributed generation is critical to managing this two-sided variability. Utilities that fail to adapt risk both operational stress and financial penalties in competitive markets.

Transition to the Demo

In this chapter’s demo, we will build a forecasting pipeline that reflects these challenges. We will:

We will focus on short-term (hourly) and day-ahead horizons, showing how even modest improvements in forecast accuracy translate into operational and cost benefits. By grounding the methods in realistic scenarios, the demo connects statistical modeling directly to the core business need of balancing supply and demand in a modern, data-driven grid.

This chapter establishes forecasting as both an analytic and operational discipline: one that informs decisions across planning, operations, markets, and customer engagement, making it a cornerstone of utility machine learning efforts.

pyfile shortcode: missing param 'file'. Example: {{< pyfile file="script.py" >}}

Code

"""
Chapter 4: Load Forecasting and Demand Analytics
Time series forecasting using ARIMA, Prophet, and LSTM (Darts).
"""

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from statsmodels.tsa.arima.model import ARIMA
from prophet import Prophet
from darts import TimeSeries
from darts.models import RNNModel
from darts.metrics import rmse

def generate_synthetic_load():
    """
    Generate synthetic hourly load data for one year with seasonality and noise.
    """
    date_rng = pd.date_range(start="2022-01-01", periods=24*365, freq="H")
    base_load = 800 + 150 * np.sin(2 * np.pi * date_rng.dayofyear / 365)
    daily_cycle = 50 * np.sin(2 * np.pi * date_rng.hour / 24)
    noise = np.random.normal(0, 20, len(date_rng))
    load = base_load + daily_cycle + noise
    return pd.DataFrame({"timestamp": date_rng, "Load_MW": load})

def plot_load(df):
    """
    Plot hourly load.
    """
    plt.figure(figsize=(12, 4))
    plt.plot(df["timestamp"], df["Load_MW"], color="black")
    plt.xlabel("Time")
    plt.ylabel("Load (MW)")
    plt.title("Hourly Load Profile")
    plt.tight_layout()
    plt.savefig("chapter4_load.png")
    plt.show()

def arima_forecast(df):
    """
    Forecast using ARIMA.
    """
    ts = df.set_index("timestamp")["Load_MW"]
    model = ARIMA(ts, order=(3, 1, 2))
    fit = model.fit()
    forecast = fit.forecast(steps=24*7)  # 1-week forecast

    plt.figure(figsize=(12, 4))
    plt.plot(ts[-24*7:], label="Observed", color="gray")
    plt.plot(forecast, label="ARIMA Forecast", color="black")
    plt.xlabel("Time")
    plt.ylabel("Load (MW)")
    plt.legend()
    plt.tight_layout()
    plt.savefig("chapter4_arima.png")
    plt.show()

def prophet_forecast(df):
    """
    Forecast using Prophet.
    """
    df_prophet = df.rename(columns={"timestamp": "ds", "Load_MW": "y"})
    model = Prophet(daily_seasonality=True, yearly_seasonality=True)
    model.fit(df_prophet)
    future = model.make_future_dataframe(periods=24*7, freq="H")
    forecast = model.predict(future)

    fig = model.plot(forecast)
    plt.title("Prophet Forecast")
    plt.tight_layout()
    plt.savefig("chapter4_prophet.png")
    plt.show()

def lstm_forecast(df):
    """
    Forecast using LSTM (Darts).
    """
    series = TimeSeries.from_dataframe(df, "timestamp", "Load_MW")
    train, val = series[:-24*7], series[-24*7:]
    model = RNNModel(model="LSTM", input_chunk_length=48, output_chunk_length=24, n_epochs=50, random_state=42)
    model.fit(train)
    pred = model.predict(len(val))

    plt.figure(figsize=(12, 4))
    train[-24*7:].plot(label="Observed", lw=1, color="gray")
    pred.plot(label="LSTM Forecast", lw=2, color="black")
    plt.legend()
    plt.title(f"LSTM Forecast (RMSE: {rmse(val, pred):.2f})")
    plt.tight_layout()
    plt.savefig("chapter4_lstm.png")
    plt.show()

if __name__ == "__main__":
    df_load = generate_synthetic_load()
    plot_load(df_load)
    arima_forecast(df_load)
    prophet_forecast(df_load)
    lstm_forecast(df_load)