A day-ahead Delhi load forecast baseline from SLDC data
In this article (7 sections)
Related programme: the Data Science course (6 months; ₹1,30,000 (inclusive of GST) · EMI from ₹5,000/month).
Before anyone trains a gradient-boosted model or a neural network on electricity demand, someone should answer a plainer question. How accurate is "tomorrow looks like today" or "tomorrow looks like the same day last week"? This project builds those baselines for Delhi's total load, using the five-minute tables that the Delhi State Load Despatch Centre publishes for each day. It backtests the baselines honestly, with rolling origins, gaps handled and errors reported for the evening peak as well as the whole day. The output is a number every later model must beat, and a clear account of when the simple forecast fails.
Data
Fetch the day pages from SLDC's load data page, changing the date in mode=dd/mm/yyyy. Each shows a table headed "SCADA Load for" the date, with a TIMESLOT column at five-minute steps and columns for DELHI, BRPL, BYPL, NDPL, NDMC and MES. The page does not print a unit, so confirm it before you label axes.
Our pull of 6 to 26 September 2026 showed the problems a forecaster must handle. 8 September returned no table, 7 September stopped at 17:05, and complete days had between 278 and 288 of the expected 288 slots. The load also peaked late, typically between about 22:40 and midnight, and on some days the highest reading of the table was the 00:00 slot. A forecast that is judged at the peak therefore needs a peak window that crosses midnight. For a meaningful baseline, collect at least several months, including one change of season, and not just three weeks.
Why this is employer-relevant
Discoms, power traders, grid operators and energy-analytics firms schedule purchases a day ahead, so forecast error costs money. Outside energy, the same discipline matters for demand planning, staffing and capacity forecasts. Data science interviewers ask about baselines constantly, because candidates who skip them cannot say whether their model adds value.
Design decisions
- Resolution. Aggregate five-minute data to hourly means for the first version. Forecast 24 hourly values for the next day.
- Baselines. Three baselines are compared. Persistence uses the same hour yesterday. Seasonal naive uses the same hour on the same weekday last week. Blended uses the mean of the same hour over the last three same weekdays.
- Backtest. Use a rolling origin. For each forecast day, use only data available by the end of the previous day. There is no random train/test split for time series.
- Gaps. If an input hour is missing, the baseline for that hour is missing too. Do not fill it with a later value, since that leaks information. Score only hours where both forecast and actual exist, and report coverage.
- Metrics. Report MAE and MAPE over all hours, and the error at the daily peak hour, because the peak is what drives cost.
Code on a synthetic load series
The hourly series below is synthetic. It has a daily cycle with a late-evening peak, a weekly effect, a slow drift, noise and planted gaps. It is not Delhi's load, and the error figures it produces say nothing about real forecast accuracy.
import numpy as np
import pandas as pd
rng = np.random.default_rng(5)
idx = pd.date_range("2026-01-01", periods=24 * 42, freq="h")
h = idx.hour.to_numpy()
weekly = np.where(idx.dayofweek >= 5, -250, 0)
drift = np.linspace(0, 400, len(idx))
y = 4800 + 1100 * np.cos((h - 23) / 24 * 2 * np.pi) + weekly + drift + rng.normal(0, 120, len(idx))
load = pd.Series(y.round(1), index=idx)
load.iloc[24 * 20: 24 * 21] = np.nan # a missing day
load.iloc[24 * 30 + 17: 24 * 31] = np.nan # a partial day
fc = pd.DataFrame({
"actual": load,
"persistence": load.shift(24),
"seasonal_naive": load.shift(24 * 7),
"blended": pd.concat([load.shift(24 * 7 * k) for k in (1, 2, 3)], axis=1).mean(axis=1, skipna=False),
})
fc = fc[fc.index >= idx[0] + pd.Timedelta(days=21)] # first origin with three weeks of history
rows = []
for m in ["persistence", "seasonal_naive", "blended"]:
ok = fc[["actual", m]].dropna()
err = ok[m] - ok["actual"]
day = ok.groupby(ok.index.date)
peak_err = day.apply(lambda d: d.loc[d["actual"].idxmax(), m] - d["actual"].max())
rows.append({"method": m, "hours_scored": len(ok), "coverage_pct": round(100 * len(ok) / len(fc), 1),
"MAE": round(err.abs().mean(), 1), "MAPE_pct": round((err.abs() / ok["actual"]).mean() * 100, 2),
"peak_MAE": round(peak_err.abs().mean(), 1), "peak_bias": round(peak_err.mean(), 1)})
print(pd.DataFrame(rows).to_string(index=False))In this synthetic run, seasonal naive has the lowest all-hours MAE, because it gets the weekend right. Persistence has the smallest peak-hour error, because yesterday is closer to today's upward drift than last week is. No single baseline wins on every metric, which is why you report several. The blend looks back three weeks, so it trails the trend furthest, as its large negative peak bias shows. It also loses more forecast days to the planted gaps, which is why its coverage is lower. Report coverage beside the errors, or you may be comparing methods on different days.
Analysis plan
- 1Collect SLDC daily pages over a long window, keep the raw pages, and log missing and partial days.
- 2Aggregate to hourly means, keeping an hourly completeness count, and mark any hour with fewer than ten of twelve slots as missing.
- 3Backtest the three baselines with rolling origins and score MAE, MAPE and peak-hour error by month and by weekday.
- 4Add one simple improvement, such as a weekday-specific blend or a temperature feature from an official source, and test whether it beats the best baseline on the same hours.
- 5Examine the ten worst forecast days and describe what was unusual, such as a holiday, a data gap or a weather shift. Label any cause you infer as a hypothesis.
Deliverables
- Error table: method, coverage, MAE, MAPE, peak MAE and bias, overall and by month.
- Forecast chart: a week of actual load against each baseline.
- Worst-days table: date, error, and the suspected reason, marked as a hypothesis.
- README: data window, gaps and how they were scored, why random splits were not used, the unit and its source, and the baseline any future model must beat.
Interview questions
- Why is a random train/test split wrong for this data?
- Your model beats persistence on MAPE but not at the peak hour. Which matters more to a discom, and why?
- How would you handle holidays that fall on different weekdays each year?
- Two methods were scored on different numbers of hours. Why is that a problem, and how do you fix it?
Forecast evaluation of this kind is part of our Data Science programme, taught live online with optional in-person labs at our Noida centre. Start with the SLDC peak-load project to build the data pipeline first.
Pankit Kumar has 10 years in Data Science & AI, building and shipping production systems in regulated pharma and clinical environments. He is a freelance trainer at Boston Institute of Analytics, AnalytixLabs and Scaler, and has taught this material to thousands of working professionals.
This article is part of our Data Science programme — 6 months. From data foundations to machine learning, deep learning and deployment.
Explore Data Science