Week 1 · Your first end-to-end ML project
Read the words of the video
- Imagine cooking a delicious meal. You first shop for ingredients, then wash and chop them. Next, you cook, taste, and adjust the seasoning before serving. Skipping a step, like tasting, means you might serve something awful! Machine learning projects follow a very similar recipe.
- This week, you learn the first crucial steps of a machine learning recipe. First, we frame the problem: what are we predicting? Then, we gather data and take a first look. Most importantly, we prepare the data by exploring it, filling any holes, and turning words into numbers. This cleaning is vital!
- A key rule, just like a fair exam, is to hide a test set. You can't study with last year's paper and then take the same paper again. So, before doing any data exploration or modeling, you lock away a small part of your data. This hidden data helps honestly check your model's performance later.
- Here's an exam tip: always remember to 'fit' your data transformation tools only on the *training* data. Then, 'transform' both the training and the hidden test data. This avoids accidentally leaking information. You've learned to explore, clean, and protect your data. Great job preparing for your machine learning journey!
The one-minute story
Cooking a proper meal has steps: shop for ingredients, wash and chop them, cook, taste, fix the seasoning, then serve. Skip washing and your dinner is gritty; skip tasting and you serve something awful.
A machine learning project is exactly that, and the code in this course is the kitchen. This week you walk through the whole recipe once, end to end, on a tiny example:
- Frame the problem (what are we predicting, and how will we judge it?).
- Get the data and take a first look with pandas (a spreadsheet inside Python).
- Hide a test set before you do anything else (so you can taste the dish honestly at the end).
- Explore and clean the rest: fill the holes, turn words into numbers, put numbers on the same scale.
Next week we finish the meal: pipelines, training, tuning and serving.
By the end of this week you can:
- list the stages of an end-to-end ML project and the scikit-learn design principles;
- explore a pandas DataFrame: shape, missing values, summary statistics, groups, filters;
- split data into train and test (random and stratified), and say why you must;
- impute missing values, encode categories and scale features, fitting on the training data only.
Words you need
- DataFrame - pandas' table: rows are examples, columns are features (and the label).
- Feature matrix and label vector - the inputs and the thing to predict.
- Train / test split - the data the model learns from vs data hidden for the final honest check.
- Data leakage - information from the test set sneaking into training, giving falsely good scores.
- Imputation - filling missing values.
- Encoding - turning categories (words) into numbers.
- Scaling - rewriting features so they have comparable ranges.
- Estimator / transformer / predictor - scikit-learn's three kinds of objects (below).
Idea 1: The stages of a project
- Look at the big picture: what is the goal? Is it supervised? Regression or classification? What measure of success?
- Get the data.
- Explore it to gain insight.
- Prepare the data (clean, encode, scale).
- Select a model and train it.
- Fine-tune it.
- Present the solution.
- Launch, monitor and maintain.
Choosing the performance measure. For regression the usual measure is the RMSE (root mean squared error), , in the units of the target. When there are many outliers, the MAE (mean absolute error) is less sensitive: squaring makes big errors count a lot. RMSE is the norm of the error vector (divided by ), MAE the norm.
Idea 2: scikit-learn's design principles
Quiz questions often ask: "Scikit-Learn's API is well designed. The main principles are...". Remember five words:
- Consistency: every object has a simple, uniform interface.
- Inspection: all hyperparameters and learned values are public attributes. Learned values end with an underscore (
coef_,statistics_,mean_). - Non-proliferation of classes: data is plain NumPy arrays / SciPy sparse matrices / pandas, not special classes. Hyperparameters are plain strings and numbers.
- Composition: building blocks combine (a Pipeline is itself an estimator).
- Sensible defaults: you can get a working model quickly.
Three kinds of objects:
| Kind | Key methods | Examples |
|---|---|---|
| Estimator | fit(X, y) | everything that learns |
| Transformer | transform(X), fit_transform(X) | SimpleImputer, StandardScaler, OneHotEncoder |
| Predictor | predict(X), score(X, y) | LinearRegression, DecisionTreeRegressor |
The golden rule you will meet again and again: fit on the training data only; transform both. Calling fit on the test data leaks information.
Idea 3: A first look with pandas
We will use a tiny table of 8 houses so every output fits on screen.
import numpy as np
import pandas as pd
df = pd.DataFrame({
"area": [50, 80, 65, np.nan, 120, 95, 70, 60],
"rooms": [2, 3, 2, 3, 4, 3, np.nan, 2],
"city": ["A", "B", "A", "C", "B", "A", "C", "B"],
"price": [30, 52, 40, 55, 90, 70, 48, 38],
})
The commands you use first, with the real output for this table:
df.shape # (8, 4) 8 rows, 4 columns
df.isna().sum() # missing values per column
df.describe() # count, mean, std, min, quartiles, max (numbers only)
df["city"].value_counts() # how often each category occurs
df.groupby("city")["price"].mean()
df.dropna().shape # rows left if we drop every row with a hole
shape: (8, 4)
isna: area 1, rooms 1, city 0, price 0
describe -> count area 7, rooms 7, price 8
mean area 77.14, rooms 2.71, price 52.88
min area 50, rooms 2, price 30
max area 120, rooms 4, price 90
value_counts: A 3, B 3, C 2
groupby city mean price: A 46.67, B 60.0, C 51.5
dropna().shape: (6, 4)
Notice count is 7 for area and rooms: describe ignores the missing values. And dropna() would throw away 2 of our 8 rows, a quarter of the data. That is wasteful, which is why we impute instead.
Other everyday pandas moves:
df["area"] # one column (a Series)
df[["area", "price"]] # several columns (a DataFrame)
df[df["price"] > 50] # rows where price > 50 (boolean filter)
df.loc[2, "price"] # by LABEL: row label 2, column "price"
df.iloc[2, 3] # by POSITION: 3rd row, 4th column
df["price_per_m2"] = df["price"] / df["area"] # a new column
df.sort_values("price", ascending=False).head(3)
df.groupby("city")["price"].agg(["mean", "median", "count"])
df.corr(numeric_only=True) # Pearson correlation matrix
.locuses labels and includes the end of a slice;.ilocuses positions and excludes the end.- The default
df.std()in pandas uses (ddof=1); NumPy'snp.stduses (ddof=0). df.corr()measures linear relationship only: a perfect curve can still give correlation near 0. It lies between and .df.isna().sum()counts missing values per column;df.isna().sum().sum()counts all of them.
Idea 4: Hide a test set first
Suppose you study with last year's paper, then get tested on last year's paper: of course you do well. That is not learning, that is memorising. A fair exam uses fresh questions you never saw. So before you explore or model anything, you lock a piece of the data in a drawer and do not touch it until the very end. Its score is the honest estimate of how the model will do on future data.
from sklearn.model_selection import train_test_split
X = df.drop(columns="price") # features
y = df["price"] # label
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.25, random_state=42)
print(X_train.shape, X_test.shape) # (6, 3) (2, 3)
test_size=0.25gives 2 of 8 rows to the test set. If you omittest_sizeandtrain_size, the default test share is 0.25.random_state=42makes the "random" shuffle repeatable, so you and your friend get identical splits.- By default the split is random. With a small or imbalanced data set, a random split can accidentally mis-represent a class. Stratified sampling fixes this: it keeps the class proportions the same in train and test:
train_test_split(X, y, test_size=0.2, stratify=y, random_state=0)
With labels that are 80% class 0 and 20% class 1, the test set keeps exactly 20% class 1. (Also available: StratifiedShuffleSplit.) For a regression target you can stratify on a binned version (e.g. income categories) of an important feature.
Why do we need a test set at all? (A standard 2-mark question.) It lets us simulate the model's performance on new data by reserving a portion for testing; without it we would only know how well the model fits data it has already seen, hiding overfitting. Risk of not splitting: an over-optimistic estimate of performance.
Idea 5: Cleaning missing values
Three options for a column with holes: drop the rows (dropna), drop the column, or fill the holes. Filling is done with SimpleImputer:
from sklearn.impute import SimpleImputer
imputer = SimpleImputer(strategy="median")
imputer.fit(X_train[["area", "rooms"]]) # learns the medians from TRAIN only
print(imputer.statistics_) # the learned fill values
X_filled = imputer.transform(X_train[["area", "rooms"]])
On the full 8-row table the learned medians are [70. 3.] (area 70, rooms 3), and no nan remains afterwards. With strategy="mean" the fill values would be [77.143 2.714].
strategy= | Fills with | Notes |
|---|---|---|
"mean" (default) | column mean | sensitive to outliers |
"median" | column median | robust to outliers |
"most_frequent" | most common value | works for text columns too |
"constant" | fill_value=... | you choose |
Also: KNNImputer(n_neighbors=5) fills a hole from the average of the most similar rows; add_indicator=True appends a column that records which values were missing. The learned values live in statistics_ (an attribute with a trailing underscore: inspection in action).
Idea 6: Words to numbers (encoding categories)
Models need numbers. For the city column ("A", "B", "C"):
from sklearn.preprocessing import OneHotEncoder, OrdinalEncoder
OneHotEncoder().fit_transform(df[["city"]]) # 8x3 SPARSE matrix of 0/1
OrdinalEncoder().fit_transform(df[["city"]]) # 0.,1.,2. integers in one column
OneHotEncoder first rows -> A: [1,0,0] B: [0,1,0] A: [1,0,0]
OrdinalEncoder -> [0, 1, 0, 2, 1, 0, 2, 1]
Which one? An ordinal code says "A < B < C" and that B is "twice" A, which is only meaningful for ordered categories (small < medium < large). For plain nominal categories like city names use one-hot, which gives each category its own 0/1 column with no fake order. One-hot returns a sparse matrix by default (.toarray() to see it); handle_unknown="ignore" stops an error when the test data contains a category never seen in training; drop="first" removes one column to avoid redundancy.
LabelEncoder is for targets only (it turns ["cat","dog","cat","bird"] into [1, 2, 1, 0] with classes_ = ['bird', 'cat', 'dog']).
Idea 7: Feature scaling
If one feature is in the hundreds (area) and another in single digits (rooms), distance-based and gradient-based methods get dominated by the big one. Two standard fixes:
| Scaler | Formula | Result |
|---|---|---|
MinMaxScaler | values in (e.g. ) | |
StandardScaler | mean 0, standard deviation 1 |
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler().fit(X_train[["area"]])
print(scaler.mean_, scaler.scale_) # learned from train: attributes end with _
StandardScaler is much less affected by outliers than MinMaxScaler, which a single extreme value can squash. Tree-based models do not need scaling; linear models with regularisation, k-NN, SVMs and neural networks do.
Solved examples
Question. For the 8-house table, answer without running code: (a) how many missing values in total? (b) what does df.groupby("city")["price"].mean() give for city B? (c) how many rows survive dropna()?
Solution.
(a) area has one nan and rooms has one: 2 in total.
(b) City B has prices . Mean .
(c) The nan in area is in row 3 and the one in rooms is in row 6, two different rows, so rows remain.
Question. Match the goal to the code. (i) count the null values in the column rooms; (ii) median price for each city; (iii) add a Boolean column Cheap that is True when the price is below 50.
Solution.
(i) df["rooms"].isna().sum()
(ii) df.groupby("city")["price"].median()
(iii) df["Cheap"] = df["price"] < 50
Question. An OneHotEncoder is fitted on cities ["A","B","A","C"]. At test time the city "D" appears. What happens with default settings, and what with handle_unknown="ignore"?
Solution. By default it raises an error (unknown category). With handle_unknown="ignore" the row is encoded as all zeros [0,0,0], and the pipeline keeps running.
Your turn
X has 1000 rows and 8 feature columns, y is one column. After train_test_split(X, y, test_size=0.2), what are the four shapes?
Need a hint?
rows go to the test set.
Show the full solution
X_train , X_test , y_train , y_test .
A column has values . What fill value does SimpleImputer use with strategy="mean", and with "median"?
Need a hint?
Ignore the nan entries.
Show the full solution
Observed: . Mean . Median: sorted , middle two and , so . The median is less affected by the outlier 20.
A student runs scaler.fit_transform(X) on all the data and only then calls train_test_split. What is wrong?
Need a hint?
Think about what fit learns.
Show the full solution
The mean and standard deviation were computed using the test rows too, so information from the test set has leaked into the training pipeline: data leakage. Correct: split first, fit the scaler on X_train only, then transform both.
Which encoder for (a) size in {small, medium, large}; (b) country in {India, Japan, Brazil}?
Need a hint?
Is there a natural order?
Show the full solution
(a) Ordinal (small < medium < large; pass the order via categories=). (b) One-hot (no natural order).
A data set has 90 negatives and 10 positives. You split 70/30 with stratify=y. How many positives are in the test set?
Need a hint?
Keep the 10% proportion.
Show the full solution
Test size 30, so of 30 positives (and 27 negatives).
Exam radar
MLP Quiz 1 (26 papers) summaries mention data preprocessing in 18, imputation in 13, train-test split in 13, ColumnTransformer in 15, Pandas operations in 7, handling missing values in 8, one-hot encoding in 10, label encoding in 8, StandardScaler in 7, MinMaxScaler in 6, reasons for preprocessing in 7, risk of not performing a train-test split in 7, Pearson correlation in several, and the sklearn API design principles in 4.
Question styles: "which code gives the median of Balance per Marital category?", "count the nan values in a column", "which sklearn object can reduce, expand or generate feature representations?", "what is the shape of X and y for this code?", "which statements about the train-test split are true?", and "which of these is a design principle of the scikit-learn API?".
- Fit on train, transform both. Never
fiton test. train_test_splitdefaulttest_sizeis 0.25, not 0.2.OneHotEncoderreturns a sparse matrix; use.toarray()to look at it.LabelEncoderis for the target, not features..locend-inclusive,.ilocend-exclusive;ddofdiffers between pandas and NumPy.describe()counts non-missing values only.
Quick quiz
Which scikit-learn method learns values (like medians) from data?
- transform
- fit
- predict
- score
Why must a test set be kept aside before modelling?
- To estimate performance on new data honestly.
- To make the training faster.
- To remove outliers.
- To balance the classes.
Which encoder is suitable for the nominal feature "colour" (red, green, blue)?
- LabelEncoder on the features
- OrdinalEncoder
- OneHotEncoder
- StandardScaler
Select all TRUE statements. (select all that apply)
- Learned attributes in scikit-learn end with an underscore.
- SimpleImputer(strategy="median") is robust to outliers.
- The default test_size of train_test_split is 0.2.
- You should fit the scaler on the whole data before splitting.
Recap
- Project stages: frame, get data, explore, prepare, model, tune, present, monitor. Judge regression by RMSE (outlier-sensitive) or MAE.
- sklearn design: consistency, inspection, non-proliferation of classes, composition, sensible defaults. Estimator
fit; transformertransform; predictorpredict. - Split first (
train_test_split, default 0.25;stratify=keeps class ratios). Fit on train only. - Impute (
SimpleImputer), encode (one-hot for nominal, ordinal for ordered), scale (StandardScaler,MinMaxScaler).
- RMSE ; MAE
- Standardise ; min-max
train_test_split(X, y, test_size=0.25, random_state=..., stratify=y)SimpleImputer(strategy="mean"|"median"|"most_frequent"|"constant"); learnedstatistics_OneHotEncoder(handle_unknown="ignore"),OrdinalEncoder(),LabelEncoder()(targets)
- MLF week 1 - What is machine learning, and which maths do we need?: What 'learning from data' means, then your first end-to-end project.
Practise with real exam questions
These come straight from past papers (with the real figures and the official answer key). Open 60 real questions on this week All Quiz 1 papers