Module 6 · Section 8 of 8
Capstone Project
Pick one track and produce a short technical report (analysis notebook + 1-2 page write-up). Each track exercises the full workflow from Modules 2-5: load -> clean -> explore -> model -> interpret.
Track A - Extreme-Value Analysis (any discipline)
Given an annual-maximum series (peak rainfall, peak demand, peak load, worst-case process upset), fit a Gumbel distribution, build a return-period table (10/25/50/100 yr), check the fit, and recommend a design value with justification.
Track B - Consumption / Demand Forecasting
Given a monthly series with trend and seasonality, build a forecast model (trend+seasonal regression, or ARIMA/SARIMA), produce a 12-month forecast with uncertainty, back-test it on held-out data, and state the expected error.
Track C - Classification / Anomaly Detection
Given a labeled dataset of "normal" vs "fault" (equipment, samples, transactions), train a classifier, evaluate it honestly with a train/test split and precision/recall, and identify which features matter most.
Starter Datasets
import numpy as np, pandas as pd
from scipy import stats
# Three starter datasets - pick ONE track for your capstone report.
# --- Track A: 35-year annual maximum series (frame the units to your discipline) ---
np.random.seed(201)
track_a = pd.DataFrame({
"year": np.arange(1989, 2024),
"annual_max": np.round(stats.gumbel_r.rvs(loc=200, scale=48, size=35, random_state=201), 1),
})
# --- Track B: 120 months with a linear trend + a 12-month seasonal cycle + noise ---
np.random.seed(202)
tb = np.arange(120)
track_b = pd.DataFrame({
"date": pd.date_range("2014-01-01", periods=120, freq="MS"),
"value": np.round(5000 + 9 * tb + 400 * np.sin(2 * np.pi * tb / 12)
+ np.random.normal(0, 120, 120), 1),
})
# --- Track C: 600 labelled units - 3 numeric features + a 0/1 fault label ---
np.random.seed(203)
n = 600
f1 = np.random.uniform(0, 10, n)
f2 = np.random.uniform(20, 80, n)
f3 = np.random.uniform(100, 5000, n)
# a hidden weighted rule (with noise) decides the label
score = 0.4 * (f1 - 5) + 0.05 * (f2 - 50) + 0.0004 * (f3 - 2500) + np.random.normal(0, 1, n)
track_c = pd.DataFrame({"feat_1": f1.round(2), "feat_2": f2.round(1),
"feat_3": f3.round(0), "fault": (score > 0.6).astype(int)})
print("Track A:", track_a.shape, " Track B:", track_b.shape, " Track C:", track_c.shape)
print(track_c["fault"].value_counts().to_dict()) # check the class balance for Track C
track_a.head()
Suggested Workflow (all tracks)
- Inspect - shape, dtypes, missing values, obvious outliers.
- Clean - decide drop vs. fill, and document the choice.
- Explore - at least two charts that show the structure you're about to model.
- Model - fit, and state your assumptions.
- Validate - a fit check (Track A), a back-test (Track B), or a train/test split with precision/recall (Track C).
- Interpret - 2-3 sentences a non-specialist decision-maker could act on, plus the main limitation of your analysis.
Grading Rubric
| Criterion | Points |
|---|---|
| Data handled correctly (inspect / clean, documented) | 20 |
| Appropriate model, correctly applied | 30 |
| Honest validation (fit check / back-test / held-out metrics) | 25 |
| Charts that support the argument | 15 |
| Written interpretation with a stated limitation | 10 |
| Total | 100 |
63 / 63 sections · Course home · Join the coaching cohort