LECTURE 12

Preparing features

Missing values, categories, scaling and pipelines

Today's key line of code
make_pipeline(StandardScaler(),
              KNeighborsClassifier())

The road ahead: machine learning

L7Charts and stats
L8What is ML?
L9Regression
L10Classification
L11Evaluation
TodayL12Features
L13Clustering
L14Responsible ML

Today you will

  1. 1Find and handle missing values
  2. 2Turn categories into numbers with one-hot encoding
  3. 3See why scaling changes k-NN, by hand
  4. 4Scale without leaking test information
  5. 5Build a pipeline that does it all correctly
Md Mahbub E NoorLecture 12: Preparing featuresUniversity of Barishal2 / 19

Meet the data: invented student records

  • 150 invented students, made for this course
  • Target: passed (yes / no)
  • isna().sum() counts missing values per column
  • Some hours and GPAs are missing, as in real data
Code
import pandas as pd
st = pd.read_csv("students.csv")
print(st.head())
print(st.isna().sum())
Output
   hours  attendance  prior_gpa  dept passed
0    NaN          55       3.28   CSE    yes
1    NaN          88       2.37   EEE    yes
2    9.3          81       3.52  Math    yes
3    2.7          83       3.52   EEE     no
4    3.6          78       3.44   CSE     no
hours         8
attendance    0
prior_gpa     6
dept          0
passed        0
dtype: int64
Md Mahbub E NoorLecture 12: Preparing featuresUniversity of Barishal3 / 19

Missing values: drop or fill?

  • dropna() removes rows with any missing value
  • Fast, but throws data away
  • fillna(median) keeps rows, guessing a typical value
  • Choose, and say which you chose in your report
Code
print(len(st), len(st.dropna()))
filled = st.copy()
for col in ["hours", "prior_gpa"]:
    filled[col] = filled[col].fillna(filled[col].median())
print(filled.isna().sum().sum())
Output
150 136
0
Md Mahbub E NoorLecture 12: Preparing featuresUniversity of Barishal4 / 19

One-hot encoding: categories become 0/1 columns

A model cannot multiply the word CSE. One-hot encoding gives each category its own yes/no column.

deptdept_CSEdept_EEEdept_Math
CSE100
EEE010
Math001
CSE100
Md Mahbub E NoorLecture 12: Preparing featuresUniversity of Barishal5 / 19

One-hot encoding in pandas

pd.get_dummies creates the 0/1 columns · dtype=int shows 0 and 1 instead of False/True · Every category gets its own column · In pipelines we use OneHotEncoder instead (later)

Code
dummies = pd.get_dummies(filled["dept"], prefix="dept", dtype=int)
print(dummies.head(4))
Output
   dept_CSE  dept_EEE  dept_Math
0         1         0          0
1         0         1          0
2         0         0          1
3         0         1          0
Md Mahbub E NoorLecture 12: Preparing featuresUniversity of Barishal6 / 19

By hand: why scale matters for k-nearest neighbours

Distance from student A (hours 2, attendance 90) to B (hours 10, attendance 85) and C (hours 3, attendance 60).

Code
# Unscaled: attendance dominates.
#   C looks far, B looks close, although
#   B studies 8 hours more than A!
# After scaling both columns to similar ranges,
#   hours matter again and C is nearer.
pairhours gapattendance gapdistance
A to B859.4
A to C13030.0
A to B, scaled0.670.080.67
A to C, scaled0.080.500.51

Next row: arrow key or the ▶ button. Predict it first!

Md Mahbub E NoorLecture 12: Preparing featuresUniversity of Barishal7 / 19

Watch it: scaling changes the neighbours

WATCH FOR

  • 14 real rows from students.csv; the star is one student
  • Unscaled: neighbours chosen almost only by attendance
  • The scaler stretches hours to a fair spread
  • Scaled: the three nearest neighbours change

Silent video: press play, pause any time.

Md Mahbub E NoorLecture 12: Preparing featuresUniversity of Barishal8 / 19

StandardScaler: mean 0, standard deviation 1

Each column: subtract its mean, divide by its std · Every feature now has a similar range · Fit the scaler on training data only · Then transform both training and test data

Code
from sklearn.preprocessing import StandardScaler
num = filled[["hours", "attendance", "prior_gpa"]]
scaled = StandardScaler().fit_transform(num)
print(num.mean().round(1).to_dict())
print(scaled.mean(axis=0).round(2) + 0.0, scaled.std(axis=0).round(2))
Output
{'hours': 6.1, 'attendance': 70.0, 'prior_gpa': 3.0}
[0. 0. 0.] [1. 1. 1.]
Md Mahbub E NoorLecture 12: Preparing featuresUniversity of Barishal9 / 19

Data leakage: the silent killer

1

What it is

Information from the test set sneaks into training, so scores look better than reality.

2

Common causes

Scaling or filling with statistics of ALL data; choosing settings on the test set; duplicate rows.

3

The fix

Split first. Fit every preparation step on training data only. Pipelines do this for you.

Md Mahbub E NoorLecture 12: Preparing featuresUniversity of Barishal10 / 19

Watch it: leakage happening, then prevented

WATCH FOR

  • Wrong: the scaler learns from all rows, test included
  • Splitting afterwards is too late
  • Right: split first, lock the test set away
  • The pipeline fits on training rows only

Silent video: press play, pause any time.

Md Mahbub E NoorLecture 12: Preparing featuresUniversity of Barishal11 / 19

A pipeline does every step, in the right order

Numbers: fill missing with the median, then scale · Categories: one-hot encode · Then the model · Inside cross-validation, each step is fitted on training folds only

Code
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import OneHotEncoder
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import cross_val_score
X = st[["hours", "attendance", "prior_gpa", "dept"]]
y = st["passed"]
prep = ColumnTransformer([
    ("num", make_pipeline(SimpleImputer(strategy="median"),
                          StandardScaler()),
     ["hours", "attendance", "prior_gpa"]),
    ("cat", OneHotEncoder(), ["dept"])])
model = make_pipeline(prep, KNeighborsClassifier())
print(round(cross_val_score(model, X, y, cv=5).mean(), 3))
Output
0.893
Md Mahbub E NoorLecture 12: Preparing featuresUniversity of Barishal12 / 19

Does scaling really matter? Measure it.

  • Same pipeline, with and without the scaler
  • Same data, same cross-validation
  • k-NN gains a lot from scaling
  • A decision tree would barely change (try it)
Code
def build(scale):
    num_steps = [SimpleImputer(strategy="median")]
    if scale:
        num_steps.append(StandardScaler())
    prep = ColumnTransformer([
        ("num", make_pipeline(*num_steps),
         ["hours", "attendance", "prior_gpa"]),
        ("cat", OneHotEncoder(), ["dept"])])
    return make_pipeline(prep, KNeighborsClassifier())
for scale in [False, True]:
    s = cross_val_score(build(scale), X, y, cv=5).mean()
    print("scaled" if scale else "unscaled", round(s, 3))
Output
unscaled 0.687
scaled 0.893
Md Mahbub E NoorLecture 12: Preparing featuresUniversity of Barishal13 / 19

Your turn 10 minutes

  1. 1Revisit Practice 1, task B2: k-NN on wine
  2. 2Put StandardScaler and KNeighborsClassifier in a pipeline
  3. 3Compare 5-fold CV accuracy with and without scaling
  4. 4How big is the difference?
Code
from sklearn.datasets import load_wine
Xw, yw = load_wine(return_X_y=True)
plain = KNeighborsClassifier()
scaled = make_pipeline(StandardScaler(), KNeighborsClassifier())
...

Starter code: replace the ... with your own code

Md Mahbub E NoorLecture 12: Preparing featuresUniversity of Barishal14 / 19

Common feature-preparation mistakes

Read the last line of an error first. It names the problem.

What you typedWhat Python saysWhat it means
KNeighborsClassifier().fit(filled[["hours", "dept"]], filled["passed"])
ValueError: could not convert string to float: 'CSE'
Text column: one-hot encode dept first.
KNeighborsClassifier().fit(st[["hours"]], st["passed"])
ValueError: Input X contains NaN.
Missing values: fill or drop them first (SimpleImputer).
OneHotEncoder().fit_transform(st["dept"])
ValueError: Expected a 2-dimensional container but got <class 'pandas.Series'> instead. Pass a DataFrame containing a si ...
Encoders want a table: use st[["dept"]] with double brackets.
Md Mahbub E NoorLecture 12: Preparing featuresUniversity of Barishal15 / 19

Interactive lab: Feature Prep Lab

In the lab you can

  1. Missing valuesDrop, median, mean or 0: see the data and the CV score.
  2. Categories to numbersOne-hot versus 1, 2, 3: the distances a model sees.
  3. Scaling and neighboursClick a student; the 3 nearest change after scaling.
  4. Data leakageOver 90% on coin tosses in the wrong order; spot the leak.
Open the lab ►︎

Opens in a new tab and works offline. All labs: labs/START_HERE.html

Screenshot of the Feature Prep Lab
Md Mahbub E NoorLecture 12: Preparing featuresUniversity of Barishal16 / 19

Today's toolkit

df.isna().sum()Count missing values
fillna / SimpleImputerFill missing values
pd.get_dummies / OneHotEncoderCategories to 0/1 columns
StandardScaler()Mean 0, std 1
ColumnTransformerDifferent steps per column
make_pipeline(...)Chain steps and model
leakageTest info sneaking into training
Md Mahbub E NoorLecture 12: Preparing featuresUniversity of Barishal17 / 19

Homework 12

Due before Lecture P2 · [date]

1

By hand

Scale [2, 4, 6] with StandardScaler by hand: subtract the mean, divide by the standard deviation (divide by count). Check in code.

2

Wine with scaling

Choose k for a scaled k-NN pipeline on wine with cross-validation, then report test accuracy once.

3

Tree vs k-NN

On students.csv, compare the pipeline with k-NN and with a decision tree, each scaled and unscaled. Table of 4 CV scores.

4

Leakage hunt

Describe one way leakage could happen in a project of your choice, and how a pipeline prevents it.

5

Explain

Why is one-hot encoding better than numbering categories 1, 2, 3?

Submit on [LMS / Google Classroom]: your notebook file HW12_<your ID>.ipynb, or a Colab link set to “Anyone with the link can view”.
Md Mahbub E NoorLecture 12: Preparing featuresUniversity of Barishal18 / 19