ML Study Hub

Home / MLP / Week 1

Week 1 · Your first end-to-end ML project

Official syllabus line: End-to-end machine learning project on scikit-learn
About 90 minQuiz 1 material (see exam radar)BSCS2008 · Ashish Tendulkar
Before you start: Python basics (lists, functions, import). MLF/MLT week 1 helps but is not required. You do NOT need to know pandas or scikit-learn yet.
Watch first: the animated story of this weekAbout 1-2 minutes of real 3D animation with a friendly voice. Pause any time.
Read the words of the video
  1. 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.
  2. 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!
  3. 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.
  4. 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

Think of it like this

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:

  1. Frame the problem (what are we predicting, and how will we judge it?).
  2. Get the data and take a first look with pandas (a spreadsheet inside Python).
  3. Hide a test set before you do anything else (so you can taste the dish honestly at the end).
  4. 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

Words you need
  • DataFrame - pandas' table: rows are examples, columns are features (and the label).
  • Feature matrix XX and label vector yy - 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

The idea
  1. Look at the big picture: what is the goal? Is it supervised? Regression or classification? What measure of success?
  2. Get the data.
  3. Explore it to gain insight.
  4. Prepare the data (clean, encode, scale).
  5. Select a model and train it.
  6. Fine-tune it.
  7. Present the solution.
  8. Launch, monitor and maintain.

Choosing the performance measure. For regression the usual measure is the RMSE (root mean squared error), 1n∑(yi−y^i)2\sqrt{\frac1n\sum(y_i-\hat y_i)^2}, in the units of the target. When there are many outliers, the MAE (mean absolute error) 1n∑∣yi−y^i∣\frac1n\sum|y_i-\hat y_i| is less sensitive: squaring makes big errors count a lot. RMSE is the ℓ2\ell_2 norm of the error vector (divided by n\sqrt n), MAE the ℓ1\ell_1 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:

KindKey methodsExamples
Estimatorfit(X, y)everything that learns
Transformertransform(X), fit_transform(X)SimpleImputer, StandardScaler, OneHotEncoder
Predictorpredict(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
Watch out - real exam trap
  • .loc uses labels and includes the end of a slice; .iloc uses positions and excludes the end.
  • The default df.std() in pandas uses n−1n-1 (ddof=1); NumPy's np.std uses nn (ddof=0).
  • df.corr() measures linear relationship only: a perfect curve can still give correlation near 0. It lies between −1-1 and +1+1.
  • df.isna().sum() counts missing values per column; df.isna().sum().sum() counts all of them.

Idea 4: Hide a test set first

Think of it like this

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.25 gives 2 of 8 rows to the test set. If you omit test_size and train_size, the default test share is 0.25.
  • random_state=42 makes 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 withNotes
"mean" (default)column meansensitive to outliers
"median"column medianrobust to outliers
"most_frequent"most common valueworks 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:

ScalerFormulaResult
MinMaxScalerx−min⁡max⁡−min⁡\dfrac{x-\min}{\max-\min}values in [0,1][0,1] (e.g. 1,5,9→0, 0.5, 11,5,9\to0,\,0.5,\,1)
StandardScalerx−μσ\dfrac{x-\mu}{\sigma}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

Solved example: 1. Reading the table

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 52, 90, 3852,\,90,\,38. Mean =1803=60=\frac{180}3=\mathbf{60}.

(c) The nan in area is in row 3 and the one in rooms is in row 6, two different rows, so 8−2=68-2=\mathbf6 rows remain.

Solved example: 2. Which code does what?

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

Solved example: 3. Where do the numbers come from?

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

Your turn: 1. Predict the shape

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?

0.2×10000.2\times1000 rows go to the test set.

Show the full solution

X_train (800,8)(800,8), X_test (200,8)(200,8), y_train (800,)(800,), y_test (200,)(200,).

Your turn: 2. Fill the holes

A column has values 4, nan, 8, nan, 6, 204,\,\text{nan},\,8,\,\text{nan},\,6,\,20. What fill value does SimpleImputer use with strategy="mean", and with "median"?

Need a hint?

Ignore the nan entries.

Show the full solution

Observed: 4,8,6,204,8,6,20. Mean =384=9.5=\frac{38}4=\mathbf{9.5}. Median: sorted 4,6,8,204,6,8,20, middle two 66 and 88, so 7\mathbf7. The median is less affected by the outlier 20.

Your turn: 3. Spot the leak

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.

Your turn: 4. Ordinal or one-hot?

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).

Your turn: 5. Stratified split

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 10%10\% of 30 =3=\mathbf3 positives (and 27 negatives).

Exam radar

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?".

Watch out - real exam trap
  • Fit on train, transform both. Never fit on test.
  • train_test_split default test_size is 0.25, not 0.2.
  • OneHotEncoder returns a sparse matrix; use .toarray() to look at it.
  • LabelEncoder is for the target, not features.
  • .loc end-inclusive, .iloc end-exclusive; ddof differs between pandas and NumPy.
  • describe() counts non-missing values only.

Quick quiz

Quick quiz

Which scikit-learn method learns values (like medians) from data?

  • transform
  • fit
  • predict
  • score
fit learns from the data; transform applies what was learned.

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.
Its score simulates performance on unseen data. Evaluating on training data hides overfitting.

Which encoder is suitable for the nominal feature "colour" (red, green, blue)?

  • LabelEncoder on the features
  • OrdinalEncoder
  • OneHotEncoder
  • StandardScaler
One-hot avoids creating a fake order. LabelEncoder is meant for targets.

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.
The default is 0.25, and fitting on all data leaks test information.

Recap

Remember this
  • 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; transformer transform; predictor predict.
  • 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).
Formulas to keep
  • RMSE =1n∑(y−y^)2=\sqrt{\frac1n\sum(y-\hat y)^2}; MAE =1n∑∣y−y^∣=\frac1n\sum|y-\hat y|
  • Standardise x−μσ\frac{x-\mu}\sigma; min-max x−min⁡max⁡−min⁡\frac{x-\min}{\max-\min}
  • train_test_split(X, y, test_size=0.25, random_state=..., stratify=y)
  • SimpleImputer(strategy="mean"|"median"|"most_frequent"|"constant"); learned statistics_
  • OneHotEncoder(handle_unknown="ignore"), OrdinalEncoder(), LabelEncoder() (targets)
Connected weeks (learn them together)

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