Skip to content
ML Academy

Core day 21 of 60 · Week 5 · Part I · application depth

Time Series Foundations

The commercial team asks for "a forecast of bookings per route for next year". Before choosing a model: what is inside the series - which components, how many seasonal periods, does it behave the same way throughout - and what kind of data is it?

By the end: For one airline series you name its trend, every seasonal period, the events and breaks you can see, its data type and the stationarity checks you would run, and you write the six workflow steps with one concrete entry per step.

At a glance

Before modelling, name every part of the series, check whether it behaves the same throughout, and say what kind of data it is.

  • Order matters

    Neighbouring values resemble each other, so a series is never shuffled.

  • Parts

    Trend, one or more seasons, events, breaks and noise; daily sales or load data have two periods.

  • Stationarity

    Constant mean, variance and autocorrelation: sniff with rolling statistics, then test.

  • Breaks

    A dated change of process; averaging across it blends two worlds.

  • Data type first

    Gaps, panels, hierarchies, zeros and counts change the model, metric and interval.

Recall · 5 min

Without looking back: on the Day 2 framing card, what are the cutoff and the horizon of the fare-class decision, and why can a missing date on a flight series not be read as zero bookings?

Open due reviews

Sign in to record this block.

Concept · 10 min

A time series is a sequence in which order carries information: neighbouring values resemble each other, so shuffling the rows destroys what you want to learn. Read every series as parts that add (or multiply): trend, one or more seasons, events, breaks and noise. In a short window a break, a trend and a long cycle look alike.

A break moves the rolling mean like a trend and the rolling variance like a volatility change - the plot decides which

Predict first

With a level break of -30 switched on and a 12-step rolling window, what does the rolling mean do at the break?

Text description of the figure

A synthetic series built from trend, seasonal amplitude, period, noise and an optional level break, with the dashed underlying level. Below it, a trailing rolling mean and a trailing rolling variance over an adjustable window, and a readout that flags the mean or variance as not constant.

Six parts to look for in any series Overview
  • Trend

    A long-run direction, not necessarily straight.

  • Seasonality

    A fixed, known period tied to the calendar: 7 and about 365.25 days for daily data.

  • Cycle

    Rises and falls with no fixed period, such as business cycles.

  • Calendar events

    Holidays and fairs. Moving holidays are known-in-advance regressors, not a period.

  • Noise

    What is left once the other parts are removed.

  • Break

    A change in the process itself: a competitor's move, a price-rule change, a system migration.

Decomposition, computed: where a break goes Illustration
100140Observed100130Trend (12-month centred MA)dashed: the true level (a step)-160+16Seasonal (average per month)peak-to-trough 27.6-80+8Remainderbreak, month 54month 0month 8312-month window

The step at month 54 is a single jump in the data, but the moving average spreads it over the 12 months around it (shaded). The remainder there has a standard deviation of 5.0, against 2.1 elsewhere: the break leaks into both layers.

Synthetic monthly series, seeded: trend 0.6 per month, a 12-month season, a level drop of 18 at month 54 and noise (sd 2.2). Decomposed with the course's classical-decomposition code; the first and last six months have no centred average.

Trend, season and remainder of a seeded monthly series. The step cannot live in a smooth trend, so it is smeared over one period and leaks into the remainder.

One symptom, three causes Illustration
A steady downward trendrolling mean -9.6the 3 years you were shown12 years of context (window shaded)

Keeps falling: model a trend, difference or detrend.

A level break at month 102rolling mean -12.9the 3 years you were shown12 years of context (window shaded)

Flat, one step, flat: date it, step regressor or post-break window.

A long cycle (8 years)rolling mean -10.6the 3 years you were shown12 years of context (window shaded)

Turns back up: no fixed period, so neither trend nor season.

Three synthetic monthly series (seeded, noise sd 2.5, no season). Top: a 36-month window with its 12-month trailing mean. Bottom: the full 144 months.

In a three-year window the rolling mean falls by a similar amount for a trend, a break and a long cycle. The longer context, or a dated comparison, tells them apart.

Sniff first, then test Sequence
  1. Plot the raw series

    Components, gaps and dated changes, by eye.

  2. Rolling mean and variance

    Window = the seasonal period. A drifting level or spread is the sniff, not the verdict.

  3. Remove the season

    Seasonal difference or STL, so the tests see the remainder.

  4. KPSS and ADF

    Opposite null hypotheses: read them as a pair (matrix below).

  5. Backtest the choice

    The final judge of any transformation (Day 25).

Both tests assume no deterministic season and have little power on short series, so they come after the plot and the season removal, never instead of them.

Reading ADF and KPSS together Map

Both say non-stationary

Difference or detrend, then check again.

Conflict

Often a break, a trend or a season the tests were not built for. Plot and date it.

Too little evidence

Short or noisy series: low power. Rely on plots and the backtest.

Both say stationary

Proceed with this transformation; the backtest still decides.

The two tests ask opposite questions. Agreement is informative; disagreement is a prompt to look for what neither test models.

The airline break in the sample database: year-on-year change, 2024 vs 2023 Chart
  • Stepped routes, Jan-Feb5.4 %
  • Other routes, Jan-Feb3.1 %
  • Stepped routes, Mar-Deca fall: competitor +25% seats from 2024-03-01-5.6 %
  • Other routes, Mar-Dec3.8 %

Output of today's query on the synthetic database. Same months remove the season; the comparison group removes the network trend; the gap moves by about 12 points.

  • Parts: trend, seasonality with a fixed period (daily sales, load or traffic data carry 7 and about 365.25 days), cycles, moving calendar events, noise, breaks.
  • Stationarity: mean, variance and autocorrelation do not depend on when you look. Sniff with rolling statistics, then test with KPSS and ADF.
  • Structural break: a dated change of process. Date it, find the cause, model it explicitly.
  • Data type and the forecasting workflow decide the next six steps. Details are in the pictures and the optional depth below.

Assumptions

  • The timestamps are regular, or the missing ones are made explicit; lags and seasonal periods are meaningless on an uneven or gapped index.

  • A fixed period is a calendar decision; the weekly period is exact, the annual one is about 365.25 days, and moving holidays need their own variables.

  • The process in the forecast window is related to the one in the history; a break you did not date violates this silently.

Sign in to record this block.

Application and workplace decision · 10 min

Run the query on the airline sample database. Before you run it, predict the sign of the March-December change on the three stepped routes.

  • Groups: the three routes where competitors added 25% seats on 2024-03-01 (IST-ADB, IST-AYT, IST-FRA) and all other routes.
  • Measure: average final bookings per departure in the same calendar months of 2023 and 2024.
  • Equal months remove the season; the comparison group removes the network-wide trend.

SQL (airline sample database)

WITH f AS (
  SELECT CASE WHEN route IN ('IST-ADB', 'IST-AYT', 'IST-FRA')
              THEN 'competitor +25% seats' ELSE 'other routes' END AS route_group,
         CASE WHEN substr(date, 6, 2) IN ('01', '02') THEN 'Jan-Feb' ELSE 'Mar-Dec' END AS span,
         substr(date, 1, 4) AS yr,
         bookings_final
  FROM flight_day
  WHERE substr(date, 1, 4) IN ('2023', '2024')
)
SELECT route_group, span,
       ROUND(AVG(CASE WHEN yr = '2023' THEN bookings_final END), 1) AS avg_2023,
       ROUND(AVG(CASE WHEN yr = '2024' THEN bookings_final END), 1) AS avg_2024,
       ROUND(100.0 * (AVG(CASE WHEN yr = '2024' THEN bookings_final END)
                    / AVG(CASE WHEN yr = '2023' THEN bookings_final END) - 1), 1) AS yoy_pct
FROM f
GROUP BY route_group, span
ORDER BY route_group, span;

This code is executed in the repository's checks (pnpm lesson:py / lesson:sql). It is not run on this site; copy it into your notebook.

Sign in to record this block.

Mini-assessment · 8 min

Your first answer is your evidence. You can re-answer later in practice mode; that is labelled practice.

Question 1 · easy

Daily bookings on IST-AYT peak every Friday, swell every July, and jump in the days around a public holiday whose date moves from year to year. Which description names the components correctly?
Options

How confident are you? (optional, used for calibration)

Question 2 · medium

A series (ignore noise and season) sat at 100 for 700 days and at 75 for the most recent 200 days after a competitor entered. You forecast the next 30 days with the mean of all 900 days. What is the absolute error per day, in the same units?
units per day

How confident are you? (optional, used for calibration)

Question 3 · hard

A bank analyst fits 12 macro indicators to monthly equity returns over 1990-2023 and reports an in-sample R-squared of 0.07 with a joint p < 0.01 for the indicators. The proposal is to use the model for tactical allocation. Which next step best fits what this lesson says about forecastability?
Options

How confident are you? (optional, used for calibration)

Question 4 · medium

(Illustrative data.) A table holds daily purchases of an extra-baggage add-on for IST-AYT: about 85% of days show 0, the other days show 1 to 4, and no date is missing. Which description of the data type, and of what it changes, is right?
Options

How confident are you? (optional, used for calibration)

Question 5 · hard

(Illustrative numbers.) IST-AYT daily bookings, with the weekly pattern removed, show a level step on 2024-03-01. Over 2022-2024 the ADF test rejects a unit root (p = 0.02) and the KPSS test rejects stationarity (p = 0.02). A colleague says the tests contradict each other and asks which one to trust. What is the best reading and next step?
Options

How confident are you? (optional, used for calibration)

Sign in to complete lessons and keep your progress.

Technical depth · optional · 30 min in total

Open only what you need. Nothing here is required to complete the day; the assessment covers the core above.

The probability model: white noise and the random walk5 minWhat object a forecast estimates, stationarity defined precisely, and the random walk's variance derived and then measured.
A series is one draw from a process; a forecast describes the rest of it Diagram
Process {Y_t}one randomvariable per timetObservedy_1..y_Tthe onerealization we seeStationary?stable momentsForecast of Y_T+hE[Y_T+h | y_1..y_T] or aquantileTransform or modelthe changedifference, detrend,date the break (Day 22)yesno
  1. Process {Y_t}one random variable per time t
  2. Observed y_1..y_Tthe one realization we see
  3. Stationary?stable moments
    • yes: to Forecast of Y_T+h
  4. Transform or model the changedifference, detrend, date the break (Day 22)
  5. Forecast of Y_T+hE[Y_T+h | y_1..y_T] or a quantile

One path teaches you about T+h only if what you estimate from it (mean, variance, autocorrelation) does not depend on when you look. Strict stationarity, a shift-invariant joint distribution, is stronger; the linear models of Days 22-23 and the ADF and KPSS tests need only the weak form.

Weak stationarity in one line Formula
E[yt]=μ,Var⁡(yt)=σ2,Cov⁡(yt, yt−k)=γk\mathbb{E}[y_t]=\mu,\quad \operatorname{Var}(y_t)=\sigma^{2},\quad \operatorname{Cov}(y_t,\,y_{t-k})=\gamma_k
μ\mu
the same mean at every time t
σ2\sigma^{2}
the same variance at every time t
γk\gamma_k
autocovariance depends only on the lag k

Trend breaks the first condition, growing swings the second, and a fixed seasonal pattern makes the mean depend on the date. Strict stationarity (a shift-invariant joint distribution) is stronger; models use this weak form.

White noise and the random walk: variance derived Formula
yt=yt−1+εt=y0+∑i=1tεi,Var⁡(yt)=t σ2y_t = y_{t-1} + \varepsilon_t = y_0 + \sum_{i=1}^{t}\varepsilon_i, \qquad \operatorname{Var}(y_t) = t\,\sigma^{2}
εt\varepsilon_t
white noise: mean 0, the same variance sigma squared, zero correlation across time
∑εi\sum \varepsilon_i
unrolling the recursion telescopes every shock into the level
t σ2t\,\sigma^{2}
variances of uncorrelated shocks add, so t shocks give t sigma squared
t σ\sqrt{t}\,\sigma
the standard deviation: uncertainty grows with the square root of time

The variance depends on t, so no random walk is stationary; its first difference is the white noise itself. Every shock stays in the level for ever, which is why naive is the right baseline for it.

Worked by hand: four shocks through a random walk (sigma = 2) Table
Step tShockLevel y_tVar(y_t) = t sigma^2sd = sigma sqrt(t)
1+2242.0
2-1182.83
3+34123.46
4-22164.0

One illustrative path (y_0 = 0); the variance column is over all possible paths. 200,000 simulated walks with sigma = 2 (numpy default_rng(0)) give variances 4.00, 8.01, 12.05, 16.02 at steps 1-4: the formula, measured.

The derivation, measured: variance on the line t sigma^2 Illustration
-12-60612010203040Random walk paths widen like √t+2σ√t-2σ√tStep tLevel y_t

24 of 400 simulated paths. Each step adds an uncorrelated shock, so spread accumulates: the dashed envelope is ±2σ√t, growing with the square root of time, never settling.

010203040010203040Var(y_t) lies on the line t·σ²line: theory t·σ²dots: measured across pathst = 10: 11.2 vs 10t = 40: 39.8 vs 40Step tVariance across paths

Variance of y_t across the 400 paths at each step (dots) against the derived line t·σ². The level is unforecastable far ahead, and no fixed mean or variance exists: the walk is not stationary.

Computed: 400 seeded random walks y_t = y_t-1 + ε_t with ε ~ N(0, 1); the left panel draws 24 of them, the right panel the variance over all 400 at each step against the derived t·σ² line. Synthetic data.

Simulated random-walk paths widen like the square root of t (left); the variance across paths at each step lies on the derived straight line (right).

Stationarity is about stable moments, not skill Comparison
CriterionWhite noisey_t = epsilon_tRandom walky_t = y_t-1 + epsilon_t
Stationary?Yes: same mean, variance and (zero) autocorrelationNo: Var(y_t) = t sigma^2 grows with t
Best point forecastThe mean, at every horizon: no skill beyond itThe last value (naive), at every horizon
Interval at horizon hConstant widthWidens like sigma sqrt(h)
First difference givesOver-differencing: lag-1 correlation of -0.5 appears (Day 22)The white noise back: the logic of the d in ARIMA (Days 22-23)

Opposite corners. White noise is stationary and unforecastable beyond its mean; the random walk is non-stationary, yet its best forecast is trivial. Stock prices and exchange rates are the classic approximate random walks. The -0.5 is exact: the difference of white noise has covariance -sigma^2 over variance 2 sigma^2.

Components, periods and data types4 minWhich periods a series can carry, how additive and multiplicative structures differ, and the six data types that change the model.
Additive or multiplicative? Comparison
CriterionAdditiveMultiplicative
Seasonal swingConstant size at every levelGrows with the level
Structurey = trend + season + remaindery = trend x season x remainder
Rolling varianceRoughly flatGrows with the trend
TreatmentDecompose as it isTake logs: a product becomes a sum (Day 22)
Seasonal periods are calendar facts, not fitted numbers Table
Data frequencyPeriod(s) mTypical series
Quarterly4Quarterly revenue
Monthly12Monthly bookings
Weeklyabout 52.18 (365.25 / 7)Weekly store sales
Daily7 and about 365.25Daily bookings, sales, web traffic
Hourly24, 168 and about 8,766Electricity load, call-centre arrivals

Daily and hourly data carry several periods at once. Moving holidays (religious holidays that shift against the Gregorian calendar, Easter retail peaks) are not a period at all: they are regressors known in advance.

Two periods in daily data: four ways to carry both Overview
  • MSTL

    One STL seasonal layer per period (7 and 365), then the trend. statsforecast ships it.

  • Fourier terms + ARIMA errors

    Sine and cosine pairs per period as regressors; ARIMA models the rest (dynamic harmonic regression).

  • TBATS

    Exponential smoothing with trigonometric seasonal terms. statsforecast ships it.

  • Calendar features

    ML models read weekday, day of year and holiday windows as columns (Day 24).

Name the data type before the model: six types, six different next steps Hierarchy
irregularrelatednestedmostly 0small integersboundedWhich data type?decide before the modelThe time indexis it a regular grid?Many serieshow are they related?The valueszeros, integers, boundsGapped timestampsreindex; decide zero vsmissing (Day 6)Panellags per entity, sharedfit (Day 24)Hierarchicallevels must add up (Day26)IntermittentCroston; no MAPE (Days 16,26)Countscount intervals: Poisson,neg. binomialPositive or boundedlog or logit keeps it inrange (Day 22)
  • Which data type?decide before the model
    • The time indexis it a regular grid?
      • irregularGapped timestampsreindex; decide zero vs missing (Day 6)
    • Many serieshow are they related?
      • relatedPanellags per entity, shared fit (Day 24)
      • nestedHierarchicallevels must add up (Day 26)
    • The valueszeros, integers, bounds
      • mostly 0IntermittentCroston; no MAPE (Days 16, 26)
      • small integersCountscount intervals: Poisson, neg. binomial
      • boundedPositive or boundedlog or logit keeps it in range (Day 22)

A day with no sales is a measured zero; a day the logger was down is missing. Examples: SKUs per store or meters per feeder (panel), store, region and chain totals (hierarchical), spare-parts demand (intermittent), daily insurance claims or defects per shift (counts).

Decomposition: classical, STL and MSTL4 minWhat each method can represent, its library defaults, how strong the season is, and why a break ends up in the wrong layer.
Three decompositions: what each one can represent Comparison
CriterionClassicalSTLMSTL
TrendCentred moving average of one period: smears steps over m pointsLoess curve; default trend window 23 for monthly dataSTL trend after removing every season
Seasonal shapeFixed: the same pattern every yearMay evolve; the seasonal window (default 7) sets how fastOne seasonal layer per period, such as 7 and 365
Outliers and breaksLeak into trend, season and remainderrobust=True down-weights them; the default is FalseRobust fitting passed through stl_kwargs
Ends of the seriesNo trend for the first and last m/2 pointsEstimated to the ends, least stable thereAs STL
statsmodels call (0.15)seasonal_decompose(y, period=m)STL(y, period=m, seasonal=7, robust=False)MSTL(y, periods=(7, 365))

Defaults read from the installed statsmodels 0.15.0. Classical over-smooths fast changes and repeats one seasonal pattern every year (FPP3, 3.4); STL lets the season evolve and resists outliers when robust (FPP3, 3.6). All three are additive: take logs first when the season scales with the level.

Is the season worth modelling? Seasonal strength from the decomposition Formula
FS=max⁡(0,  1−Var⁡(Rt)Var⁡(St+Rt))F_S = \max\left(0,\; 1 - \frac{\operatorname{Var}(R_t)}{\operatorname{Var}(S_t + R_t)}\right)
StS_t
the seasonal layer of the decomposition
RtR_t
the remainder: what trend and season leave over
FSF_S
near 0: no usable season; near 1: the season dominates the remainder

Day 22's lab computes it. Then read the remainder, not the trend: structure left in R_t (runs of one sign, a spike pair at a date) is where breaks and events show up.

Stationarity, defined and tested3 minWhat ADF and KPSS returned on five constructed series, read against the definition in the first chapter.
ADF and KPSS on five constructed series (hands-on lab) Table
SeriesADF pKPSS pReading
Constant level + weekly season0.0000.100 or moreStationary by both tests
Linear trend + weekly season0.9080.010 or lessNon-stationary by both
Weekly + annual season0.5110.100 or moreNeither rejects, so the verdicts disagree: the annual season fools ADF
Level break at day 7000.8370.010 or lessNon-stationary, but the cause is a break, not a unit root
Random walk0.9980.010 or lessUnit root: difference it

Synthetic, three years of daily data. ADF tests the null of a unit root, KPSS the null of stationarity: opposite questions. Neither models a deterministic season, which the annual-season row proves, so remove the season first. statsmodels reports KPSS p-values only inside its table range: 0.100 means at least 0.10, 0.010 at most 0.01.

Inside the two tests, and the knobs that move themadvanced4 minThe regressions behind ADF and KPSS, their statsmodels defaults, and why a break pushes them apart.
What ADF actually regresses Formula
Δyt=α+βt+γ yt−1+∑i=1pδi Δyt−i+εt,H0:γ=0\Delta y_t = \alpha + \beta t + \gamma\, y_{t-1} + \sum_{i=1}^{p} \delta_i\, \Delta y_{t-i} + \varepsilon_t, \qquad H_0: \gamma = 0
γ\gamma
pull back toward the mean; 0 means a unit root (shocks never fade)
α, βt\alpha,\ \beta t
deterministic terms: regression='c' keeps alpha, 'ct' adds the trend
pp
lagged differences that whiten the errors; autolag='AIC' picks p up to ceil(12 (T/100)^(1/4))
εt\varepsilon_t
assumed white noise; a season or break left in it distorts the test

The statistic is the t-ratio of gamma, compared with Dickey-Fuller critical values, not the normal table. A level shift inside the sample looks like a shock that never fades, so ADF loses power to reject.

What KPSS assumes under its null Formula
yt=ξt+rt+εt,rt=rt−1+ut,H0:σu2=0y_t = \xi t + r_t + \varepsilon_t, \qquad r_t = r_{t-1} + u_t, \qquad H_0: \sigma_u^{2} = 0
rtr_t
a random-walk level; under the null its variance is zero, so the level is constant
ξt\xi t
a deterministic trend, included with regression='ct' (default 'c': none)
εt\varepsilon_t
stationary noise; its long-run variance uses a data-dependent lag (nlags='auto')

A level shift looks like a random-walk step to KPSS, which is why a break makes it reject.

Test knobs that change the verdict (statsmodels 0.15 defaults) Table
KnobDefaultWhat it trades
adfuller regression'c' (constant)Use 'ct' for a trending series; the wrong choice costs power or size
adfuller autolag / maxlag'AIC', ceil(12 (T/100)^(1/4)): 14 for T = 144More lags whiten the errors and cost power
kpss regression'c' (level stationary)'ct' tests stationarity around a trend
kpss nlags'auto' (data-dependent)Too few lags over-reject on persistent but stationary series
kpss p-valueInterpolated, bounded to 0.01-0.10Outside the table you get the bound plus a warning
AutoARIMA d (statsforecast 2.0)test='kpss'Repeated KPSS tests choose d: every caveat above applies inside the search

Break-aware unit-root tests exist (statsmodels ships zivot_andrews), but in business data a dated change log plus a backtest is usually the cheaper evidence (practitioner heuristic).

Breaks: what they change and how to treat them5 minFour kinds of break, a decision flow, the window trade-off, three treatments compared and the loop that catches the next one.
Four things a break can change Overview
  • Level

    A step up or down. A step regressor handles exactly this.

  • Slope

    Growth speeds up or stalls. Needs a trend change term or a post-break window.

  • Seasonal shape

    Peaks move, for example a new timetable or shift plan. Needs post-break seasons or interactions.

  • Variance

    Swings get larger. Point forecasts survive; intervals must be refitted.

Treating a suspected break: three questions, then the backtest Diagram
Dated causefound?log, control groupCheck the data firstmigration, definitionchangePermanent?Intervention dummyspike or temporary stepFull seasonsince?12 months afterStep regressorborrows the old seasonalshapeStep vs post-breakwindowboth are real candidatesRolling-originbacktestpicks the treatment (Day25)noyesnoyesnoyesagainst naive
  1. Dated cause found?log, control group
    • no: to Check the data first
  2. Permanent?
    • no: to Intervention dummy
  3. Full season since?12 months after
    • no: to Step regressor
  4. Step vs post-break windowboth are real candidates
    • to Rolling-origin backtest
  5. Check the data firstmigration, definition change
  6. Intervention dummyspike or temporary step
  7. Step regressorborrows the old seasonal shape
  8. Rolling-origin backtestpicks the treatment (Day 25)

Without a full post-break season, a step regressor is the only option that keeps a seasonal shape; with one, a post-break window becomes a real candidate. Either way the backtest decides, not the flow.

How much history after a break? Illustration
0246803060120180240Drop of 10 (1.7 noise sd): best window 30 daysbest: 30 days, error 0.91crosses the breakL = 240: 8.80Window length L (days averaged)Level error (MAE)

Best window: 30 days, exactly the post-break history. Every older day adds bias faster than it removes noise.

01203060120180240Drop of 1 (0.17 noise sd): best window 60 daysbest: 60 days, error 0.69crosses the breakL = 240: 0.84Window length L (days averaged)Level error (MAE)

Best window: 60 days, reaching back across the break: the step is small next to the noise, so extra history buys precision.

Simulation, seeded: 360 days of history with noise sd 6 and a level drop 30 days before the forecast origin. Vertical axis: mean absolute error of the mean of the last L days against the true new level, averaged over 200 draws per point (separate scales).

Short windows are noisy, long ones blend two levels. The size of the break against the noise decides where the best window lies.

Three ways to treat a dated break Comparison
CriterionIgnore itStep regressorPost-break window
AssumesOne regime before and afterThe break changed only the levelOnly recent data describe the future
Keeps seasonal historyYes, but blendedYesNo: less than one annual cycle at first
Main riskEvery month after a drop is over-forecastMisses a changed slope or seasonSeason poorly estimated; noisy with little data

Compare the options on rolling origins after the break (Day 25); none is right by default.

Breaks are caught by a loop, not by a one-off check Loop
keep the changelog next to thedata1Change logprices, competitorentries, migrations2Forecastfrom a fixed origin3Error monitora run of same-sign errorsafter a date4Date and explaincontrol group, samemonths, data check5Choose a treatmentstep, dummy or window, bybacktestinformmonitorsuspect a breaktreatrecord it
  1. Change logprices, competitor entries, migrationsinform ↓
  2. Forecastfrom a fixed originmonitor ↓
  3. Error monitora run of same-sign errors after a datesuspect a break ↓
  4. Date and explaincontrol group, same months, data checktreat ↓
  5. Choose a treatmentstep, dummy or window, by backtestrecord it ↓
  6. ↺ back to 1 · Change log

A run of same-sign forecast errors after a date is the cheapest detector (practitioner heuristic). Every confirmed break goes back into the change log, so the next model starts with it dated.

Forecastability and common misreadings3 minFour conditions that set a ceiling no model can lift, and five misreadings that cost credibility in a review.
Four questions that set the ceiling on forecastability Overview
  • Drivers understood?

    How well do you know what moves the series?

  • Data available?

    How much relevant history exists?

  • Future like the past?

    Will the process in the horizon resemble the history?

  • Does the forecast feed back?

    Publishing it can change the outcome; financial returns are the classic case.

The four conditions from the textbook (FPP3, 1.1). A ceiling set by noise and weak drivers cannot be lifted by a better algorithm. In one comprehensive study, equity-premium predictors proposed in the literature performed poorly out of sample over about 30 years (Welch and Goyal, 2008).

Five misreadings a staff-level answer avoids Overview
  • Stationary means predictable

    Stable moments say nothing about skill; white noise is stationary and unforecastable.

  • Difference the break away

    A difference turns a step into one spike; the level change is still unmodelled.

  • One p-value decides

    Both tests have little power on short series and are distorted by seasons and breaks.

  • The period is estimated

    A seasonal period is a calendar fact (7, 12, about 365.25), not a fitted number.

  • A cycle is a season

    Cycles have no fixed period; a seasonal model will repeat them at the wrong time.

The workflow for the rest of Part I2 minSix steps, each with the day that covers it.
The six-step forecasting workflow used on Days 22-28 Sequence
  1. Define the problem

    Decision, horizon, cutoff and metric (Day 2).

  2. Gather information

    Data, and which drivers are known in advance for the whole horizon.

  3. Explore

    Plot, components, breaks, gaps and data type (today).

  4. Fit candidates

    Baselines first (Day 23), then classical and ML models (Day 24).

  5. Forecast

    From a fixed origin, at the horizon the decision uses.

  6. Evaluate

    Rolling origins against the baseline (Day 25).

The textbook lists five steps and merges the last two; splitting them keeps producing a forecast and judging it apart.

Memory anchor

Name every part before you model: periods, dates, breaks - and ask first whether the series can be forecast at all.

Useful for

  • Triage of any new series in ten minutes - components, data type, breaks and forecastability before a model is chosen.

  • Writing the exploration step of a forecasting project, and deciding which features to build (calendar, event, step).

  • Explaining to stakeholders why history before a structural change may not be a valid basis for a forecast.

Not suitable for

  • Choosing between models - this lesson names what is in the series; baselines and ETS/ARIMA come on Day 23 and validation on Day 25.

  • Causal statements about a break - a dated level shift is descriptive; its cause needs a design (Days 5 and 28).

Traps

Reading a break as a trend

A rolling mean or a fitted line over a window that contains a level shift shows a slope. The model extrapolates the slope, and each new month confirms the error.

How to avoid it

Plot the raw series and compare the same calendar months across years and against a control group before fitting any trend term.

Letting one stationarity test decide

ADF and KPSS ask opposite questions, are not designed for seasonal data, and are weak on short series. A single p-value is read as a verdict and the series is differenced or left alone on that basis.

How to avoid it

Use plots, a rolling-statistics check, a test of each type after removing the season, and finally the backtest result of the choice (Day 22).

Senior reasoning

A senior reads the series before modelling it: components with periods and dates, gaps and events, the data type, and whether the process in the forecast window is the process in the history. They keep a list of dated changes next to the data (pricing rules, competitor entries, system migrations), treat each as a modelling decision with a backtest, and set expectations on forecastability early: a ceiling set by noise and weak drivers cannot be lifted by a better algorithm.

Interview question

"Sales fell 15% in April. How do you tell whether it is seasonality, trend, or a structural break?" Structure the answer.

Recall cards scheduled after completion

  • Name the parts of a daily airline series, and say which of them history can and cannot extrapolate.
  • Which checks would you run before differencing, and what are the six workflow steps?

Reviewed on a 1 · 3 · 7 · 14 · 30-day schedule; a miss returns the card to 1 day.

References

Optional sections

Deep dive · 10 min

Additive vs multiplicative. In an additive series the seasonal swing has constant size; in a multiplicative one it scales with the level, so the rolling variance grows with the trend (the widget's multiplicative toggle). A log transform turns a product into a sum, which is why Day 22 transforms before it decomposes.

Multiple seasonality. Most textbook methods take one period. For daily data with weekly and annual patterns the textbook suggests STL with several seasonal periods, or dynamic harmonic regression with Fourier terms for each period and ARIMA errors; MSTL (iterated STL over several periods) and TBATS (exponential smoothing with trigonometric seasonal terms) are other options, and statsforecast ships both. ML models (Day 24) get the same information through calendar features: weekday, day of year, holiday windows.

Data-type table.

TypeWhat changesWhere it is handled
Irregular or gapped timestampsLags and seasonal periods lose meaning; reindex to a grid, decide zero vs missingDay 6, insight on time grids
Panel (many series)Share information across series; entity-safe lagsDay 24
HierarchicalForecasts must add up across levelsDay 26
IntermittentMany zeros; MAPE undefined; Croston-type methodsDays 16 and 26
CountSmall integers; Poisson or negative-binomial intervalsDay 26
Positive or boundedLog or logit transform so forecasts stay in rangeDay 22

Stationarity tests, read carefully. ADF tests the null of a unit root; KPSS tests the null of stationarity around a constant or a trend. Both assume no deterministic seasonality, and the lab shows ADF failing to reject on a stationary-plus-annual-season series. KPSS p-values outside the interpolation table are reported as boundary values (the library says so), so read "0.10" as "at least 0.10".

Forecastability and finance. The textbook gives four conditions: how well the drivers are understood, how much data exist, how similar the future is to the past, and whether forecasts affect the outcome. Equity-premium predictors proposed in the literature performed poorly out of sample over about 30 years in one comprehensive study. Volatility and volume are often more predictable than returns - a common practitioner view, not shown by a source in this course's registry.

Role lenses. An analyst checks the grid and compares the same months across years; a data scientist writes the component list and the break treatment; an ML engineer makes sure the calendar and break variables exist at serving time for the whole horizon.

Hands-on lab: Stationarity checks on five constructed series · 20 min

Run the code, then answer: (1) Which series do ADF and KPSS classify the same way, and which do they disagree on? (2) Why does the seasonal series get a high ADF p-value although it has no trend? (3) Replace the break series by one with the same break but no weekly season and compare the rolling-mean statistic. (4) Change the "last 200 days" window to 50 and to 400 and describe the trade-off in the final two lines.

import warnings

import numpy as np
from statsmodels.tsa.stattools import adfuller, kpss

warnings.filterwarnings("ignore")
rng = np.random.default_rng(21)
n = 3 * 364                                   # three years of daily data (a multiple of the weekly period)
t = np.arange(n)
weekly = 8 * np.sin(2 * np.pi * t / 7) + 4 * np.cos(4 * np.pi * t / 7)
annual = 20 * np.sin(2 * np.pi * (t - 80) / 365.25)
noise = rng.normal(0, 5, n)
cases = {
    "constant level + weekly season": 100 + weekly + noise,
    "linear trend + weekly season": 100 + 0.05 * t + weekly + noise,
    "weekly + annual season": 100 + weekly + annual + noise,
    "level break at day 700": 100 + weekly + noise - 25 * (t >= 700),
    "random walk": 100 + np.cumsum(rng.normal(0, 3, n)),
}
print(f"{'series':32s} {'ADF p':>6s} {'KPSS p':>7s} {'roll-mean range / sd':>21s}")
for name, y in cases.items():
    adf_p = adfuller(y, result_object=True).pvalue                # H0: unit root (result_object: statsmodels 0.15+)
    kpss_p = kpss(y, regression="c", result_object=True).pvalue   # H0: stationary around a constant
    roll = np.convolve(y, np.ones(28) / 28, mode="valid")         # 28 days = four weekly cycles
    print(f"{name:32s} {adf_p:6.3f} {kpss_p:7.3f} {np.ptp(roll) / y.std():21.2f}")

# Which period does the series remember? Autocorrelation at weekly and annual lags
y = cases["weekly + annual season"]
z = y - y.mean()
for lag in (7, 14, 182, 364):
    print(f"lag {lag:3d} autocorrelation: {np.corrcoef(z[:-lag], z[lag:])[0, 1]:+.2f}")

# What a break does to a level forecast: all history vs the post-break window only
y = cases["level break at day 700"]
train, future = y[:900], y[900:]
print(f"MAE, mean of all 900 days:  {np.abs(future - train.mean()).mean():.1f}")
print(f"MAE, mean of last 200 days: {np.abs(future - train[-200:].mean()).mean():.1f}")
Interview practice · 10 min

A telecom analyst shows a plot of daily support tickets for three years and says "it is stationary, so we can use an ARIMA model directly". What do you ask and check?

Content version 6 · last verified 2026-10-18 · depends on sqlite, statsmodels, numpy