learn-timeseries-forecasting-with-phoebe / Builder session 1 of 10
Learn Timeseries Forecasting with Phoebe · Builder track · Session 1 of 10

Forecasting foundations: the series, the split, the baseline

Every forecast, from next week's web traffic to next quarter's demand, starts from one object: a value indexed by time. This session builds that series from our running case - Lumen's daily website sessions - in real pandas, sets up the one split that keeps forecasting honest (by time, never at random), and ships the baseline every fancy model has to beat: the seasonal-naive forecast.

🟢 Builder track Practitioners: analysts · data scientists · engineers Python 3.10+ · a notebook Start here
0-3 · Welcome 3-18 · The series + the split 18-42 · Build-along: series + baseline 42-45 · Q&A
Part 0

How this track works

Ten hands-on sessions, one running series: Lumen, a skincare brand whose website sessions we forecast day by day. You will build the series, then climb the method ladder in real Python - decomposition and baselines, exponential smoothing, ARIMA/SARIMA, Prophet, gradient-boosting, and a look at deep models - then backtest, serve, and monitor it, following one forecast from the analyst's first plot to a monitored production model. Tonight lays the foundation: the data shape, the honest split, and the baseline. A running note: forecasts are uncertain, so we always carry a prediction interval, and we teach the published canon (FPP3, Box-Jenkins, Prophet) honestly.

Live - presented in session Self-study - read after class ★ Run it now (Python) Sources covered (cited)
★ What you walk out with today The Lumen sessions series built on a pandas datetime index and resampled to a clean daily frequency, the time-based train/test split that prevents leakage, the four building blocks of any series (trend, seasonality, cycle, noise), and a working seasonal-naive baseline - the number every model in this course has to beat.
Part 1 · covers FPP3 foundations

A series is four things stacked together 6 min live

Strip away the jargon and any time series is a sum of four parts: a trend (the slow drift up or down), seasonality (a pattern that repeats on a fixed period - here, a weekly rhythm), a longer cycle, and noise (the irregular wiggle). Forecasting is the craft of pulling those apart, projecting the ones that persist, and being honest about the one that does not.

Observed = trend + seasonality + noise Observed the messy real line Trend slow drift upward Seasonality repeats every week Noise irregular, unforecastable Every method in this course is a different way to separate the signal (trend + seasonality) from the noise.
🔍 Click to zoom - a series is trend + seasonality + noise, stacked
LiveFrequency, index, and the gaps that break everything3 min▶

Before any model, the series has to be clean and regular:

  • A real datetime index - not strings. pandas needs to know Tuesday follows Monday, so it can resample, shift, and roll.
  • One fixed frequency - daily, weekly, hourly. Raw logs are irregular; you resample to a grid first.
  • No silent gaps - a missing day is not a zero. Decide explicitly: fill, interpolate, or flag. A gap treated as zero will wreck seasonality.
Real world

Frequency is a modelling choice, not a given. Lumen's raw analytics arrive per-session with timestamps. Forecasting daily sessions means resampling those events to a daily count first. Choose the frequency that matches the decision: daily for staffing, weekly for planning. The wrong frequency hides the very pattern you need.

Self-studyBuild the Lumen series on a datetime index (pandas)2 min read▶

Raw event logs are irregular. A resample turns them into a clean daily series:

★ Run it now (Python) - events to a daily seriesimport pandas as pd, numpy as np # Lumen web analytics: one row per session, with a timestamp rng = pd.date_range("2026-01-01", periods=20000, freq="17min") events = pd.DataFrame({"ts": rng, "session": 1}) # collapse to a clean DAILY series on a real datetime index s = (events.set_index("ts")["session"] .resample("D").sum() .asfreq("D")) # explicit daily frequency, no gaps print(s.head()) print("freq:", s.index.freq, "| missing days:", s.isna().sum())

That s - a value per day on a datetime index - is the input to every model from here.

Part 2 · covers FPP3 evaluation setup

Split by time, never at random 5 min live

The single most common forecasting bug is a random train/test split. Time only runs one way: you must train on the past and test on the future. Shuffle the rows and you leak tomorrow into today - your accuracy looks great and collapses in production.

Right: split by time Train (the past) Test (the future) Everything in test comes strictly after everything in train. This is how it will run live. Wrong: split at random test dates scattered A shuffled split lets the model peek at the future. It scores high, then fails live. Always split by time.
🔍 Click to zoom - train on the past, test on the future
LiveHoldout, and why a single split is only the start3 min▶

A simple time holdout is enough to begin: keep the last N periods as the test set and never look at them while modelling.

  • Holdout length = your real horizon. If you forecast 14 days ahead, hold out 14 days and score there.
  • One split is noisy. A single holdout can flatter or punish a model by luck. In b8 you will upgrade to rolling-origin backtesting - many splits marched forward through time.
  • Never tune on the test set. If you pick a model by peeking at test, you have leaked. Use a validation slice or backtest.
The horizon shapes everything A 7-day and a 90-day forecast are different problems with different best models and different uncertainty. Decide the horizon before you model, and evaluate at exactly that horizon.
Demo 1 of 2

Build the series and split it by time ★ 12 min · everyone builds

Turn Lumen's daily sessions into a clean series and carve off an honest holdout.

Load or build a daily series on a datetime index (use the sample below, or export your own non-confidential daily counts to CSV).

Resample to a fixed daily frequency and confirm there are no missing days.

Split by time: last 14 days as the test set, everything before as train. Print the two date ranges to prove test comes after train.

Plot it. Eyeball the trend and the weekly rhythm before you model anything.

★ Run it now (Python) - daily series + time splitimport pandas as pd, numpy as np # a Lumen-style daily sessions series: trend + weekly seasonality + noise idx = pd.date_range("2026-01-01", periods=112, freq="D") season = np.array([40,55,60,50,30,-120,-140]) # weekday shape i = np.arange(112) y = 1000 + 2.5*i + season[i % 7] + 14*np.sin(i*1.7) + 9*np.cos(i*0.6) s = pd.Series(np.round(y), index=idx, name="sessions") H = 14 # forecast horizon train, test = s.iloc[:-H], s.iloc[-H:] print("train:", train.index.min().date(), "->", train.index.max().date()) print("test :", test.index.min().date(), "->", test.index.max().date()) assert train.index.max() < test.index.min() # no leakage
Plot first, model second Half of forecasting is looking. Before any model, plot the series and its weekly and monthly averages. The trend, the seasonal period, and the outliers are usually visible to the eye - and they tell you which method on the ladder to reach for.
Demo 2 of 2

Ship the seasonal-naive baseline ★ 8 min · build your own

Before any clever model, build the baseline it has to beat. For a weekly-seasonal series the honest baseline is seasonal-naive: tomorrow equals the same weekday last week. It is two lines, and on seasonal data it is shockingly hard to beat - which is exactly why you measure against it.

Forecast each future day as the value from 7 days earlier (the last observed same weekday).

Score it on the holdout with MAPE (mean absolute percentage error).

Keep it - in b8 you will measure every model against this line, and in b3 you will watch Holt-Winters leave it behind.

★ Run it now (Python) - seasonal-naive baselinem = 7 # weekly seasonality history = train.copy() preds = [] for step in range(H): preds.append(history.iloc[-m + (step % m)]) # same weekday last cycle fc = pd.Series(preds, index=test.index) mape = (np.abs(test.values - fc.values) / test.values).mean() * 100 print(f"seasonal-naive MAPE: {mape:.1f}% (the line every model must beat)")
Real world

Never skip the baseline. The M-competitions - the field's biggest empirical bake-offs - show again and again that simple methods and their combinations are extremely hard to beat. If your ARIMA or neural net cannot beat seasonal-naive on a backtest (b8), it is not earning its complexity. The baseline is also your fallback when a fancy model breaks in production.

Homework

Try it yourself - this week ◐ 30-45 min total

Source material

Sources covered (cited)

Taught from the forecasting canon and the published empirical evidence. This session covers:

✓Forecasting: Principles and Practice, 3rd ed (Hyndman & Athanasopoulos, FPP3)Parts 1-2 · the components of a series, frequency, the honest split
◐M-competitions (Makridakis, M4/M5)Demo 2 · why a simple baseline is hard to beat
Check yourself

Three questions before you go 🎯 ◐ 90 seconds

1 · What are the building blocks of a time series?

A series is a sum of a slow trend, a repeating seasonality, a longer cycle, and irregular noise. Forecasting separates the persistent signal from the unforecastable noise.

2 · Why must a time series be split by time, not at random?

Time runs one way. Train on the past, test on the future. Shuffling lets the model peek at the future - the accuracy is a mirage.

3 · Why build the seasonal-naive baseline first?

On seasonal data, "same weekday last week" is surprisingly strong. If a complex model cannot beat it on a backtest (b8), it is not earning its complexity.

Builder session 1 cheat sheet · pin this

A series = 4 partstrend + seasonality + cycle + noise. Forecasting projects the signal, not the noise.
Datetime indexreal timestamps, one fixed frequency (resample), no silent gaps. A missing day is not a zero.
Split by timetrain on the past, test on the future. Never shuffle - it leaks the future and flatters the score.
Holdout = horizonhold out exactly as many periods as you will forecast, and score there.
Plot firstthe trend, seasonal period, and outliers are usually visible before you model.
Seasonal-naive baselinetomorrow = same weekday last week. The line every model must beat + the fallback.
MAPEmean absolute percentage error - the plain-English accuracy number. Lower is better.
Next sessionb2 - decomposition (STL) and the family of baselines (naive, drift, seasonal-naive).