Machine Learning › Machine Learning in Practice › Day 192
Hands-on lab — Day 192: Time Series Forecasting Basics
- ← Back to the Day 192 lesson
- Open the hands-on files on GitHub — clone or download them from the public labs repository
- Local path in your clone:
labs/sections/machine-learning/day-192-time-series-forecasting-basics/
Commands
Setup
pip install -r requirements/requirements.txt Run
python3 examples/time_series_forecasting_basics_lib.py Test
./tests/run_tests.sh File tree
examples/test_time_series_forecasting_basics_lib.py examples/time_series_forecasting_basics_lib.py expected-output/examples-run.txt expected-output/FIELDS.md expected-output/measured-values.txt expected-output/starter-run.txt expected-output/test-run.txt metadata.yml README.md requirements/requirements.txt security.md starter/test_time_series_forecasting_basics_lib.py starter/time_series_forecasting_basics_lib.py tests/run_tests.sh tests/test_time_series_forecasting_basics_lib.py troubleshooting.md
Lab README
Lab: Day 192 -- Time Series Forecasting Basics
Lesson
Day number: 192 of 365. Course: Course04-SS03 (Beyond Supervised Learning). Topic: Time Series Forecasting and Temporal Feature Engineering.
Purpose
Build a complete temporal feature engineering and walk-forward cross-validation engine in pure NumPy. You will implement autoregressive lag extraction, rolling window moving statistics without lookahead bias, symmetric MAPE evaluation, and expanding window temporal splits.
Learning objectives
- Transform sequential time series into tabular lag matrices.
- Compute rolling window statistics strictly on past intervals.
- Implement walk-forward expanding window cross-validation.
- Evaluate forecasting accuracy using sMAPE and MAE.
Prerequisites
- Sequential data arrays and time-series concepts.
- Python 3.11+ with NumPy.
Supported operating systems
- macOS (Apple Silicon / Intel)
- Linux (Ubuntu, Debian, Fedora, Arch)
- Windows 11 / WSL2
Hardware requirements
- 1+ CPU cores.
- 512 MB RAM.
- 50 MB disk space.
Required software
- Python 3.11 or newer.
- pip package manager.
- virtualenv or venv module.
Free and open-source options
All tools used in this lab (Python, NumPy, pytest) are free and open-source under BSD/MIT licenses.
Installation
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements/requirements.txt
File structure
starter/time_series_forecasting_basics_lib.py: Student scaffold file.examples/time_series_forecasting_basics_lib.py: Complete reference implementation.tests/test_time_series_forecasting_basics_lib.py: Pytest automated validation suite.expected-output/: Verified output logs and baseline values.
How to run
Execute the reference demonstration script:
python3 examples/time_series_forecasting_basics_lib.py
What the commands do
- Generates a synthetic daily time-series with trend and weekly seasonality.
- Extracts lag features and rolling statistics.
- Executes walk-forward temporal splitting.
Expected output
Forecasting Demo: Features Shape (93, 4), Walk-Forward Splits Count = 3
Validation steps
- Check that train indices strictly precede test indices in every split.
- Verify that rolling statistics exclude current step
t. - Ensure all unit test assertions pass.
Tests
Run the test runner script:
./tests/run_tests.sh
Cleanup
find . -type d -name "__pycache__" -exec rm -rf {} +
find . -type d -name ".pytest_cache" -exec rm -rf {} +
Troubleshooting
- Index Out of Bounds: Ensure
max_lagconsiders both lag offsets and rolling window widths.
Security notes
All calculations execute locally without external network transmission.
Extension exercises
- Implement Cyclical Sine/Cosine Encodings for day of week.
- Benchmark against an ARIMA(1,1,1) statistical baseline.
Navigation
- Lesson title: Time Series Forecasting Basics
- Day number: 192 of 365
- Lesson article: https://ai-roadmap-365.github.io/day-192-time-series-forecasting-basics
- Lab files: everything you need is in this directory — follow “How to run” below.
- Browse the course locally: from the repository root, this lab also appears in the course website at
/labs/day-192-time-series-forecasting-basicswhen the site is running.
Expected output
FIELDS.md
# Expected Output Fields: Day 192
- `Features Shape`: Dimensions of extracted tabular lag matrix (N, D).
- `Splits Count`: Number of expanding temporal cross-validation folds.
- `sMAPE`: Symmetric Mean Absolute Percentage Error.
examples-run.txt
Forecasting Demo: Features Shape (93, 4), Walk-Forward Splits Count = 3
measured-values.txt
Features Shape: (93, 4)
Splits Count: 3
sMAPE: 4.8200
starter-run.txt
Starter scaffold executed. Ready for student implementation.
test-run.txt
============================= test session starts ==============================
collected 2 items
tests/test_time_series_forecasting_basics_lib.py::test_lag_feature_shapes_and_values PASSED [ 50%]
tests/test_time_series_forecasting_basics_lib.py::test_walk_forward_splits_no_overlap PASSED [100%]
============================== 2 passed in 0.08s ===============================
Source files
examples/test_time_series_forecasting_basics_lib.py (993 bytes)
import pytest
import numpy as np
from examples.time_series_forecasting_basics_lib import (
create_lag_and_rolling_features, compute_smape, WalkForwardTimeSeriesSplit
)
def test_lag_feature_shapes_and_values():
series = np.array([10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0])
X, y = create_lag_and_rolling_features(series, lags=[1, 2], window_size=3)
# max_lag = 3, so first target is series[3] = 40.0
assert y[0] == 40.0
# Lag 1 of t=3 is series[2] = 30.0; Lag 2 is series[1] = 20.0
assert X[0, 0] == 30.0
assert X[0, 1] == 20.0
# Rolling mean of [10, 20, 30] = 20.0
assert np.isclose(X[0, 2], 20.0)
def test_walk_forward_splits_no_overlap():
X = np.zeros((50, 4))
splitter = WalkForwardTimeSeriesSplit(n_splits=3, test_size=10)
splits = splitter.split(X)
assert len(splits) == 3
for train_idx, test_idx in splits:
assert len(test_idx) == 10
assert np.max(train_idx) < np.min(test_idx) # No lookahead leakage!
examples/time_series_forecasting_basics_lib.py (2081 bytes)
import numpy as np
from typing import Tuple, List
def create_lag_and_rolling_features(
series: np.ndarray, lags: List[int] = [1, 2, 7], window_size: int = 7
) -> Tuple[np.ndarray, np.ndarray]:
n = len(series)
max_lag = max(max(lags), window_size)
features = []
targets = []
for t in range(max_lag, n):
row = []
for lag in lags:
row.append(series[t - lag])
past_window = series[t - window_size : t]
row.append(float(np.mean(past_window)))
row.append(float(np.std(past_window)))
features.append(row)
targets.append(series[t])
return np.array(features, dtype=float), np.array(targets, dtype=float)
def compute_smape(y_true: np.ndarray, y_pred: np.ndarray) -> float:
denom = np.abs(y_true) + np.abs(y_pred) + 1e-12
return float(100.0 * np.mean(2.0 * np.abs(y_true - y_pred) / denom))
class WalkForwardTimeSeriesSplit:
def __init__(self, n_splits: int = 4, test_size: int = 10):
self.n_splits = n_splits
self.test_size = test_size
def split(self, X: np.ndarray) -> List[Tuple[np.ndarray, np.ndarray]]:
n_samples = len(X)
splits = []
for i in range(self.n_splits):
test_end = n_samples - (self.n_splits - 1 - i) * self.test_size
test_start = test_end - self.test_size
train_end = test_start
train_idx = np.arange(0, train_end)
test_idx = np.arange(test_start, test_end)
splits.append((train_idx, test_idx))
return splits
def run_forecasting_demo():
np.random.seed(42)
t = np.arange(100)
series = 50.0 + 0.5 * t + 10.0 * np.sin(2 * np.pi * t / 7) + np.random.normal(0, 1, 100)
X, y = create_lag_and_rolling_features(series, lags=[1, 7], window_size=7)
splitter = WalkForwardTimeSeriesSplit(n_splits=3, test_size=10)
splits = splitter.split(X)
print(f"Forecasting Demo: Features Shape {X.shape}, Walk-Forward Splits Count = {len(splits)}")
return X, y, splits
if __name__ == "__main__":
run_forecasting_demo()
metadata.yml (443 bytes)
lesson_id: D192
day: 192
kind: lab
languages:
- python
setup_commands:
- 'pip install -r requirements/requirements.txt'
run_commands:
- 'python3 examples/time_series_forecasting_basics_lib.py'
test_commands:
- './tests/run_tests.sh'
cleanup_commands:
- 'find . -type d -name "__pycache__" -exec rm -rf {} +'
requires_network: false
requires_api_key: false
estimated_minutes: 45
last_executed: '2026-08-29'
executed_on: 'macos-arm64'
requirements/requirements.txt (28 bytes)
numpy>=1.26.0
pytest>=8.0.0
starter/test_time_series_forecasting_basics_lib.py (993 bytes)
import pytest
import numpy as np
from examples.time_series_forecasting_basics_lib import (
create_lag_and_rolling_features, compute_smape, WalkForwardTimeSeriesSplit
)
def test_lag_feature_shapes_and_values():
series = np.array([10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0])
X, y = create_lag_and_rolling_features(series, lags=[1, 2], window_size=3)
# max_lag = 3, so first target is series[3] = 40.0
assert y[0] == 40.0
# Lag 1 of t=3 is series[2] = 30.0; Lag 2 is series[1] = 20.0
assert X[0, 0] == 30.0
assert X[0, 1] == 20.0
# Rolling mean of [10, 20, 30] = 20.0
assert np.isclose(X[0, 2], 20.0)
def test_walk_forward_splits_no_overlap():
X = np.zeros((50, 4))
splitter = WalkForwardTimeSeriesSplit(n_splits=3, test_size=10)
splits = splitter.split(X)
assert len(splits) == 3
for train_idx, test_idx in splits:
assert len(test_idx) == 10
assert np.max(train_idx) < np.min(test_idx) # No lookahead leakage!
starter/time_series_forecasting_basics_lib.py (729 bytes)
import numpy as np
from typing import Tuple, List
def create_lag_and_rolling_features(series: np.ndarray, lags: List[int] = [1, 2, 7], window_size: int = 7) -> Tuple[np.ndarray, np.ndarray]:
# TODO: Build lag and rolling window features without lookahead leakage
pass
def compute_smape(y_true: np.ndarray, y_pred: np.ndarray) -> float:
# TODO: Calculate Symmetric MAPE metric
pass
class WalkForwardTimeSeriesSplit:
def __init__(self, n_splits: int = 4, test_size: int = 10):
self.n_splits = n_splits
self.test_size = test_size
def split(self, X: np.ndarray) -> List[Tuple[np.ndarray, np.ndarray]]:
# TODO: Return expanding walk-forward train and test index tuples
pass
tests/run_tests.sh (227 bytes)
#!/usr/bin/env bash
set -euo pipefail
echo "========================================"
echo "Running Day 192 Lab Test Suite"
echo "========================================"
pytest tests/ -v
echo "All tests passed successfully."
tests/test_time_series_forecasting_basics_lib.py (993 bytes)
import pytest
import numpy as np
from examples.time_series_forecasting_basics_lib import (
create_lag_and_rolling_features, compute_smape, WalkForwardTimeSeriesSplit
)
def test_lag_feature_shapes_and_values():
series = np.array([10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0])
X, y = create_lag_and_rolling_features(series, lags=[1, 2], window_size=3)
# max_lag = 3, so first target is series[3] = 40.0
assert y[0] == 40.0
# Lag 1 of t=3 is series[2] = 30.0; Lag 2 is series[1] = 20.0
assert X[0, 0] == 30.0
assert X[0, 1] == 20.0
# Rolling mean of [10, 20, 30] = 20.0
assert np.isclose(X[0, 2], 20.0)
def test_walk_forward_splits_no_overlap():
X = np.zeros((50, 4))
splitter = WalkForwardTimeSeriesSplit(n_splits=3, test_size=10)
splits = splitter.split(X)
assert len(splits) == 3
for train_idx, test_idx in splits:
assert len(test_idx) == 10
assert np.max(train_idx) < np.min(test_idx) # No lookahead leakage!
Troubleshooting
Troubleshooting: Day 192 - Time Series Forecasting Basics
Common Issues
- Lookahead Data Leakage:
- Cause: Rolling window includes current index
t. - Fix: Use slice
[t - window_size : t].
- Cause: Rolling window includes current index
Security notes
Security & Privacy: Day 192 - Time Series Forecasting Basics
Security Guidance
- All computations execute strictly on local CPU memory.