Define What Is Known Before the Event

PythonIntermediate
Practice Now

Introduction

A community studio prepares materials seven days before each workshop. Predicting attendance from post-event scans would look impressive but would be useless at that deadline. You will first establish a simple baseline, then decide which rows and columns a real earlier prediction may use.

You need Python functions, basic terminal input, pandas selection and joins, and introductory regression/train-test concepts. Python and the scientific libraries are prepared. Every table is original synthetic teaching data; the small sample demonstrates a workflow, not a production forecasting guarantee. The 55-minute duration is an author estimate including reading and ordinary corrections.

Measure a Simple Baseline in People

In this step, you will predict two later workshops from an early median and interpret the average absolute error.

Start in WebIDE, then open Terminal → New Terminal. The editor and terminal share files. cd changes the working directory in this terminal; a new terminal needs its own cd.

cd /home/labex/project/workshop-attendance

The prepared ./python launcher uses this course's Python environment. The ./ means “the executable in this directory.” Use it instead of a system Python when running the course files. cat displays a text file without editing it.

cat warmup-early.csv

This small warmup has three earlier actual attendance counts: 12, 18 and 24. Their median, the middle sorted value, is 18 people. A baseline predicts 18 for every later event without using any event features.

cat warmup-later.csv

The later actual counts are 20 for L-7 and 27 for L-2. Their absolute errors are abs(20 - 18) = 2 and abs(27 - 18) = 9. Mean absolute error (MAE) is their average: (2 + 9) / 2 = 5.5 people. “Average error of 5.5 people” is meaningful; a unitless “accuracy” would hide what is being measured. This tiny warmup is separate from the full season used next.

Create a reusable baseline function. The shell redirection > replaces baseline.py. The quoted here-document <<'PY' sends everything up to a line containing only PY into that file without shell expansion. Paste the entire block, including its final PY. You can instead edit the same file in the WebIDE and save with File → Save.

The runner passes two pandas tables into summarize. itertuples() visits rows with named fields; the dictionary associates each later workshop ID with its own error. float converts pandas numeric values into ordinary Python numbers for JSON output.

cat > baseline.py <<'PY'
def summarize(early, later):
    baseline = float(early.actual_attendance.median())
    errors = {
        row.session_id: float(abs(row.actual_attendance - baseline))
        for row in later.itertuples()
    }
    return {
        "median_attendance": baseline,
        "absolute_errors": errors,
        "mae": sum(errors.values()) / len(errors),
    }
PY

Run the prepared file-writing wrapper. Its baseline argument selects this stage; it saves your function's result in output/baseline.json. JSON stores named values and objects rather than CSV rows.

./python run.py baseline

The displayed values should include median 18.0, errors 2.0 and 9.0, and MAE 5.5. Read the saved artifact with the prepared reader, which never rebuilds missing results.

./python show_results.py baseline

You now have a benchmark whose error uses the same units as the planning problem. A later model must be compared on the same evaluation events; beating this warmup value on another sample would not be a fair comparison.

Separate Training Time from Prediction Time

In this step, you will associate outcomes by workshop ID and reserve later dates for evaluation.

Continue in the same WebIDE terminal at /home/labex/project/workshop-attendance. Read the season boundary and the three main tables.

cat season.json
cat history.csv
cat labels.csv
cat future.csv

The data dictionary is:

Field Meaning and availability Use
session_id Stable workshop identity; file order is arbitrary Associate rows, never a model feature
event_date Scheduled event day, ISO YYYY-MM-DD Decide the time split
booked_7d Booking snapshot saved exactly seven days before the event Allowed numeric feature
duration_minutes Workshop duration already announced at the seven-day deadline Allowed numeric feature
final_bookings Final booking total at the event Unavailable at the prediction deadline
scanned_entries Entry scans collected at the event Unavailable at the prediction deadline
actual_attendance Outcome known by the end of the event day Training target or held-out evaluation label

A blank numeric CSV cell means missing information, not zero bookings. Every allowed feature has at least one observed training value. IDs are unique within each table; labels.csv contains exactly one outcome per historical ID, deliberately in a different order. future.csv has no outcomes or post-event columns. Invalid keys, all-empty training features and rolling model updates are outside this course's input scope.

The model is trained once and frozen on April 1, 2026 at 00:00 UTC, as_of_date. Only events before that date can contribute training labels. In this supplied season the last training event is March 8, so those labels are already available. The holdout starts on April 12, holdout_start; its earliest seven-day prediction deadline is April 5, after the training freeze. There are no supplied historical events in the gap.

These are different clocks: April 1 freezes the fitted model; each later event supplies its own booked_7d snapshot on its own prediction date. The April 12 event's April 5 snapshot is used on April 5, not claimed to be known on April 1. This file-based exercise replays those saved snapshots. The later actual labels are revealed only to measure performance retrospectively. May workshops in future.csv remain unlabeled forecasts.

Replace the unfinished position-based split. A one-to-one merge associates labels by session_id; it does not assume row 1 matches row 1. pd.to_datetime parses calendar dates, pd.Timestamp parses the configured boundaries, and boolean masks select the corresponding rows. Sorting by date and then ID gives a readable order; ID spelling never determines the period. .copy() makes independent selected tables.

cat > split.py <<'PY'
import pandas as pd


def split_events(history, labels, season):
    events = history.merge(labels, on="session_id", validate="one_to_one")
    dates = pd.to_datetime(events["event_date"])
    train = events.loc[dates < pd.Timestamp(season["as_of_date"])].copy()
    holdout = events.loc[dates >= pd.Timestamp(season["holdout_start"])].copy()
    return (
        train.sort_values(["event_date", "session_id"]),
        holdout.sort_values(["event_date", "session_id"]),
    )
PY
./python run.py split

The training order should be W-91, W-04, W-77, W-12, W-63, W-02; the holdout IDs are W-88, W-09. These irregular IDs demonstrate why neither alphabetical order nor the last two file rows define the split.

./python show_results.py split

Check W-88: it must retain actual attendance 46 from its matching label, not the label at the same row position. The saved training.csv and holdout.csv still include post-event columns for inspection. The next step will select only the two allowed features; numeric storage does not make a column legitimate for prediction.

If you accidentally close the terminal, open Terminal → New Terminal and repeat the cd above. A “No such file or directory” error usually means the wrong directory or a misspelled filename. Use pwd to display the current path and ls to list its files, then return to this workspace and rerun the intended command. Do not restart the lab to correct an ordinary path mistake.

Learn Missing-Value Means from Training Only

In this step, you will fit one imputer on earlier workshops and reuse it on later snapshots without learning from them.

Continue in WebIDE at /home/labex/project/workshop-attendance. You may edit baseline.py, split.py, prepare.py and your output/ files. Preserve all supplied CSV/JSON inputs, python, run.py, coursekit.py, show_results.py and every starter-*.py. The starter copies let you recover a module, but you can also paste its complete taught version again. The main functions must remain reusable when dates, row orders and numeric values change.

Imputation replaces a missing feature with a learned value. SimpleImputer(strategy="mean") learns each column's arithmetic mean. Here booked_7d has training observations 10, 20, 40, 25 and 35: the mean is 26. The six known durations average 80 minutes. Using all periods would let later workshops influence these training choices, even without their outcome labels. Fit after the split, not before it.

FEATURES, supplied by coursekit, is exactly ['booked_7d', 'duration_minutes']. Double brackets select those two columns in that order. fit_transform learns means and fills the training matrix; transform reuses those means on holdout and future matrices. statistics_ exposes the learned means in feature order. The returned arrays preserve each input table's row order so the wrapper can retain its IDs.

cat > prepare.py <<'PY'
from sklearn.impute import SimpleImputer
from coursekit import FEATURES


def prepare_features(train, holdout, future):
    imputer = SimpleImputer(strategy="mean")
    training_values = imputer.fit_transform(train[FEATURES])
    holdout_values = imputer.transform(holdout[FEATURES])
    future_values = imputer.transform(future[FEATURES])
    return training_values, holdout_values, future_values, imputer.statistics_
PY
./python run.py prepare

The learned means should be booked_7d: 26.0 and duration_minutes: 80.0.

./python show_results.py prepare

In holdout-features.csv, W-88 uses 26 bookings and its observed 150 minutes; W-09 retains 90 bookings and uses 80 minutes. In future-features.csv, F-101 uses the same training booking mean. No final bookings, scans, dates, IDs or outcomes enter these numeric matrices. The original blank cells remain unchanged in the source files.

You have prepared a genuine earlier-to-later evaluation boundary. This does not prove the future behaves like the past. The next independent lab will fit a small model and compare its continuous errors with a training-median baseline on the same labeled holdout. Equivalent calculations and different output row orders are accepted when their ID associations and numerical meaning are unchanged; numeric comparisons allow 1e-8 absolute and relative tolerance.

Summary

You created a reusable median baseline, measured MAE in people, joined event outcomes by ID and separated earlier training from later evaluation dates. You selected only information available at the seven-day deadline and fitted missing-value means on training rows alone. The saved tables retain the original identities and preserve the source data for the next stage of analysis.