A time series is a sequence of observations in time order — sales per day, CPU load per minute. Order matters: neighbouring values are correlated, the future must never leak into the past, and a simple “same as last week” baseline is surprisingly hard to beat.
| Component | Meaning | Example |
|---|---|---|
| Trend | Long-term increase or decrease | Growing user base |
| Seasonality | Repeating pattern with a fixed period | Weekly shopping cycle, yearly peaks |
| Cycles | Rises and falls without a fixed period | Business cycles |
| Noise | Irregular variation | Random day-to-day fluctuation |
A forecasting example
Daily demand with a trend, a weekly pattern and noise. Forecast the last 28 days using only information available the day before:
import numpy as np
import pandas as pd
from sklearn.linear_model import Ridge
from sklearn.metrics import mean_absolute_error
rng = np.random.default_rng(0)
days = pd.date_range("2024-01-01", periods=364, freq="D")
t = np.arange(364)
weekly = np.array([0, 2, 3, 3, 5, 12, 9])[days.dayofweek] # busier on weekends
y = pd.Series(50 + 0.05 * t + weekly + rng.normal(0, 2, 364), index=days)
print(round(y.autocorr(lag=1), 2), round(y.autocorr(lag=7), 2)) # 0.68 0.91 ← strong weekly autocorrelation
df = pd.DataFrame({"y": y})
df["lag_1"] = y.shift(1) # yesterday
df["lag_7"] = y.shift(7) # same weekday last week
df["roll_7"] = y.shift(1).rolling(7).mean() # past week's mean — shift FIRST, then roll
df["dow"] = df.index.dayofweek
df = pd.get_dummies(df, columns=["dow"], dtype=float).dropna()
train, test = df.iloc[:-28], df.iloc[-28:] # split by time, never shuffle
X_cols = [c for c in df.columns if c != "y"]
model = Ridge(alpha=1.0).fit(train[X_cols], train["y"])
print(round(mean_absolute_error(test["y"], test["lag_1"]), 2)) # 5.18 ← naive: tomorrow = today
print(round(mean_absolute_error(test["y"], test["lag_7"]), 2)) # 2.51 ← seasonal naive: = last week
print(round(mean_absolute_error(test["y"], model.predict(test[X_cols])), 2)) # 1.99 ← model with lag + calendar featuresApproaches
| Family | Examples | Good for |
|---|---|---|
| Baselines | Naive, seasonal naive, moving average | Always — the bar to beat |
| Classical statistical | Exponential smoothing (ETS, Holt-Winters), ARIMA/SARIMA (statsmodels) | Single series, clear trend/seasonality, small data, prediction intervals |
| ML on engineered features | Lags, rolling stats, calendar and holiday features + gradient boosting / linear models | Many related series, external drivers (price, promotions, weather) |
| Deep learning | RNN/LSTM, temporal CNNs, transformers (see Deep Learning) | Very many long series, complex patterns |
Practical points
Multi-step horizons: recursive (feed predictions back as lags) accumulates errors; direct (one model per horizon) avoids that at more cost.
Metrics: MAE/RMSE per horizon; MASE = MAE ÷ MAE of the seasonal-naive forecast (below 1 beats the baseline); avoid MAPE with values near 0.
Validation: rolling-origin (expanding window) evaluation mirrors how forecasts are produced in production.
Label availability: if yesterday's sales are only final two days later,
lag_1isn't available at prediction time — use a gap.Report prediction intervals, not only point forecasts — inventory and staffing decisions depend on the uncertainty.
How is validating a time-series model different?
What is stationarity and why does it matter?
What baseline would you use for a forecasting problem?