Skip to content

Exercise 1: penguins, three classes

Chapter 43 6 code blocks 1427 words

The code in this chapter uses these filesrun it on your own machine

Why this exercise

Until now everything was binary: yes or no, 0 or 1. precision_score(y, b) returned one number.

Now there are three classes. And the pattern you learned breaks: precision_score raises an error, the confusion matrix is 3x3 instead of 2x2, and the thing called "TP" disappears.

That's transfer: learning what to do when the pattern breaks.

Getting to know the data

python8 lines
import pandas as pd

pg = pd.read_csv(DATA / "penguins.csv")
print("shape:", pg.shape)
print()
print(pg.dtypes.to_string())
print()
show(pg.head(6))
Output
shape: (344, 7)

species               object
island                object
bill_length_mm       float64
bill_depth_mm        float64
flipper_length_mm    float64
body_mass_g          float64
sex                   object
speciesislandbill_length_mmbill_depth_mmflipper_length_mmbody_mass_gsex
AdelieTorgersen39.118.71813 750Male
AdelieTorgersen39.517.41863 800Female
AdelieTorgersen40.3181953 250Female
AdelieTorgersenNaNNaNNaNNaNNaN
AdelieTorgersen36.719.31933 450Female
AdelieTorgersen39.320.61903 650Male
python9 lines
print("Classes:")
print(pg.species.value_counts().to_string())
print()
print("ratio:")
print(pg.species.value_counts(normalize=True).round(3).to_string())
print()
missing = pg.isna().sum()
print("Missing values:")
print(missing[missing > 0].to_string())
Output
Classes:
species
Adelie       152
Gentoo       124
Chinstrap     68

ratio:
species
Adelie       0.442
Gentoo       0.360
Chinstrap    0.198

Missing values:
bill_length_mm        2
bill_depth_mm         2
flipper_length_mm     2
body_mass_g           2
sex                  11

The task

Walk the 12 steps from Chapter 1 yourself. Nobody shows you the way. Write the following in your notebook:

python15 lines
import pandas as pd
show(pd.DataFrame([
    {"#": 1, "Answer to write down": "How many rows, how many columns"},
    {"#": 2, "Answer to write down": "\"I predict ___ from ___\": one sentence"},
    {"#": 3, "Answer to write down": "Task type: regression / binary / multiclass"},
    {"#": 4, "Answer to write down": "The target ratio and the BASELINE number"},
    {"#": 5, "Answer to write down": "Did you split? Do you need stratify?"},
    {"#": 6, "Answer to write down": "The dummy result"},
    {"#": 7, "Answer to write down": "Which columns you dropped, and WHY"},
    {"#": 8, "Answer to write down": "What you did about missing values and types"},
    {"#": 9, "Answer to write down": "Your first honest result"},
    {"#": 10, "Answer to write down": "Which metric you chose, and WHY"},
    {"#": 11, "Answer to write down": "Which group the mistakes cluster in"},
    {"#": 12, "Answer to write down": "Which ONE change you made, and the result"},
]))
#Answer to write down
1How many rows, how many columns
2"I predict ___ from ___": one sentence
3Task type: regression / binary / multiclass
4The target ratio and the BASELINE number
5Did you split? Do you need stratify?
6The dummy result
7Which columns you dropped, and WHY
8What you did about missing values and types
9Your first honest result
10Which metric you chose, and WHY
11Which group the mistakes cluster in
12Which ONE change you made, and the result

And most important, question 13:

Which of your hypotheses was REJECTED?

If not a single one was rejected, you didn't test anything. Go back.

Warnings, but not the solution

Binary metrics don't work

precision_score(y_te, pred) raises an error with three classes:

ValueError: Target is multiclass but average='binary'

You need the average= parameter. Which do you choose: macro, micro or weighted? Reread Chapters 8 and 9. The classes are 152 / 124 / 68, which is not an even split.

The sex column has 11 missing values

It's a text column. SimpleImputer(strategy="median") doesn't work with text. What do you do?

There are three ways and each has a price. Choose one and write down why you chose it.

Where your first suspicion should be

Look at the island column. groupby it against the target. What do you see?

It isn't leakage, but it tells you something important about the data.

Checking yourself

Work it out yourself first. Then look at these numbers.

python33 lines
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.impute import SimpleImputer
from sklearn.linear_model import LogisticRegression
from sklearn.dummy import DummyClassifier
from sklearn.metrics import accuracy_score, f1_score, confusion_matrix, classification_report

num = ["bill_length_mm", "bill_depth_mm", "flipper_length_mm", "body_mass_g"]
cat = ["island", "sex"]
X, y = pg[num + cat], pg["species"]
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.3, random_state=42, stratify=y)

baseline = accuracy_score(y_te, DummyClassifier(strategy="most_frequent").fit(X_tr[num], y_tr).predict(X_te[num]))

pipe = Pipeline([
    ("prep", ColumnTransformer([
        ("num", Pipeline([("impute", SimpleImputer(strategy="median")),
                          ("scale", StandardScaler())]), num),
        ("cat", Pipeline([("impute", SimpleImputer(strategy="most_frequent")),
                          ("encode", OneHotEncoder(handle_unknown="ignore"))]), cat),
    ])),
    ("model", LogisticRegression(max_iter=3000)),
]).fit(X_tr, y_tr)

b = pipe.predict(X_te)
print(f"BASELINE (most_frequent): {baseline:.4f}")
print(f"Model accuracy          : {accuracy_score(y_te, b):.4f}")
print(f"Model macro F1          : {f1_score(y_te, b, average='macro'):.4f}")
print(f"Model weighted F1       : {f1_score(y_te, b, average='weighted'):.4f}")
cv = cross_val_score(pipe, X, y, cv=5, scoring="f1_macro")
print(f"CV macro F1             : {cv.mean():.4f} ±{cv.std():.4f}")
Output
BASELINE (most_frequent): 0.4423
Model accuracy          : 0.9904
Model macro F1          : 0.9918
Model weighted F1       : 0.9904
CV macro F1             : 0.9939 ±0.0076
python7 lines
print("The 3x3 confusion matrix:")
classes = sorted(y.unique())
cm = confusion_matrix(y_te, b, labels=classes)
print(pd.DataFrame(cm, index=[f"truth: {s}" for s in classes],
                   columns=[f"predicted: {s}" for s in classes]).to_string())
print()
print(classification_report(y_te, b, digits=4))
Output
The 3x3 confusion matrix:
                  predicted: Adelie  predicted: Chinstrap  predicted: Gentoo
truth: Adelie                    46                     0                  0
truth: Chinstrap                  0                    21                  0
truth: Gentoo                     1                     0                 36

              precision    recall  f1-score   support

      Adelie     0.9787    1.0000    0.9892        46
   Chinstrap     1.0000    1.0000    1.0000        21
      Gentoo     1.0000    0.9730    0.9863        37

    accuracy                         0.9904       104
   macro avg     0.9929    0.9910    0.9918       104
weighted avg     0.9906    0.9904    0.9904       104
python2 lines
print("The island column against the target:")
print(pd.crosstab(pg.island, pg.species).to_string())
Output
The island column against the target:
species    Adelie  Chinstrap  Gentoo
island                              
Biscoe         44          0     124
Dream          56         68       0
Torgersen      52          0       0

Look at the last table. Only Adelie live on Torgersen island. Gentoo dominate on Biscoe. That isn't leakage, it's real geography. So island is a useful feature, and it shouldn't be dropped.

What you learned

If your result came out very high, don't be surprised. The penguins dataset is easy: the classes separate almost completely by bill and flipper measurements.

That's a lesson too: on an easy dataset every model does well. To compare models you need a hard dataset. The next exercise is harder.

Questions about this chapter?

The course, homework review and the other five books are in Uzbek. Questions in English are welcome on Telegram.

Write on Telegram Contents