Skip to main content

Module 1 — What makes a time series different from other data

Every previous course in this track assumed rows were independent. Two customers, two transactions, two images — you could shuffle them without changing what a model could learn. This course begins by naming a class of data where that assumption is wrong, and then showing what happens when a team pretends it still holds.

Our thread throughout the course is a daily demand forecast for a pharmacy chain: four years of sales per store per day, with weekly and yearly seasonality, national holidays and occasional promotions. Each store's series has around 1 460 observations, one per day. The team has to hand procurement a 28-day forecast every Monday morning.

The observation that matters is the order

A time series is a sequence of measurements y1,y2,,yTy_1, y_2, \dots, y_T indexed by time. What makes it different from a regular table is that the value at tt depends on values at t1t-1, t2t-2, t7t-7, and so on. Yesterday tells you something about today; a table row about customer A tells you very little about customer B.

Three consequences follow, and each one shapes the rest of the course.

First, the order is information. Reshuffling a time series destroys it. Any preprocessing step that mixes past and future — even a global scaler fitted on the whole series — will leak information the model would not have at inference time.

Second, residuals are not independent. Two consecutive prediction errors from a mediocre model tend to be positive together, or negative together. That kills the assumption behind classical cross-validation and behind confidence intervals computed on independent errors. Module 9 will replace both with rolling-origin evaluation.

Third, you forecast forward. The model receives everything before tt and produces a prediction for one or more future points. The number of future points is the horizon hh. For our pharmacy, h=28h = 28 days. A one-step forecast (predict tomorrow) is a different problem from a 28-step forecast (predict the whole month); a model excellent at the first often collapses on the second.

Frequency, horizon and the vocabulary of the field

We will use a small vocabulary consistently.

  • Frequency: how often the series is sampled. Ours is daily. Others are hourly (electricity), monthly (macroeconomics), five-minutely (web traffic).
  • Horizon hh: how many steps ahead we forecast. Point forecasts return a single value at each of those hh steps; probabilistic forecasts return a distribution or an interval.
  • Lookback or history window: how many past steps the model sees when producing a forecast. A weekly model might need at least 14 days; a yearly-seasonal model at least 365 or 730.
  • Backtest: replaying the model on historical data as if it had been deployed then, moving the origin forward. It is our substitute for cross-validation and it lives in module 9.

We will also insist on one convention: the training set is called history, and the held-out block that follows it in time is called the test window. Never "test set" without saying "chronologically after training". Sloppy vocabulary is where leakage sneaks in.

The mistake that ruins everything: the shuffled split

We ran the following on our pharmacy data, and it produced the most misleading result of any experiment in this course.

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_absolute_error

df = pd.read_parquet("pharmacy_daily.parquet")
df["dow"] = df["date"].dt.dayofweek
df["month"] = df["date"].dt.month
df["lag_1"] = df["units"].shift(1)
df["lag_7"] = df["units"].shift(7)
df = df.dropna()

X = df[["dow", "month", "lag_1", "lag_7"]]
y = df["units"]

X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.2, random_state=0) # WRONG
model = RandomForestRegressor().fit(X_tr, y_tr)
print("MAE (shuffled):", mean_absolute_error(y_te, model.predict(X_te)))

MAE: 3.1 units per day, on a series whose mean is 82 units. That looks excellent. It is fake. The shuffle placed rows from July 2024 in the training set and rows from July 2025 in the test set. The model does not need to learn seasonality — it has already seen "the same day of the week around the same date, minus and plus one year". A chronological split of the same data yields 11.6 units per day. The 3.1 was a metric measuring memorization, not forecasting.

The correct split: chronological, and with a gap

The right split is a straight line on the calendar.

cutoff = df["date"].quantile(0.8, interpolation="nearest")
train = df[df["date"] < cutoff]
test = df[df["date"] >= cutoff]

For a 28-day horizon we go further and forbid the training set from seeing any row within 28 days before the test window opens, because our features include a 7-day lag and any feature that peeks into the horizon poisons the metric. Module 9 formalizes this into rolling-origin backtests; module 7 explains which features are safe to compute at inference time.

A model that beats the shuffled baseline still cheats

If a colleague reports "we got MAE 3.1 with a random forest, let's ship it", the first question is not "which model?" but "how did you split?". A shuffled split makes any model look strong, from a linear regression to a Transformer. It is the single most common cause of forecasting results that fail in production.

What a good forecasting workflow looks like

Given the above, the pipeline we will build across the course has this shape:

  1. Look at the series (module 2): trend, seasonality, obvious holidays, outliers.
  2. Test stationarity and difference if needed (module 3).
  3. Compute baselines (module 4): naïve, seasonal-naïve, moving average, drift.
  4. Try classical models: ARIMA/SARIMA (module 5), Holt-Winters and Prophet (module 6).
  5. Turn the series into a supervised table and try gradient boosting (module 7).
  6. Try deep learning where it earns its cost (module 8).
  7. Evaluate everything with a rolling origin and pick a metric adapted to the horizon (module 9).
  8. Ship a forecast with intervals and a retraining plan (module 10).

At each step, a single row will be added to a shared results table — one MAE, one MASE, one interval score per model — so we can compare them honestly at the end.

Summary

  • A time series is a temporally ordered sequence where the value at tt depends on previous values; the order carries information a random shuffle destroys.
  • The vocabulary that will run through the course: frequency, horizon, lookback, backtest.
  • Splitting a series with train_test_split(shuffle=True) produces metrics that look great and are wrong; a chronological split with a gap for the horizon is the only sound baseline.
  • The workflow of the course is a pipeline of comparisons: baseline first, then complexity only if it beats the baseline on a rolling backtest.

Next module: decomposing the series into trend, seasonality and residuals, and reading what the pieces tell us before we even fit a model.