Free tutorials & notes in Hindi & English · Clean code examples 🧰 Free PDF & Image Tools →
⚙️ Week 8 – Advanced ML  ·  Day 49

Day 49: Time Series & Forecasting

⏱ 2.5 hours📶 Intermediate → Advanced🎯 100% self-study
Day 49 / 60

✅ Today you will learn

  • What makes time-series data special
  • Trend, seasonality and decomposition
  • Baselines: naive and seasonal-naive
  • ML forecasting with lag and calendar features
  • Holt-Winters and time-based validation

🏢 In the real world

Delhi’s schools and hospitals want a 30-day air-quality outlook to plan closures, masks and extra staff. Retailers want next month’s demand, banks next week’s cash withdrawals. All of these are forecasting problems – and the rules are different from normal ML because the future must never leak into the past.

Look at the series

Python
import pandas as pd
import matplotlib.pyplot as plt
w = pd.read_csv("delhi_weather.csv", parse_dates=["date"]).set_index("date").asfreq("D")
print(w.shape, "| missing days:", w["aqi"].isna().sum())
m = w["aqi"].resample("MS").mean()
fig, ax = plt.subplots(figsize=(10, 3.6))
ax.plot(w.index, w["aqi"], color="#cbd5e1", lw=0.7, label="daily")
ax.plot(m.index, m.values, color="#dc2626", marker="o", lw=2, label="monthly mean")
ax.set_title("Delhi AQI 2022–2024: strong yearly seasonality"); ax.legend(); plt.show()
(1096, 2) | missing days: 0
Daily and monthly Delhi AQI 2022 to 2024
The same shape repeats every year: bad in winter, clean in monsoon.

Trend + seasonality + noise

Python
import pandas as pd
import matplotlib.pyplot as plt
from statsmodels.tsa.seasonal import seasonal_decompose
w = pd.read_csv("delhi_weather.csv", parse_dates=["date"]).set_index("date")
weekly = w["aqi"].resample("W").mean()
dec = seasonal_decompose(weekly, model="additive", period=52)
fig = dec.plot(); fig.set_size_inches(10, 6.5); fig.suptitle("Decomposition of weekly AQI", y=1.01)
plt.tight_layout(); plt.show()
print("seasonal swing (max − min):", round(dec.seasonal.max() - dec.seasonal.min()), "AQI points")
seasonal swing (max − min): 242 AQI points
Seasonal decomposition of weekly AQI into trend, seasonal and residual
Seasonality dominates; the trend is almost flat; the residual is day-to-day noise.

Split by time, never randomly

Python
import pandas as pd
w = pd.read_csv("delhi_weather.csv", parse_dates=["date"]).set_index("date")
train, test = w.loc[:"2023-12-31", "aqi"], w.loc["2024-01-01":, "aqi"]
print("train:", train.index.min().date(), "→", train.index.max().date(), len(train))
print("test :", test.index.min().date(), "→", test.index.max().date(), len(test))
train: 2022-01-01 → 2023-12-31 730 test : 2024-01-01 → 2024-12-31 366
⚠️ Random splits leak the future

With train_test_split, a model could learn from 15 March to predict 14 March – impossible in real life. Always train on the past and test on the future (or use TimeSeriesSplit).

Baselines first

Python
import numpy as np
import pandas as pd
from sklearn.metrics import mean_absolute_error, mean_absolute_percentage_error
w = pd.read_csv("delhi_weather.csv", parse_dates=["date"]).set_index("date")["aqi"]
test = w.loc["2024"]
preds = {"naive (last value of 2023)": pd.Series(w.loc["2023-12-31"], index=test.index),
         "mean of 2022–23": pd.Series(w.loc[:"2023"].mean(), index=test.index),
         "seasonal naive (same day last year)": w.shift(365).loc["2024"]}
for name, p in preds.items():
    print(f"{name:36s} MAE {mean_absolute_error(test, p):6.1f}   MAPE {mean_absolute_percentage_error(test, p) * 100:5.1f}%")
naive (last value of 2023) MAE 106.3 MAPE 102.2% mean of 2022–23 MAE 71.9 MAPE 56.9% seasonal naive (same day last year) MAE 34.0 MAPE 23.2%
🧠 MAPE

Mean absolute percentage error: “on average we are 18% off”. Easy for managers, but unstable when actual values are near zero (sales of 0 units).

Machine learning with lag features

Python
import numpy as np
import pandas as pd
from sklearn.ensemble import HistGradientBoostingRegressor
from sklearn.metrics import mean_absolute_error
w = pd.read_csv("delhi_weather.csv", parse_dates=["date"]).set_index("date")
f = pd.DataFrame(index=w.index)
f["aqi"] = w["aqi"]
f["lag_365"] = w["aqi"].shift(365)                   # same day last year
f["lag_30"] = w["aqi"].shift(30)                     # a month ago (known 30 days ahead)
f["roll_30_lag_30"] = w["aqi"].shift(30).rolling(30).mean()
f["doy_sin"] = np.sin(2 * np.pi * w.index.dayofyear / 365.25)
f["doy_cos"] = np.cos(2 * np.pi * w.index.dayofyear / 365.25)
f["weekday"] = w.index.dayofweek
f = f.dropna()
tr, te = f.loc[:"2023"], f.loc["2024"]
feats = [c for c in f.columns if c != "aqi"]
model = HistGradientBoostingRegressor(max_iter=300, learning_rate=0.05, random_state=0).fit(tr[feats], tr["aqi"])
pred = pd.Series(model.predict(te[feats]), index=te.index)
print("training rows:", len(tr), "| features:", feats)
print(f"ML forecast MAE {mean_absolute_error(te['aqi'], pred):.1f}")
pred.to_frame("pred").assign(actual=te["aqi"]).to_csv("aqi_forecast_2024.csv")
training rows: 365 | features: ['lag_365', 'lag_30', 'roll_30_lag_30', 'doy_sin', 'doy_cos', 'weekday'] ML forecast MAE 26.8
💡 Only use what you will know

For a 30-day-ahead forecast, yesterday’s AQI is not available – so the shortest lag is 30 days. Matching the lags to the forecast horizon is the time-series version of avoiding leakage.

Holt-Winters exponential smoothing

Python
import pandas as pd
import matplotlib.pyplot as plt
from statsmodels.tsa.holtwinters import ExponentialSmoothing
from sklearn.metrics import mean_absolute_error
w = pd.read_csv("delhi_weather.csv", parse_dates=["date"]).set_index("date")["aqi"]
monthly = w.resample("MS").mean()
train, test = monthly.loc[:"2023"], monthly.loc["2024"]
hw = ExponentialSmoothing(train, trend=None, seasonal="additive", seasonal_periods=12).fit()
fc = hw.forecast(12)
snaive = monthly.shift(12).loc["2024"]
print(f"monthly MAE → Holt-Winters {mean_absolute_error(test, fc):.1f} | seasonal naive {mean_absolute_error(test, snaive):.1f}")
plt.figure(figsize=(10, 3.8))
plt.plot(monthly.index, monthly, color="#64748b", marker="o", label="actual")
plt.plot(fc.index, fc, color="#dc2626", marker="o", ls="--", label="Holt-Winters forecast 2024")
plt.axvspan(test.index.min(), test.index.max(), color="#fee2e2", alpha=0.5)
plt.title("Forecasting 2024 monthly AQI from 2022–23 only"); plt.legend(); plt.show()
monthly MAE → Holt-Winters 4.0 | seasonal naive 4.7
Monthly AQI actual values and Holt-Winters forecast for 2024
Trained only on 2022–23, the model predicts the 2024 winter spike well.

Time-series cross-validation

Python
import numpy as np
from sklearn.model_selection import TimeSeriesSplit
idx = np.arange(12)
for i, (tr, te) in enumerate(TimeSeriesSplit(n_splits=4, test_size=2).split(idx), 1):
    print(f"fold {i}: train months {tr.min()}–{tr.max()}  →  test months {te.tolist()}")
fold 1: train months 0–3 → test months [4, 5] fold 2: train months 0–5 → test months [6, 7] fold 3: train months 0–7 → test months [8, 9] fold 4: train months 0–9 → test months [10, 11]
MethodGood for
Seasonal naiveAlways – the baseline to beat
Holt-Winters / ETSClear trend + seasonality, few series
ARIMA / SARIMAClassic statistical forecasting, short-term
ProphetBusiness series with holidays (Diwali!), easy to use
ML with lags (boosting)Many related series, extra features like weather, promotions

🏅 Job skills unlocked today

✔ Decompose a time series✔ Split and validate by time✔ Beat naive and seasonal-naive baselines✔ Forecast with lag features and Holt-Winters

Practice tasks

🏋️ Practice – do it yourself

Task 1: Forecast 2024 monthly average temperature with Holt-Winters and compare with seasonal naive.
Show solution
Python
import pandas as pd
from statsmodels.tsa.holtwinters import ExponentialSmoothing
from sklearn.metrics import mean_absolute_error
t = pd.read_csv("delhi_weather.csv", parse_dates=["date"]).set_index("date")["avg_temp_c"].resample("MS").mean()
fc = ExponentialSmoothing(t.loc[:"2023"], seasonal="additive", seasonal_periods=12).fit().forecast(12)
print("HW", round(mean_absolute_error(t.loc["2024"], fc), 2), "| seasonal naive", round(mean_absolute_error(t.loc["2024"], t.shift(12).loc["2024"]), 2))
HW 0.28 | seasonal naive 0.45
Task 2: Build weekly BazaarKart revenue for 2024 and forecast the last 8 weeks with a 4-week moving average. Why does it over-forecast the weeks after the festive peak?
Show solution
Python
import pandas as pd
s = pd.read_csv("sales_data.csv", parse_dates=["order_date"]); s["rev"] = s["quantity"] * s["unit_price"] * (1 - s["discount"])
wk = s.set_index("order_date")["rev"].resample("W").sum()
ma = wk.shift(1).rolling(4).mean()
print(pd.DataFrame({"actual": wk, "moving_avg_forecast": ma}).tail(8).round(0))
actual moving_avg_forecast order_date 2024-11-17 149198.0 248842.0 2024-11-24 212992.0 235032.0 2024-12-01 174036.0 239370.0 2024-12-08 110144.0 237219.0 2024-12-15 147478.0 161593.0 2024-12-22 86928.0 161162.0 2024-12-29 96331.0 129647.0 2025-01-05 30867.0 110220.0
Task 3: Add the lag-7 feature to the ML model for a 7-day-ahead forecast. How much does MAE improve?

Quick quiz

🧠 Quick Quiz – test yourself

1. Why not use train_test_split for time series?
Train on past, test on future.
2. Seasonal naive predicts…
e.g. same day last year.
3. A lag-30 feature means…
shift(30).
4. MAPE = 12% means…
Mean absolute percentage error.
5. Holt-Winters models…
Exponential smoothing of each part.

Interview corner

💼 Interview Corner

How is time-series validation different?

Data must be split by time – train on earlier periods, test on later ones – using expanding or rolling windows (TimeSeriesSplit). Features must only use information available at forecast time.

What baselines do you use for forecasting?

Naive (last value), seasonal naive (same period last season) and a moving average. A complex model is only useful if it clearly beats them.

Summary

  • Time series = trend + seasonality + noise.
  • Split by time; beat naive and seasonal-naive baselines.
  • ML with lags matched to the horizon + calendar features works well.
  • Holt-Winters is a strong simple seasonal model; report MAE and MAPE.
➡️ Next

Day 50: deploy your model – save it with joblib and serve predictions with Streamlit and Flask.

Frequently Asked Questions

What is stationarity?
A series whose mean and variance do not change over time. ARIMA needs it (use differencing); ML and Holt-Winters handle trend and seasonality more directly.
Can I add Diwali as a feature?
Yes – holiday flags or “days to Diwali” are powerful features for Indian retail and pollution data. Prophet supports holiday lists directly.
← Data Science (60 Days)
🔗

Share this topic with a friend

यह topic किसी दोस्त को भेजें

Found it useful? Send it to a classmate learning the same thing.

अच्छा लगा? जो दोस्त यही सीख रहा है, उसे भेज दीजिए।

Next topicDay 50: Save & Deploy Models (Streamlit, Flask) Your progress: 49 / 60 (82%) · Lesson 50 / 60 Continue →

How was this lesson?

Your feedback helps us make the content better.

Write a review or suggestion (optional)

After approval, your name and city may appear publicly with your review. Please do not write your email or phone number.

▶ CodingEasily on YouTube

Learn with video lessons in easy Hindi

Featured video: RDBMS Part 3 – Class XI Commerce IT

▶ Subscribe – it’s free