Time Series Analysis & Forecasting β¦
Data Science Interview Notes β components, stationarity, ACF/PACF, ARIMA family, CV
Big idea: time series data breaks the i.i.d. assumption most ML relies on β order matters, and today depends on yesterday. Almost every interview question here is really asking: "do you understand why time makes this different from tabular ML?"
1. Components of a Time Series
- Trend (T): long-term increase/decrease in the level of the series (e.g., steadily rising sales over years).
- Seasonality (S): fixed, known-period repeating pattern (daily, weekly, yearly) β e.g., retail sales spike every December.
- Cyclicity (C): repeating pattern but no fixed period, usually driven by economic/business cycles (e.g., recessions every 7-10 years, but not exactly). Often confused with seasonality β key difference is fixed vs variable period.
- Noise / Residual (R): random, irreducible variation left after removing trend, seasonality, cyclicity.
Additive vs Multiplicative decomposition
| Model | Formula | When to use |
| Additive | Y = T + S + C + R | Seasonal fluctuation size stays constant over time, regardless of trend level |
| Multiplicative | Y = T Γ S Γ C Γ R | Seasonal fluctuation grows/shrinks proportionally with the trend (e.g., sales seasonality swings get bigger as overall sales grow) |
Quick visual test: plot the series. If the seasonal "wiggle" height stays the same throughout β additive. If the wiggles get bigger as the series grows β multiplicative. (Or take log(Y) to convert multiplicative β additive β very common interview trick.)
2. Stationarity
Why models need it
- A series is stationary if its statistical properties β mean, variance, autocorrelation β don't change over time.
- Classical models (AR, MA, ARIMA) assume the underlying process is stable, so the same equation/parameters can describe the past and extrapolate the future. If mean/variance keep shifting, a model fit on old data has no reason to hold for new data.
- Non-stationary series (with trend/seasonality) β correlations look artificially strong ("spurious regression") and forecasts drift or blow up.
Augmented Dickey-Fuller (ADF) Test
- Tests Hβ: series has a unit root (non-stationary) vs Hβ: series is stationary.
- Look at the p-value: p β€ 0.05 β reject Hβ β series is stationary. p > 0.05 β fail to reject β likely non-stationary, needs differencing.
- Also gives a test statistic compared against critical values (1%, 5%, 10%) β more negative than the critical value β stronger evidence of stationarity.
Making a series stationary
- Differencing:
Y'_t = Y_t β Y_{t-1} removes trend. If seasonality remains, apply seasonal differencing: Y_t β Y_{t-m} (m = season length, e.g. 12 for monthly data with yearly seasonality).
- Transformations:
log(), sqrt(), or Box-Cox to stabilize variance (use when spread grows with level β i.e., multiplicative-looking series).
- Rule of thumb: transform first (stabilize variance) then difference (stabilize mean). Re-run ADF after each step until p β€ 0.05.
"How many times did you difference?" β that count is the d in ARIMA(p,d,q). Most real series need d=0 or d=1, rarely d=2 (over-differencing adds noise and creates its own artificial autocorrelation).
Code β ADF test with statsmodels
from statsmodels.tsa.stattools import adfuller
result = adfuller(series.dropna())
print("ADF Statistic:", result[0])
print("p-value:", result[1])
print("Critical Values:", result[4])
if result[1] <= 0.05:
print("Reject H0 -> series is stationary")
else:
print("Fail to reject H0 -> difference the series and re-test")
3. Autocorrelation β ACF & PACF
- ACF (Autocorrelation Function): correlation between
Y_t and Y_{t-k}, including indirect effects through intermediate lags.
- PACF (Partial ACF): correlation between
Y_t and Y_{t-k} after removing the effect of the lags in between β the "direct" relationship at lag k only.
How to read them to pick p and q
| Model | ACF pattern | PACF pattern |
| AR(p) | Tails off gradually (decays slowly/sinusoidally) | Cuts off sharply after lag p β number of significant spikes = p |
| MA(q) | Cuts off sharply after lag q β number of significant spikes = q | Tails off gradually |
| ARMA(p,q) | Tails off | Tails off |
Memory trick: "AR shows up in PACF, MA shows up in ACF." The one that cuts off cleanly tells you that model's order.
In practice today: nobody eyeballs ACF/PACF plots to pick exact (p,q) for production β everyone uses auto_arima / grid search over AIC/BIC. But interviewers ask this to check you understand what the search is actually optimizing, so know the manual method cold.
4. Forecasting Algorithms
AR β Autoregressive
Y_t = c + ΟβY_{t-1} + ΟβY_{t-2} + ... + ΟβY_{t-p} + Ξ΅_t β today's value is a linear combination of its own past values. Order p = how many lags back you regress on.
MA β Moving Average (model, not the smoothing technique!)
Y_t = ΞΌ + ΞΈβΞ΅_{t-1} + ΞΈβΞ΅_{t-2} + ... + ΞΈ_qΞ΅_{t-q} + Ξ΅_t β today's value is a linear combination of past forecast errors, not past values. Order q = how many lagged error terms.
Common trap: "MA model" β "moving average smoothing" (the rolling-mean trend line). Interviewers love this distinction β say it explicitly to show you're not confusing the two.
ARIMA(p, d, q)
- AR(p) + Integrated (d = differencing order to induce stationarity) + MA(q).
- Works only on non-seasonal stationary (after differencing) series.
SARIMA(p,d,q)(P,D,Q,m)
- ARIMA + a seasonal block: seasonal AR order P, seasonal differencing D, seasonal MA order Q, and season length m (e.g., 12 for monthly/yearly).
- Use when ACF shows a repeating spike every m lags even after regular differencing (a clear seasonal signature).
Prophet β intuition (Facebook/Meta)
- Decomposition-based, not AR-family:
y(t) = trend(t) + seasonality(t) + holidays(t) + Ξ΅, fit via curve-fitting (additive regression), not lag-based recursion.
- Trend: piecewise linear/logistic with automatically detected changepoints.
- Seasonality: Fourier series terms to flexibly model multiple seasonalities (daily + weekly + yearly) at once.
- Big selling points: robust to missing data & outliers, handles multiple seasonalities and holiday effects out of the box, minimal manual tuning (no need to manually pick p,d,q) β good for business-style series (lots of holidays/promos), less ideal when you need tight statistical control or very short/high-frequency series.
Code β ARIMA fit with statsmodels
from statsmodels.tsa.arima.model import ARIMA
model = ARIMA(train_series, order=(2, 1, 1))
fitted = model.fit()
print(fitted.summary())
forecast = fitted.forecast(steps=12)
from statsmodels.tsa.statespace.sarimax import SARIMAX
sarima_model = SARIMAX(train_series, order=(1,1,1),
seasonal_order=(1,1,1,12))
sarima_fitted = sarima_model.fit()
Decision Flowchart β picking (p, d, q)
Raw series
β
Run ADF test
β
p-value > 0.05? (non-stationary)
β yes
Difference the series (d += 1); check for seasonality β seasonal differencing if needed. Re-run ADF.
β repeat until p β€ 0.05
Stationary series obtained β note total differences taken = d
β
Plot ACF and PACF on the stationary series
β
PACF cuts off sharply at lag p, ACF tails off
β pick AR(p) : set p = cutoff lag, q = 0
ACF cuts off sharply at lag q, PACF tails off
β pick MA(q) : set q = cutoff lag, p = 0
Both ACF & PACF tail off gradually
β mixed ARMA(p,q) : start small (e.g., 1,1) and tune
β
Fit candidate ARIMA(p,d,q) models β compare via AIC/BIC (lower is better) and residual diagnostics (residuals should look like white noise β check via Ljung-Box test)
β
Final model selected
5. Time Series Cross-Validation
Why standard K-Fold CV fails
- K-Fold shuffles data randomly into folds β for time series this causes data leakage: the model can end up training on future points and validating on past ones.
- This breaks the fundamental rule of forecasting: you can never use information from the future to predict the past. Validation score would look great but be meaningless in production.
- It also destroys the temporal dependency structure (autocorrelation) that the model is trying to learn from.
Rolling-Window / TimeSeriesSplit CV
- Always train on the past, validate on a future chunk β respects chronological order.
- Expanding window: training set keeps growing each fold (fold 1: train[1..10]βval[11..12], fold 2: train[1..12]βval[13..14], ...).
- Sliding/rolling window: training set is a fixed-size window that slides forward (fold 2 drops the oldest points as it adds new ones) β useful when older data becomes irrelevant (concept drift).
sklearn.model_selection.TimeSeriesSplit implements the expanding-window version directly.
Train[1:10] β Val[11:12]
β
Train[1:12] β Val[13:14]
β
Train[1:14] β Val[15:16]
from sklearn.model_selection import TimeSeriesSplit
tscv = TimeSeriesSplit(n_splits=5)
for train_idx, val_idx in tscv.split(series):
train, val = series[train_idx], series[val_idx]
One-liner for the interview: "K-Fold assumes i.i.d. samples; time series violates that because of temporal ordering and autocorrelation β so we use forward-chaining CV (TimeSeriesSplit) which always validates on data that comes after the training window."
β end of notes, good luck! β¦ β